/*
    SDL - Simple DirectMedia Layer
    Copyright (C) 1997-2009 Sam Lantinga

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Sam Lantinga
    slouken@devolution.com
*/

/*
    SDL_epocaudio.cpp
    Epoc based SDL audio driver implementation
    
    Markus Mertama
*/

#ifdef SAVE_RCSID
static char rcsid =
 "@(#) $Id: SDL_epocaudio.c,v 0.0.0.0 2001/06/19 17:19:56 hercules Exp $";
#endif


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/stat.h>

#include "epoc_sdl.h"

#include <e32hal.h>


extern "C" {
#include "SDL_audio.h"
#include "SDL_error.h"
#include "SDL_audiomem.h"
#include "SDL_audio_c.h"
#include "SDL_timer.h"
#include "SDL_audiodev_c.h"
}

#include "SDL_epocaudio.h"

#include "streamplayer.h"


//#define DEBUG_AUDIO


/* Audio driver functions */

static int EPOC_OpenAudio(SDL_AudioDevice *thisdevice, SDL_AudioSpec *spec);
static void EPOC_WaitAudio(SDL_AudioDevice *thisdevice);
static void EPOC_PlayAudio(SDL_AudioDevice *thisdevice);
static Uint8 *EPOC_GetAudioBuf(SDL_AudioDevice *thisdevice);
static void EPOC_CloseAudio(SDL_AudioDevice *thisdevice);
static void EPOC_ThreadInit(SDL_AudioDevice *thisdevice);

static int Audio_Available(void);
static SDL_AudioDevice *Audio_CreateDevice(int devindex);
static void Audio_DeleteDevice(SDL_AudioDevice *device);


//void sos_adump(SDL_AudioDevice* thisdevice, void* data, int len);

#ifdef __WINS__
#define DODUMP
#endif

#ifdef DODUMP
NONSHARABLE_CLASS(TDump)
	{
	public:
	TInt Open();
	void Close();
	void Dump(const TDesC8& aDes);
	private:
		RFile iFile;
    	RFs iFs; 
	};
	
TInt TDump::Open()
	{
	TInt err = iFs.Connect();
	if(err == KErrNone)
		{
#ifdef __WINS__
_LIT(target, "C:\\sdlau.raw");
#else
_LIT(target, "E:\\sdlau.raw");
#endif 
		err = iFile.Replace(iFs, target, EFileWrite);
		}
	return err;
	}
void TDump::Close()
	{
	iFile.Close();
	iFs.Close();
	}
void TDump::Dump(const TDesC8& aDes)
	{
	iFile.Write(aDes);
	}
#endif


NONSHARABLE_CLASS(CSimpleWait) : public CTimer
	{
	public:
		void Wait(TTimeIntervalMicroSeconds32 aWait);
		static CSimpleWait* NewL();
	private:
		CSimpleWait();
		void RunL();
	};


CSimpleWait* CSimpleWait::NewL()
	{
	CSimpleWait* wait = new (ELeave) CSimpleWait();
	CleanupStack::PushL(wait);
	wait->ConstructL();
	CleanupStack::Pop();
	return wait;
	}

void CSimpleWait::Wait(TTimeIntervalMicroSeconds32 aWait)
	{
	After(aWait);
	CActiveScheduler::Start();
	}
	
CSimpleWait::CSimpleWait() : CTimer(CActive::EPriorityStandard)	
	{
	CActiveScheduler::Add(this);
	}

void CSimpleWait::RunL()
	{
	CActiveScheduler::Stop();
	}

const TInt KAudioBuffers(2);
	

