/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2000-2012
 *					All rights reserved
 *
 *  This file is part of GPAC / wave audio render module
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  GPAC is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */


#include <gpac/modules/audio_out.h>
/*symbian audio stuff*/
#include <mdaaudiooutputstream.h>
#include <mda/common/audio.h>

#define EPOC_MAX_BUFFERS	8

enum {
	EPOC_AUDIO_INIT = 0,
	EPOC_AUDIO_OPEN,
	EPOC_AUDIO_PLAY,
	EPOC_AUDIO_ERROR,
};

class EPOCAudio : public MMdaAudioOutputStreamCallback
{
public:
	EPOCAudio();
	virtual ~EPOCAudio();

	virtual void MaoscOpenComplete( TInt aError );
	virtual void MaoscBufferCopied( TInt aError, const TDesC8& a_Buffer);
	virtual void MaoscPlayComplete( TInt );

	GF_Err Open(u32 sample_rate, Bool stereo);
	void Close(Bool and_wait);

	CMdaAudioOutputStream* m_stream;
	TMdaAudioDataSettings m_settings;
	u32 state;

	/*desired config*/
	u32 cfg_num_buffers, cfg_total_duration, init_vol, init_pan;
	/*actual config*/
	u32 num_buffers, total_duration, buffer_len;

	/*audio buffers*/
	char *buffers[EPOC_MAX_BUFFERS];
	TPtrC8 sent_buffers[EPOC_MAX_BUFFERS];
	u32 buffer_size;
	u32 current_buffer, nb_buffers_queued;
};

EPOCAudio::EPOCAudio()
{
	u32 i;
	m_stream = NULL;
	for (i=0; i<EPOC_MAX_BUFFERS; i++) buffers[i] = NULL;
	state = EPOC_AUDIO_INIT;
	num_buffers = 0;
	init_vol = 100;
}

EPOCAudio::~EPOCAudio()
{
	if (m_stream) {
		delete m_stream;
	}
}

GF_Err EPOCAudio::Open(u32 sample_rate, Bool stereo)
{
	TInt res = 0;
	u32 count;
	TMdaAudioDataSettings::TAudioCaps epoc_sr;


	switch (sample_rate) {
	case 8000:
		epoc_sr = TMdaAudioDataSettings::ESampleRate8000Hz;
		break;
	case 11025:
		epoc_sr = TMdaAudioDataSettings::ESampleRate11025Hz;
		break;
	case 12000:
		epoc_sr = TMdaAudioDataSettings::ESampleRate12000Hz;
		break;
	case 16000:
		epoc_sr = TMdaAudioDataSettings::ESampleRate16000Hz;
		break;
	case 22050:
		epoc_sr = TMdaAudioDataSettings::ESampleRate22050Hz;
		break;
	case 24000:
		epoc_sr = TMdaAudioDataSettings::ESampleRate24000Hz;
		break;
	case 32000:
		epoc_sr = TMdaAudioDataSettings::ESampleRate32000Hz;
		break;
	case 44100:
		epoc_sr = TMdaAudioDataSettings::ESampleRate44100Hz;
		break;
	case 48000:
		epoc_sr = TMdaAudioDataSettings::ESampleRate48000Hz;
		break;
	default:
		return GF_NOT_SUPPORTED;
	}

	state = EPOC_AUDIO_INIT;

	gf_sleep(10);
	TRAP(res, m_stream = CMdaAudioOutputStream::NewL(*this) );
	if ((res!=KErrNone) || !m_stream) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[EPOCAudio] Cannot create output audio stream\n"));
		return GF_IO_ERR;
	}
	m_stream->Open(&m_settings);

	/*wait for ack - if not getting it in 50*40 = 2sec, abort*/
	count = 50;
	while (count) {
		if (state == EPOC_AUDIO_OPEN) break;
		else if (state == EPOC_AUDIO_ERROR) {
			return GF_IO_ERR;
		}
		gf_sleep(40);

		TInt error;
		CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle);
		count--;
	}
	if (state != EPOC_AUDIO_OPEN) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[EPOCAudio] Failed to open sound device - is it present?\n"));
		return GF_NOT_SUPPORTED;
	}

	TRAP(res, m_stream->SetAudioPropertiesL(epoc_sr, stereo ? TMdaAudioDataSettings::EChannelsStereo : TMdaAudioDataSettings::EChannelsMono) );
	m_stream->SetPriority(EPriorityAbsoluteHigh, EMdaPriorityPreferenceTime );
	m_stream->SetVolume(init_vol * m_stream->MaxVolume() / 100);

	current_buffer = nb_buffers_queued = 0;

	GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[EPOCAudio] output audio stream ready - sample rate %d - %d channels\n", sample_rate, stereo ? 2 : 1));
	return GF_OK;
}

