#include <stdio.h>
#include <string.h>

#include "swis.h"

#include "WimpLib:Exception.h"
#include "WimpLib:File.h"
#include "Media.h"
#include "MediaDrv.h"
#include "Metadata.h"
#include "WimpLib:mem.h"
#include "Options.h"
#include "WimpLib:Utils.h"

extern const char ProgramName[];

typedef struct
{
	char    id[4];
	int     size;
	char    wavefmt[8];
	int     fmtsize;
	short   format;
	short   channels;
	int     samplerate;
	int     bytespersec;
	short   blocksize;
	short   bitspersample;
} File_Hdr;

// Returns true if MP3 wave, or not a wave at all
static bool File_EnsureMP3Wave(const char* filename)
{
	if (File_GetFileType(filename) == 0xfb1)
	{
		FILE*    file = fopen(filename, "rb");
		File_Hdr hdr;

		if (!file) return false;

		fread(&hdr, sizeof(hdr), 1, file);
		fclose(file);

		if (strncmp(hdr.id, "RIFF", 4))
			return false;

		if ((strncmp(hdr.wavefmt,"WAVEfmt ",8) || ((hdr.format != 0x55) && (hdr.format != 0x50)))
		    && strncmp(hdr.wavefmt,"RMP3",4))
			return false;

		return true;
	}

	return true;
}

extern const MediaDriver AmpDriver;

typedef struct
{
	unsigned int    handle;
	unsigned int    curtime;
	unsigned int    songtime;
} AmpDriver_ObjectData;

static AmpDriver_ObjectData* throw_ObjectData_New(void)
{
	AmpDriver_ObjectData* pData = throw_mem_alloc(sizeof(AmpDriver_ObjectData));

	pData->handle = 0;
	pData->curtime = 0;
	pData->songtime = 0;

	return pData;
}

static void ObjectData_Delete(AmpDriver_ObjectData* pData)
{
	mem_free(pData);
}

//------------------------------------------------------------------------------

static const _kernel_oserror* AMPlayer_InstanceCreate(unsigned int* phandle)
{
	// AMPlayer_Instance
	if (_swix(0x052e10, _INR(0,2)|_OUT(0), 0, 1, (int) &ProgramName[0], phandle))
	{
		return _swix(0x052e10, _INR(0,1)|_OUT(0), 0, 0, phandle);
	}

	return NULL;
}

static const _kernel_oserror* AMPlayer_InstanceDelete(unsigned int handle)
{
	// AMPlayer_Instance
	return _swix(0x052e10, _INR(0,2), 0, 2, handle);
}

static const _kernel_oserror* AMPlayer_Load(unsigned int handle, const char* file)
{
	// AMPlayer_Play
	return _swix(0x052e00, _IN(0)|_IN(1)|_IN(8), 2|0x80000000, (int) file, handle);
}

static const _kernel_oserror* AMPlayer_Play(unsigned int handle)
{
	// AMPlayer_Pause
	return _swix(0x052e02, _IN(0)|_IN(8), 1|0x80000000, handle);
}

static const _kernel_oserror* AMPlayer_Stop(unsigned int handle)
{
	// AMPlayer_Stop
	return _swix(0x052e01, _IN(0)|_IN(8), 0|0x80000000, handle);
}

static const _kernel_oserror* AMPlayer_Pause(unsigned int handle)
{
	// AMPlayer_Pause
	return _swix(0x052e02, _IN(0)|_IN(8), 0|0x80000000, handle);
}

static const _kernel_oserror* AMPlayer_Locate(unsigned int handle, unsigned int setpos)
{
	// AmpPlayer_Locate
	return _swix(0x052e03, _IN(0)|_IN(1)|_IN(8), 0|0x80000000, setpos, handle);
}

static const _kernel_oserror* AMPlayer_Vol(unsigned int handle, unsigned int set, unsigned int* read)
{
	// AMPlayer_Control
	return _swix(0x052e05, _IN(0)|_IN(1)|_IN(8)|_OUT(1), 0|0x80000000, set, handle, read);
}