NONSHARABLE_CLASS(CEpocAudio) : public CBase, public MStreamObs, public MStreamProvider
    {
    public:
    	static void* NewL(TInt BufferSize, TInt aFill);
    	inline static CEpocAudio& Current(SDL_AudioDevice* thisdevice);
    	
    	static void Free(SDL_AudioDevice* thisdevice);
 		
    	void Wait();
    	void Play();
    //	void SetBuffer(const TDesC8& aBuffer);
    	void ThreadInitL(TAny* aDevice);
    	void Open(TInt iRate, TInt iChannels, TUint32 aType, TInt aBytes);
    	~CEpocAudio();
    	TUint8* Buffer();
    	TBool SetPause(TBool aPause);
    #ifdef DODUMP
    	void Dump(const TDesC8& aBuf) {iDump.Dump(aBuf);}
    #endif
    private:
    	CEpocAudio(TInt aBufferSize);
    	void Complete(TInt aState, TInt aError);
    	TPtrC8 Data();
    	void ConstructL(TInt aFill);
    private:
    	TInt iBufferSize;
    	CStreamPlayer* iPlayer;
    	TInt iBufferRate;
    	TInt iRate;
    	TInt iChannels;
    	TUint32 iType;
    	TInt iPosition;
    	TThreadId iTid;
    	TUint8* iAudioPtr;
    	TUint8* iBuffer;
    //	TTimeIntervalMicroSeconds iStart;
    	TTime iStart;
    	TInt iTune;
    	CSimpleWait* iWait;
    #ifdef DODUMP
    	TDump iDump;
    #endif
    };

inline CEpocAudio& CEpocAudio::Current(SDL_AudioDevice* thisdevice)
	{
	return *static_cast<CEpocAudio*>((void*)thisdevice->hidden);
	}
	
/*

TBool EndSc(TAny*)
	{	
	CActiveScheduler::Stop();
	}
	
LOCAL_C void CleanScL()
	{
	CIdle* d = CIdle::NewLC(CActive:::EPriorityIdle);
	d->Start(TCallBack(EndSc));
	CActiveScheduler::Start();
	
	}
*/
	
void CEpocAudio::Free(SDL_AudioDevice* thisdevice)
	{
    CEpocAudio* ea = static_cast<CEpocAudio*>((void*)thisdevice->hidden);
    if(ea)
    	{
		ASSERT(ea->iTid == RThread().Id());
    	delete ea;
    	thisdevice->hidden = NULL;	
   
    	CActiveScheduler* as =  CActiveScheduler::Current();
    	ASSERT(as->StackDepth() == 0);    	
    	delete as;
    	CActiveScheduler::Install(NULL);
    	}
    ASSERT(thisdevice->hidden == NULL);
	}
	
CEpocAudio::CEpocAudio(TInt aBufferSize) : iBufferSize(aBufferSize), iPosition(-1) 
	{
	}

void* CEpocAudio::NewL(TInt aBufferSize, TInt aFill)
	{
	CEpocAudio* eAudioLib = new (ELeave) CEpocAudio(aBufferSize);
	CleanupStack::PushL(eAudioLib);
	eAudioLib->ConstructL(aFill);
	CleanupStack::Pop();
	return eAudioLib;
	}
	
void CEpocAudio::ConstructL(TInt aFill)
	{
	iBuffer = (TUint8*) User::AllocL(KAudioBuffers * iBufferSize);
	memset(iBuffer, aFill, KAudioBuffers * iBufferSize);
	iAudioPtr = iBuffer;
	}


TBool CEpocAudio::SetPause(TBool aPause)
	{
	if(aPause && iPosition >= 0)
		{
		iPosition = -1;
		if(iPlayer != NULL)
			iPlayer->Stop();
		}
	if(!aPause && iPosition < 0)
		{
		iPosition = 0;
		if(iPlayer != NULL)
			iPlayer->Start();
		}
	return iPosition < 0;
	}
	
void CEpocAudio::ThreadInitL(TAny* aDevice)
	{
	iTid = RThread().Id(); 
	CActiveScheduler* as =  new (ELeave) CActiveScheduler();
    CActiveScheduler::Install(as);
    
    EpocSdlEnv::AppendCleanupItem(TSdlCleanupItem((TSdlCleanupOperation)EPOC_CloseAudio, aDevice));
   
    iWait = CSimpleWait::NewL();
   
    iPlayer = new (ELeave) CStreamPlayer(*this, *this);
    iPlayer->ConstructL();	
    iPlayer->OpenStream(iRate, iChannels, iType);
    
    #ifdef DODUMP
    User::LeaveIfError(iDump.Open());
    #endif
	}
	
	
	
TUint8* CEpocAudio::Buffer()
	{
	iStart.UniversalTime();
//	iStart = iPlayer->Position();		
	return iAudioPtr;

	}
	
CEpocAudio::~CEpocAudio()
	{
	if(iWait != NULL)
		iWait->Cancel();
	delete iWait; 
	if(iPlayer != NULL)
		iPlayer->Close();
	delete iPlayer;
	delete iBuffer;
	}
	