void EPOCAudio::Close(Bool and_wait)
{
	u32 i;
	if (m_stream) {
		if (state==EPOC_AUDIO_PLAY) {
#if 0
			m_stream->Stop();

			while (0 && and_wait) {

				if (state != EPOC_AUDIO_PLAY) break;
				gf_sleep(1);

				TInt error;
				CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle);
			}
#endif
		}
		delete m_stream;
		m_stream = NULL;
	}
	for (i=0; i<num_buffers; i++) {
		if (buffers[i]) gf_free(buffers[i]);
		buffers[i] = NULL;
	}
	num_buffers = 0;
	state = EPOC_AUDIO_INIT;
	GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[EPOCAudio] output audio stream closed\n"));
}

void EPOCAudio::MaoscOpenComplete(TInt aError)
{
	if (aError) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[EPOCAudio] Failed to open sound device - error %d\n", aError));
	} else {
		GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[EPOCAudio] Sound device opened\n", aError));
		state = EPOC_AUDIO_OPEN;
	}
}

void EPOCAudio::MaoscBufferCopied(TInt aError, const TDesC8& a_Buffer)
{
	assert(nb_buffers_queued);
	nb_buffers_queued--;
	state = nb_buffers_queued ? EPOC_AUDIO_PLAY : EPOC_AUDIO_OPEN;
}

void EPOCAudio::MaoscPlayComplete(TInt aError)
{
	if (aError) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[EPOCAudio] Playback stopped due to error %d\n", aError));
		state = EPOC_AUDIO_ERROR;
	} else {
		GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[EPOCAudio] Playback stopped due to user request\n"));
		state = EPOC_AUDIO_OPEN;
	}
}


static GF_Err EAUD_Setup(GF_AudioOutput *dr, void *os_handle, u32 num_buffers, u32 total_duration)
{
	EPOCAudio *ctx = (EPOCAudio *)dr->opaque;
	ctx->cfg_num_buffers = MAX(num_buffers, EPOC_MAX_BUFFERS);
	ctx->cfg_total_duration = total_duration;
	return GF_OK;
}

static void EAUD_Shutdown(GF_AudioOutput *dr)
{
	EPOCAudio *ctx = (EPOCAudio *)dr->opaque;
	ctx->Close(1);
}


/*we assume what was asked is what we got*/
static GF_Err EAUD_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *nbBitsPerSample, u32 channel_cfg)
{
	u32 snd_align, bps, i;
	GF_Err e;
	EPOCAudio *ctx = (EPOCAudio *)dr->opaque;

	ctx->Close(1);

	*nbBitsPerSample = 16;
	if (*NbChannels > 2) *NbChannels = 2;

	e = ctx->Open(*SampleRate, (*NbChannels ==2) ? 1 : 0);
	if (e) return e;

	snd_align = *NbChannels * 2;
	bps = snd_align **SampleRate;

	if (ctx->cfg_total_duration) {
		ctx->num_buffers = ctx->cfg_num_buffers;
		ctx->buffer_size = (bps*ctx->cfg_total_duration/1000) / ctx->num_buffers;
	} else {
		ctx->num_buffers = 4;
		/*use 25 ms buffers*/
		ctx->buffer_size = bps / 40;
	}
	ctx->buffer_size /= snd_align;
	ctx->buffer_size *= snd_align;

	ctx->buffer_len = ctx->buffer_size * 1000 / bps;
	ctx->total_duration = ctx->buffer_len * ctx->num_buffers;

	for (i=0; i<ctx->num_buffers; i++) {
		ctx->buffers[i] = (char *)gf_malloc(sizeof(char)*ctx->buffer_size);
	}
	GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[EPOCAudio] Output audio stream configured - %d buffers of %d ms each\n", ctx->num_buffers, ctx->buffer_len));

	return GF_OK;
}

