#include "swis.h"

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

extern const MediaDriver QTMDriver;

static const char qtmmodname[] = "QTMTracker";

typedef struct QTMDriver_ObjectData
{
	unsigned int	lastpos;
} QTMDriver_ObjectData;

#define QTM_Load			0x047e40
#define QTM_Start			0x047e41
#define QTM_Stop			0x047e42
#define QTM_Pause			0x047e43
#define QTM_Clear			0x047e44
#define QTM_Info			0x047e45
#define QTM_Pos				0x047e46
#define QTM_Volume			0x047e48
#define QTM_SetSampleSpeed	0x047e49
#define QTM_SongLength		0x047e4e
#define QTM_SongStatus		0x047e55

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

static const _kernel_oserror* QTM_GetCurrentPos(unsigned int* ppos)
{
	const _kernel_oserror* e;
	unsigned int pos1 = 0;
	unsigned int pos2 = 0;

	e = _swix(QTM_Pos, _IN(0)|_IN(1)|_OUT(0)|_OUT(1), -1, -1, &pos1, &pos2);
	if (e) return e;

	*ppos = (pos1 << 6) + pos2;
	return NULL;
}

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

static const char QTMDriver_Name[] = "Queue The Music";

static bool inUse = false;

static bool throw_QTMDriver_LoadObject(MediaObject* pobj, const char* pObjectName)
{
	QTMDriver_ObjectData* volatile	pData = NULL;
	static char			string[1024];
	const char*			ptitle;
	unsigned int voices;

	// Module allows only a single soundtrack file at any time
	if (inUse) throw_os(&Media_ErrorModuleInUse);

	pData = throw_mem_alloc(sizeof(QTMDriver_ObjectData));

	Media_Attach(pobj, pData, Media_HType_File);

	// Ensure module is loaded
	throw_os(RMEnsure(qtmmodname, 140, "System:Modules.Audio.Trackers.QTMModule"));

	// stop playing and load a new file
	_swix(QTM_Stop, 0);
	throw_os(_swix(QTM_Load, _IN(0)|_IN(1), pObjectName, 0));

	// extract title info
	_swix(QTM_Info, _OUT(0), &ptitle);
	if (ptitle && ptitle[0])
		throw_MediaObject_SetText(pobj, EMetaId_StreamTitle, EMetaOrigin_Stream, string);

	_swix(QTM_Info, _OUT(3), &voices);

	throw_MediaObject_SetText(pobj, EMetaId_StreamType, EMetaOrigin_Stream
		, SPrintf("%s - %d Ch", QTMDriver_Name, voices));
	throw_MediaObject_SetText(pobj, EMetaId_ModuleName, EMetaOrigin_Stream, qtmmodname);
	throw_MediaObject_SetMeta(pobj, EMetaId_ModuleHandle, EMetaOrigin_Stream, (void*) 0, 0);

	inUse = true;

	return true;
}

static void QTMDriver_UnLoadObject(MediaObject* pobj)
{
	QTMDriver_ObjectData* pData = pobj->m_pData;

	inUse = false;

	if (pData)
	{
		mem_free(pData);
		pobj->m_pData = NULL;

		_swix(QTM_Stop, 0);
		_swix(QTM_Clear, 0);
		if (Options()->Music_Files.bUnloadModules)
			RMKill(qtmmodname);
	}
}

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

	// if pos = 0, song is either started or restarted
	if (pos == 0)
	{
		_swix(QTM_Stop, 0);
		_swix(QTM_SetSampleSpeed, _IN(0), pobj->m_iSpeed);
	}
	_swix(QTM_Start, 0);
	QTMDriver_SetConfig(pobj, Media_CfgDMASize, &Options()->Music_Files.DMASize);
	_swix(QTM_Pos, _IN(0)|_IN(1), pos, 0);
	QTM_GetCurrentPos(&pData->lastpos);
}

static void QTMDriver_Freeze(MediaObject* pobj, bool on)
{
	if (on)
	{
		_swix(QTM_Pause, 0);
		pobj->m_Status = status_pause;
	}
	else
	{
		_swix(QTM_Start, 0);
		_swix(QTM_SetSampleSpeed, _IN(0), pobj->m_iSpeed);
		QTMDriver_SetConfig(pobj, Media_CfgDMASize, &Options()->Music_Files.DMASize);
	}
}

static void QTMDriver_Stop(MediaObject* pobj)
{
	_swix(QTM_Stop, 0);
	pobj->m_Status = status_stop;
}