void CEpocAudio::Complete(TInt aState, TInt aError)
	{
	if(aState == MStreamObs::EClose)
		{
		}
	if(iPlayer->Closed())
		return;
	switch(aError)
		{
		case KErrUnderflow:
		case KErrInUse:
			iPlayer->Start();
			break;
		case KErrAbort:
			iPlayer->Open();
		}
	}
	

void sos_adump(SDL_AudioDevice* thisdevice, void* data, int len)
	{
#ifdef DODUMP
	const TPtrC8 buf((TUint8*)data, len);
	CEpocAudio::Current(thisdevice).Dump(buf);
#endif
	}

const TInt KClip(256);
	
TPtrC8 CEpocAudio::Data()
	{
	if(iPosition < 0)
		return KNullDesC8();
	
	TPtrC8 data(iAudioPtr + iPosition, KClip);
	
#ifdef DODUMP
	iDump.Dump(data);
#endif
	
	iPosition += KClip;
	if(iPosition >= iBufferSize) 
		{
		
/*		if(iAudioPtr == iBuffer)
			iAudioPtr = iBuffer + iBufferSize;
		else
			iAudioPtr = iBuffer;
*/		
		iAudioPtr += iBufferSize;
		
		if((iAudioPtr - iBuffer) >= KAudioBuffers * iBufferSize)
			iAudioPtr = iBuffer;
		
		iPosition = -1;
		if(iWait->IsActive())
			{
			iWait->Cancel();
			CActiveScheduler::Stop();
			}
		}
	return data;
	}
		


	
void CEpocAudio::Play()
	{
	iPosition = 0;
	}

void CEpocAudio::Wait()
	{
	if(iPosition >= 0 /*&& iPlayer->Playing()*/)
		{
		const TInt64 bufMs = TInt64(iBufferSize - KClip) * TInt64(1000000);
		const TInt64 specTime =  bufMs / TInt64(iRate * iChannels * 2);
		iWait->After(specTime);
		
		CActiveScheduler::Start();
		TTime end;
		end.UniversalTime();
		const TTimeIntervalMicroSeconds delta = end.MicroSecondsFrom(iStart);
	
	
//		const TTimeIntervalMicroSeconds end = iPlayer->Position();
		
		
	
		
		const TInt diff = specTime - delta.Int64();
		
		if(diff > 0 && diff < 200000)
			{
			User::After(diff);
			}
		
		}
	else
		{
	User::After(10000); 
//	iWait->Wait(10000); //just give some time...	
		}	
	}
	
void CEpocAudio::Open(TInt aRate, TInt aChannels, TUint32 aType, TInt aBytes)	
	{
	iRate = aRate;
	iChannels = aChannels;
	iType = aType;
    iBufferRate = iRate * iChannels * aBytes; //1/x
	}
	

/* Audio driver bootstrap functions */

AudioBootStrap EPOCAudio_bootstrap = {
	"epoc\0\0\0",
	"EPOC streaming audio\0\0\0",
	Audio_Available,
	Audio_CreateDevice
};


static SDL_AudioDevice *Audio_CreateDevice(int /*devindex*/)
{
	SDL_AudioDevice *thisdevice;

	/* Initialize all variables that we clean on shutdown */
	thisdevice = (SDL_AudioDevice *)malloc(sizeof(SDL_AudioDevice));
	if ( thisdevice ) {
		memset(thisdevice, 0, (sizeof *thisdevice));
		thisdevice->hidden = NULL; /*(struct SDL_PrivateAudioData *)
			 malloc((sizeof thisdevice->hidden)); */
	}
	if ( (thisdevice == NULL) /*|| (thisdevice->hidden == NULL) */) {
		SDL_OutOfMemory();
		if ( thisdevice ) {
			free(thisdevice);
		}
		return(0);
	}
//	memset(thisdevice->hidden, 0, (sizeof *thisdevice->hidden));

	/* Set the function pointers */
	thisdevice->OpenAudio = EPOC_OpenAudio;
	thisdevice->WaitAudio = EPOC_WaitAudio;
	thisdevice->PlayAudio = EPOC_PlayAudio;
	thisdevice->GetAudioBuf = EPOC_GetAudioBuf;
	thisdevice->CloseAudio = EPOC_CloseAudio;
    thisdevice->ThreadInit = EPOC_ThreadInit;
	thisdevice->free = Audio_DeleteDevice;

	return thisdevice;
}