static void EAUD_WriteAudio(GF_AudioOutput *dr)
{
	EPOCAudio *ctx = (EPOCAudio *)dr->opaque;

	/*no buffers available...*/
	if (ctx->nb_buffers_queued == ctx->num_buffers) {
		GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[EPOCAudio] Audio queue full - yielding to app\n"));
		User::After(0);
		TInt error;
		CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle);
		return;
	}

	while (ctx->nb_buffers_queued < ctx->num_buffers) {
		u32 written = dr->FillBuffer(dr->audio_renderer, ctx->buffers[ctx->current_buffer], ctx->buffer_size);
		//GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[EPOCAudio] Filling audio buffer %d / %d\n", ctx->current_buffer, ctx->num_buffers));
		if (!written) return;

		ctx->sent_buffers[ctx->current_buffer].Set((const TText8 *) ctx->buffers[ctx->current_buffer], written);

		ctx->nb_buffers_queued++;
		ctx->m_stream->WriteL(ctx->sent_buffers[ctx->current_buffer]);
		ctx->current_buffer = (ctx->current_buffer + 1) % ctx->num_buffers;
	}
}

static void EAUD_Play(GF_AudioOutput *dr, u32 PlayType)
{
}

static void EAUD_SetVolume(GF_AudioOutput *dr, u32 Volume)
{
	EPOCAudio *ctx = (EPOCAudio *)dr->opaque;
	ctx->init_vol = Volume;
	if (ctx->m_stream)
		ctx->m_stream->SetVolume(ctx->init_vol * ctx->m_stream->MaxVolume() / 100);
}

static void EAUD_SetPan(GF_AudioOutput *dr, u32 Pan)
{
}


static GF_Err EAUD_QueryOutputSampleRate(GF_AudioOutput *dr, u32 *desired_samplerate, u32 *NbChannels, u32 *nbBitsPerSample)
{
	*nbBitsPerSample = 16;
	if (*NbChannels > 2) *NbChannels = 2;
	return GF_OK;
}

static u32 EAUD_GetAudioDelay(GF_AudioOutput *dr)
{
	EPOCAudio *ctx = (EPOCAudio *)dr->opaque;
	return ctx->current_buffer*ctx->buffer_len;
}

static u32 EAUD_GetTotalBufferTime(GF_AudioOutput *dr)
{
	EPOCAudio *ctx = (EPOCAudio *)dr->opaque;
	return ctx->total_duration;
}


#ifdef __cplusplus
extern "C" {
#endif

void *EPOC_aout_new()
{
	GF_AudioOutput *driv;
	driv = (GF_AudioOutput *) gf_malloc(sizeof(GF_AudioOutput));
	memset(driv, 0, sizeof(GF_AudioOutput));
	GF_REGISTER_MODULE_INTERFACE(driv, GF_AUDIO_OUTPUT_INTERFACE, "EPOC Audio Output", "gpac distribution")

	driv->Setup = EAUD_Setup;
	driv->Shutdown = EAUD_Shutdown;
	driv->ConfigureOutput = EAUD_ConfigureOutput;
	driv->GetAudioDelay = EAUD_GetAudioDelay;
	driv->GetTotalBufferTime = EAUD_GetTotalBufferTime;
	driv->SetVolume = EAUD_SetVolume;
	driv->SetPan = EAUD_SetPan;
	driv->Play = EAUD_Play;
	driv->QueryOutputSampleRate = EAUD_QueryOutputSampleRate;
	driv->WriteAudio = EAUD_WriteAudio;

	driv->SelfThreaded = 0;
	driv->opaque = new EPOCAudio();

	return driv;
}

void EPOC_aout_del(void *ifce)
{
	GF_AudioOutput *dr = (GF_AudioOutput *) ifce;
	EPOCAudio *ctx = (EPOCAudio*)dr->opaque;
	delete ctx;
	gf_free(dr);
}

#ifdef __cplusplus
}
#endif