typedef struct
{
	int     flags;
	int     usage;
	int     songtime;
	int     time;
	char*   title;
	char*   artist;
	char*   album;
	char*   year;
	char*   comment;
	int     leftVU;
	int     rightVU;
	int     volume;
	void*   err;
	char*   nextfilename;
	int     genre;
	int     track;
	int     min_bitrate;
	int     max_bitrate;
} AMInfo;

typedef struct
{
	char    vers[4];
	int     layer;
	int     freq;
	int     rate;
	int     mode;
	int     channels;
	int     flags;
	void*   leftDCT;
	void*   rightDCT;
} FrameInfo;

static void throw_AMPlayer_Info(MediaObject* pobj, AmpDriver_ObjectData* pData, Media_Status* status)
{
	unsigned int state;
	AMInfo* pInfo = NULL;
	FrameInfo* pFrame = NULL;

	// AMPlayer_Info
	throw_os(_swix(0x052e04, _IN(0)|_INR(2,3)|_IN(8)|_OUT(0)|_OUTR(2,3), 0|0x80000000, 0, 0, pData->handle
				, &state, &pInfo, &pFrame));

	if (status)
	{
		if (state)
			*status = status_busy;
		else
			*status = status_stop;
	}

	if (pInfo)
	{
		if (pInfo->flags & 1)
		{
			if (pInfo->songtime && (pData->songtime != pInfo->songtime))
			{
				pData->songtime = pInfo->songtime;
				pobj->m_Updates |= Media_Updated_Length;
			}
		}
		if (pInfo->flags & 2)
		{
			pData->curtime = pInfo->time;
			if (pData->songtime < pData->curtime)
			{
				pData->songtime = pData->curtime;
				pobj->m_Updates |= Media_Updated_Length;
			}
		}
		if (pInfo->flags & 4)
		{
			if (strlen(pInfo->title) > 0)
				throw_MediaObject_SetText(pobj, EMetaId_StreamTitle, EMetaOrigin_Stream, pInfo->title);

			if (strlen(pInfo->artist) > 0)
				throw_MediaObject_SetText(pobj, EMetaId_StreamArtist, EMetaOrigin_Stream, pInfo->artist);

			if (strlen(pInfo->album) > 0)
				throw_MediaObject_SetText(pobj, EMetaId_StreamAlbum, EMetaOrigin_Stream, pInfo->album);
			if (strlen(pInfo->year) > 0)
				throw_MediaObject_SetText(pobj, EMetaId_StreamDate, EMetaOrigin_Stream, pInfo->year);
			if (pInfo->flags & 0x80)
				throw_MediaObject_SetMeta(pobj, EMetaId_StreamTrackNumber, EMetaOrigin_Stream, (void*) pInfo->track, 4);
		}
		if (status && pFrame && pFrame->layer) *status = status_play;
	}

	if (pInfo && pFrame && pFrame->layer
	&&  !MediaObject_FindMeta(pobj, EMetaId_StreamType))
	{
		throw_MediaObject_SetText(pobj, EMetaId_StreamType, EMetaOrigin_Stream
			, SPrintf("MPEG %s Layer %1d - %3d kbits/s", pFrame->vers, pFrame->layer, pFrame->rate));
	}
}

//------------------------------------------------------------------------------

static void AmpDriver_SetConfig(MediaObject* pobj, Media_Config type, const void* value);

static const char AmpDriver_Name[] = "AMPlayer";