static void Audio_DeleteDevice(SDL_AudioDevice *device)
    {
	//free(device->hidden);
	free(device);
    }

static int Audio_Available(void)
{
	return(1); // Audio stream modules should be always there!
}


static int EPOC_OpenAudio(SDL_AudioDevice *thisdevice, SDL_AudioSpec *spec)
{
	SDL_TRACE("SDL:EPOC_OpenAudio");

	
	TUint32 type = KMMFFourCCCodePCM16;
	TInt bytes = 2;
	
	switch(spec->format)
		{
		case AUDIO_U16LSB: 
			type = KMMFFourCCCodePCMU16; 
			break;
		case AUDIO_S16LSB: 
			type = KMMFFourCCCodePCM16; 
			break;
		case AUDIO_U16MSB: 
			type = KMMFFourCCCodePCMU16B; 
			break;
		case AUDIO_S16MSB: 
			type = KMMFFourCCCodePCM16B; 
			break; 
			//8 bit not supported!
		case AUDIO_U8: 
		case AUDIO_S8:
		default:
			spec->format = AUDIO_S16LSB;
		};
	

	
	if(spec->channels > 2)
		spec->channels = 2;
	
	spec->freq = CStreamPlayer::ClosestSupportedRate(spec->freq);
	

	/* Allocate mixing buffer */
	const TInt buflen = spec->size;// * bytes * spec->channels;
//	audiobuf = NULL;
    
    TRAPD(err, thisdevice->hidden = static_cast<SDL_PrivateAudioData*>(CEpocAudio::NewL(buflen, spec->silence)));
    if(err != KErrNone)
        return -1;

	CEpocAudio::Current(thisdevice).Open(spec->freq, spec->channels, type, bytes);
	
	CEpocAudio::Current(thisdevice).SetPause(ETrue);
	
   // isSDLAudioPaused = 1;

    thisdevice->enabled = 0; /* enable only after audio engine has been initialized!*/

	/* We're ready to rock and roll. :-) */
	return(0);
}


static void EPOC_CloseAudio(SDL_AudioDevice* thisdevice)
    {
#ifdef DEBUG_AUDIO
    SDL_TRACE("Close audio\n");
#endif

	CEpocAudio::Free(thisdevice);
	}


static void EPOC_ThreadInit(SDL_AudioDevice *thisdevice)
    {
	SDL_TRACE("SDL:EPOC_ThreadInit");
    CEpocAudio::Current(thisdevice).ThreadInitL(thisdevice);
    RThread().SetPriority(EPriorityMore);
    thisdevice->enabled = 1;
    }

/* This function waits until it is possible to write a full sound buffer */
static void EPOC_WaitAudio(SDL_AudioDevice* thisdevice)
{
#ifdef DEBUG_AUDIO
    SDL_TRACE1("wait %d audio\n", CEpocAudio::AudioLib().StreamPlayer(KSfxChannel).SyncTime());
    TInt tics = User::TickCount();
#endif

	CEpocAudio::Current(thisdevice).Wait();

#ifdef DEBUG_AUDIO
    TInt ntics =  User::TickCount() - tics;
    SDL_TRACE1("audio waited %d\n", ntics);
    SDL_TRACE1("audio at %d\n", tics);
#endif
}


 
static void EPOC_PlayAudio(SDL_AudioDevice* thisdevice)
	{
 	if(CEpocAudio::Current(thisdevice).SetPause(SDL_GetAudioStatus() == SDL_AUDIO_PAUSED))
 		SDL_Delay(500); //hold on the busy loop
 	else
 		CEpocAudio::Current(thisdevice).Play();

#ifdef DEBUG_AUDIO
    SDL_TRACE("buffer has audio data\n");
#endif

	
#ifdef DEBUG_AUDIO
	SDL_TRACE1("Wrote %d bytes of audio data\n", buflen);
#endif
}

static Uint8 *EPOC_GetAudioBuf(SDL_AudioDevice* thisdevice)
	{
	return CEpocAudio::Current(thisdevice).Buffer();
	}