static void QTMDriver_Seek(MediaObject* pobj, EMedia_Seek set)
{
	switch(set)
	{
		case EMedia_Seek_Start:
		case EMedia_Seek_SectionStart:
		{
			QTMDriver_Play(pobj, 0);
		}
		break;
		case EMedia_Seek_PreviousPos:
		{
			unsigned int pos;

			QTM_GetCurrentPos(&pos);

			if (pos < 64)
				pos = 0;
			else
				pos -= 64;

			QTMDriver_Play(pobj, (uint64_t) pos);
		}
		break;
		case EMedia_Seek_NextPos:
		{
			unsigned int pos;
			unsigned int length;

			QTM_GetCurrentPos(&pos);
			pos += 64;
			_swix(QTM_SongLength, _OUT(0), &length);
			if (pos < (length << 6))
				QTMDriver_Play(pobj, (uint64_t) pos);
		}
		break;
		default: ;
	}
}

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

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

static Media_Status QTMDriver_GetStatus(MediaObject* pobj)
{
	QTMDriver_ObjectData* pData = pobj->m_pData;
	const _kernel_oserror* e;
	unsigned int status;
	unsigned int pos;

	e = _swix(QTM_SongStatus, _OUT(0), &status);
	if (e) goto error;

	e = QTM_GetCurrentPos(&pos);
	if (e) goto error;

	if ((status & 4) == 4)
	{
		if ((pobj->m_LoopMode != loop_section_loop)
		&&  (pobj->m_LoopMode != loop_volume_loop)
		&&  ((pData->lastpos >> 6) > (pos >> 6)))
		{
			QTMDriver_Stop(pobj);
			return status_stop;
		}
		pData->lastpos = pos;
		return status_play;
	}

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

error:
	pobj->m_Err = *e;
	pobj->m_Status = status_error;

	return pobj->m_Status;
}

static void QTMDriver_GetPosition(MediaObject* pobj, MediaPosition* pos)
{
	unsigned int pos1 = 0;
	unsigned int pos2 = 0;
	unsigned int len;

	// Stop warnings
	IGNORE(pobj);

	if (_swix(QTM_Pos, _IN(0)|_IN(1)|_OUT(0)|_OUT(1), -1, -1, &pos1, &pos2))
		pos1 = 0;
	else
		pos1 = (pos1 << 6) + pos2;
	_swix(QTM_SongLength, _OUT(0), &len);
	len <<= 6;

	pos->driver.start = 0;
	pos->driver.pos = (uint64_t) pos1;
	pos->driver.end = (uint64_t) len;
	pos->volume.sections = 1;
	pos->volume.pos = pos1 * 10;
	pos->volume.len = len * 10;
	pos->section.pos = pos->volume.pos;
	pos->section.len = pos->volume.len;
	pos->section.nr = 0;
}

static void QTMDriver_SetConfig(MediaObject* pobj, Media_Config type, const void* value)
{
	switch(type)
	{
		case Media_CfgVolume:
		{
			MediaVolume* pvol = (MediaVolume*) value;
			unsigned int val = pvol->sysscaled;

			if (val > 0x10000) val = 0x10000;
			_swix(QTM_Volume, _IN(0), val >> 10);
		}
		break;
		case Media_CfgFrequency:
		{
			_swix(QTM_SetSampleSpeed, _IN(0), Quality_FromFrequency(*(unsigned int*) value));
			_swix(QTM_SetSampleSpeed, _IN(0)|_OUT(0), -1, &pobj->m_iSpeed);
		}
		break;
		case Media_CfgDMASize:
		{
			// Sound_Configure
			_swix(0x40140, _INR(0,4), 0, 208, 0, 0, 0);
		}
		break;
		default: ;
	}
}

static unsigned int QTMDriver_GetConfig(MediaObject* pobj, Media_Config type)
{
	// Stop warnings
	IGNORE(pobj);

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

			if (_swix(QTM_Volume, _IN(0)|_OUT(0), -1, &volume)) return 0xffff;

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

	return 0xffffffff;
}

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

const MediaDriver QTMDriver =
{
	  QTMDriver_Name
	, throw_QTMDriver_LoadObject
	, QTMDriver_UnLoadObject
	, NULL
	, QTMDriver_Play
	, QTMDriver_Freeze
	, QTMDriver_Stop
	, QTMDriver_Seek
	, QTMDriver_SetLoopMode
	, QTMDriver_GetLoopMode
	, QTMDriver_GetStatus
	, QTMDriver_GetPosition
	, NULL
	, QTMDriver_SetConfig
	, QTMDriver_GetConfig
};