static bool throw_AmpDriver_LoadObject(MediaObject* pobj, const char* pObjectName)
{
	AmpDriver_ObjectData* volatile	pData = NULL;

	if (!File_EnsureMP3Wave(pObjectName)) return false;

	pData = throw_ObjectData_New();

	Media_Attach(pobj, pData, Media_HType_File);

	// Ensure modules are loaded
	throw_os(RMEnsure(AmpDriver_Name, 139, "System:Modules.Audio.MP3.AMPlayer"));

	// create new instance
	throw_os(AMPlayer_InstanceCreate(&pData->handle));

	// AMPlayer_Control,1
	_swix(0x52E05, _IN(0)|_IN(1)|_IN(8), 1|0x100|0x80000000, Options()->Drivers.AMPlayer.BufferSize, pData->handle);

	// load a new file
	throw_os(AMPlayer_Load(pData->handle, pObjectName));

	throw_MediaObject_SetText(pobj, EMetaId_ModuleName, EMetaOrigin_Stream, AmpDriver_Name);
	throw_MediaObject_SetMeta(pobj, EMetaId_ModuleHandle, EMetaOrigin_Stream, (void*) pData->handle, 0);

	return true;
}

static void AmpDriver_UnLoadObject(MediaObject* pobj)
{
	AmpDriver_ObjectData* pData = pobj->m_pData;

	if (pData)
	{
		AMPlayer_Stop(pData->handle);
		// delete new instance
		AMPlayer_InstanceDelete(pData->handle);

		ObjectData_Delete(pData);
		pobj->m_pData = NULL;

		if (Options()->Music_Files.bUnloadModules)
			RMKill("AMPlayer");
	}
}

static void AmpDriver_Play(MediaObject* pobj, uint64_t currentpos)
{
	AmpDriver_ObjectData* pData = pobj->m_pData;
	unsigned int pos = (unsigned int) currentpos;

	// if pos = 0, song is either started or restarted
	if (pos == 0)
	{
		AMPlayer_Pause(pData->handle);
		AmpDriver_SetConfig(pobj, Media_CfgFrequency, &pobj->m_iSpeed);
		AmpDriver_SetConfig(pobj, Media_CfgDMASize, &Options()->Music_Files.DMASize);
	}
	if (pobj->m_Status == status_play)
		AMPlayer_Locate(pData->handle, pos);
	else
		pobj->m_Status = status_busy;
	AMPlayer_Play(pData->handle);
}

static void AmpDriver_Freeze(MediaObject* pobj, bool on)
{
	AmpDriver_ObjectData* pData = pobj->m_pData;

	if (on)
	{
		AMPlayer_Pause(pData->handle);
		pobj->m_Status = status_pause;
	}
	else
	{
		AMPlayer_Play(pData->handle);
		pobj->m_Status = status_busy;
		AmpDriver_SetConfig(pobj, Media_CfgFrequency, &pobj->m_iSpeed);
		AmpDriver_SetConfig(pobj, Media_CfgDMASize, &Options()->Music_Files.DMASize);
	}
}

static void AmpDriver_Stop(MediaObject* pobj)
{
	AmpDriver_ObjectData* pData = pobj->m_pData;

	AMPlayer_Pause(pData->handle);
	pobj->m_Status = status_stop;
}

static void AmpDriver_Seek(MediaObject* pobj, EMedia_Seek set)
{
	AmpDriver_ObjectData* pData = pobj->m_pData;

	switch(set)
	{
		case EMedia_Seek_Start:
		case EMedia_Seek_SectionStart:
		{
			AmpDriver_Play(pobj, 0);
		}
		break;
		case EMedia_Seek_PreviousPos:
		{
			unsigned int pos = pData->curtime;

			// Rewind only to start
			if (pos < 100)
				pos = 0;
			else
				pos -= 100;

			AmpDriver_Play(pobj, (uint64_t) pos);
		}
		break;
		case EMedia_Seek_NextPos:
		{
			AmpDriver_ObjectData* pData = pobj->m_pData;
			unsigned int pos = pData->curtime + 100;

			if (pos < pData->songtime)
				AmpDriver_Play(pobj, (uint64_t) pos);
		}
		break;
		default: ;
	}
}

static void AmpDriver_SetLoopMode(MediaObject* pobj, Media_LoopMode mode)
{
	pobj->m_LoopMode = mode;
}

static Media_LoopMode AmpDriver_GetLoopMode(MediaObject* pobj)
{
	return pobj->m_LoopMode;
}

static Media_Status AmpDriver_GetStatus(MediaObject* pobj)
{
	AmpDriver_ObjectData* pData = pobj->m_pData;
	Media_Status status;

	if ((pobj->m_Status == status_play)
	||  (pobj->m_Status == status_busy))
	{
		try
		{
			throw_AMPlayer_Info(pobj, pData, &status);

			if (status == status_stop)
			{
				if ((pobj->m_LoopMode == loop_section_loop)
				||  (pobj->m_LoopMode == loop_volume_loop))
				{
					throw_os(AMPlayer_Load(pData->handle, pobj->m_pFile));
					throw_os(AMPlayer_Play(pData->handle));
				}
				else
				{
					AmpDriver_Stop(pobj);
				}
			}
			else if ((status == status_play)
			||  (status == status_busy))
				pobj->m_Status = status;
		}
		catch
		{
			const exception* e = exception_current();

			pobj->m_Err = e->error;
			pobj->m_Status = status_error;
		}
		catch_end

		return status;
	}

	// stop or pause ?
	return pobj->m_Status;
}

static void AmpDriver_GetPosition(MediaObject* pobj, MediaPosition* pos)
{
	AmpDriver_ObjectData* pData = pobj->m_pData;

	pos->driver.start = 0;
	pos->driver.pos = (uint64_t) pData->curtime;
	pos->driver.end = (uint64_t) pData->songtime;
	pos->volume.sections = 1;
	pos->volume.pos = pData->curtime;
	pos->volume.len = pData->songtime;
	pos->section.pos = pos->volume.pos;
	pos->section.len = pos->volume.len;
	pos->section.nr = 0;
}

static void AmpDriver_SetConfig(MediaObject* pobj, Media_Config type, const void* value)
{
	AmpDriver_ObjectData* pData = pobj->m_pData;
	unsigned int dummy;

	switch(type)
	{
		case Media_CfgVolume:
		{
			MediaVolume* pvol = (MediaVolume*) value;
			unsigned int val = pvol->sysscaled;

			if (val > 0xffff) val = 0xffff;
			AMPlayer_Vol(pData->handle, val >> 9, &dummy);
		}
		break;
		case Media_CfgFrequency:
		{
			pobj->m_iSpeed = *(unsigned int*) value;
			// SharedSound_SetSampleRate
			_swix(0x4b446, _IN(0)|_IN(1), 0, pobj->m_iSpeed << 10);
			_swix(0x4b446, _IN(0)|_IN(1), 0, 0);
		}
		break;
		case Media_CfgDMASize:
		{
			unsigned int val = *(unsigned int*) value;
			// Sound_Configure
			if (val) _swix(0x40140, _INR(0,4), 0, val, 0, 0, 0);
		}
		break;
		default: ;
	}
}

static unsigned int AmpDriver_GetConfig(MediaObject* pobj, Media_Config type)
{
	AmpDriver_ObjectData* pData = pobj->m_pData;

	switch(type)
	{
		case Media_CfgVolume:
		{
			unsigned int volume;

			if (AMPlayer_Vol(pData->handle, -1, &volume)) return 0xffff;

			return volume << 9;
		}
		break;
		default: ;
	}

	return 0xffffffff;
}

//------------------------------------------------------------------------------

const MediaDriver AmpDriver =
{
	  AmpDriver_Name
	, throw_AmpDriver_LoadObject
	, AmpDriver_UnLoadObject
	, NULL
	, AmpDriver_Play
	, AmpDriver_Freeze
	, AmpDriver_Stop
	, AmpDriver_Seek
	, AmpDriver_SetLoopMode
	, AmpDriver_GetLoopMode
	, AmpDriver_GetStatus
	, AmpDriver_GetPosition
	, NULL
	, AmpDriver_SetConfig
	, AmpDriver_GetConfig
};
