#include <string.h>

#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 XprDriver;

typedef struct XprDriver_ObjectData
{
	unsigned int	lastpos;
} XprDriver_ObjectData;

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

#define ExpressionMod_Load		0x8A800
#define ExpressionMod_Play		0x8A801
#define ExpressionMod_Pause		0x8A802
#define ExpressionMod_Stop		0x8A803
#define ExpressionMod_Position	0x8A804
#define ExpressionMod_Info		0x8A805
#define ExpressionMod_Volume	0x8A806

static const _kernel_oserror* Xpr_GetPos(unsigned int* ppos)
{
	const _kernel_oserror* e;

	*ppos = 0;

	e = _swix(ExpressionMod_Position, _IN(0)|_OUT(0), 0, ppos);
	if (e) return e;

	*ppos -= -1;

	return NULL;
}

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

static const char XprDriver_Name[] = "XprMod";

static MediaObject* inUse = NULL;

static bool throw_XprDriver_LoadObject(MediaObject* pobj, const char* pObjectName)
{
	XprDriver_ObjectData* volatile	pData = NULL;
	char*				string;
	int channels;

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

	pData = throw_mem_alloc(sizeof(XprDriver_ObjectData));

	Media_Attach(pobj, pData, Media_HType_File);

	// Ensure Xpr modules are loaded
	throw_os(RMEnsure(XprDriver_Name, 110, "System:Modules.Audio.Trackers.XprMod"));

	// stop playing and load a new file
	_swix(ExpressionMod_Stop, 0);
	throw_os(_swix(ExpressionMod_Load, _IN(0)|_IN(1)|_IN(2)|_IN(3)|_IN(4)
		, pObjectName, 0, -1, 0, 0));
	inUse = pobj;

	throw_os(_swix(ExpressionMod_Info, _OUT(0), &string));

	// strip leading and trailing blanks
	if (strlen(string) > 0)
		throw_MediaObject_SetText(pobj, EMetaId_StreamTitle, EMetaOrigin_Stream, string);

	if (_swix(ExpressionMod_Info, _OUT(1)|_OUT(2), &string, &channels))
		throw_MediaObject_SetText(pobj, EMetaId_StreamType, EMetaOrigin_Stream, XprDriver_Name);
	else
		throw_MediaObject_SetText(pobj, EMetaId_StreamType, EMetaOrigin_Stream, SPrintf("%s - %d Ch", string, channels));
	throw_MediaObject_SetText(pobj, EMetaId_ModuleName, EMetaOrigin_Stream, XprDriver_Name);
	throw_MediaObject_SetMeta(pobj, EMetaId_ModuleHandle, EMetaOrigin_Stream, (void*) 0, 0);

	return true;
}

static void XprDriver_UnLoadObject(MediaObject* pobj)
{
	XprDriver_ObjectData* pData = pobj->m_pData;

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

	if (inUse == pobj)
	{
		inUse = NULL;
		_swix(ExpressionMod_Stop, 0);
		if (Options()->Music_Files.bUnloadModules)
			RMKill("XprMod");
	}
}

static void XprDriver_Play(MediaObject* pobj, uint64_t currentpos)
{
	XprDriver_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(ExpressionMod_Pause, 0);
		// bugs
		_swix(ExpressionMod_Play, _IN(0)|_IN(1), 1, 0);
		_swix(ExpressionMod_Position, _IN(0), 1);
	}
	pobj->m_Status = status_play;
	_swix(ExpressionMod_Play, _IN(0)|_IN(1), 1, 0);
	_swix(ExpressionMod_Position, _IN(0), pos + 1);
	Xpr_GetPos(&pData->lastpos);
}

static void XprDriver_Freeze(MediaObject* pobj, bool on)
{
	if (on)
	{
		_swix(ExpressionMod_Pause, 0);
		pobj->m_Status = status_pause;
	}
	else
	{
		_swix(ExpressionMod_Play, _IN(0)|_IN(1), 1, 0);
		pobj->m_Status = status_play;
	}
}

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

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

			Xpr_GetPos(&pos);

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

			pos <<= 6;
			XprDriver_Play(pobj, (uint64_t) pos);
		}
		break;
		case EMedia_Seek_NextPos:
		{
			unsigned int pos;
			unsigned int length;

			Xpr_GetPos(&pos);
			pos += 1;
			_swix(ExpressionMod_Info, _OUT(3), &length);
			if (pos < length)
			{
				pos <<= 6;
				XprDriver_Play(pobj, (uint64_t) pos);
			}
		}
		break;
		default: ;
	}
}

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

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

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

	if (pobj->m_Status == status_play)
	{
		e = Xpr_GetPos(&pos);
		if (e) goto error;

		if ((pobj->m_LoopMode != loop_section_loop)
		&&  (pobj->m_LoopMode != loop_volume_loop)
		&&  (pData->lastpos > pos))
		{
			XprDriver_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 XprDriver_GetPosition(MediaObject* pobj, MediaPosition* pos)
{
	unsigned int pos1 = 0;
	unsigned int pos2 = 0;
	unsigned int len;

	// Stop warnings
	IGNORE(pobj);

	if (_swix(ExpressionMod_Position, _IN(0)|_OUT(0)|_OUT(1), 0, &pos1, &pos2))
		pos1 = 1;
	pos1 = (pos1-1) << 6;
	_swix(ExpressionMod_Info, _OUT(3), &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 XprDriver_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(ExpressionMod_Volume, _IN(0), val >> 9);
		}
		break;
		case Media_CfgFrequency:
		{
			// change not supported during playback
			pobj->m_iSpeed = *(unsigned int*) value;
		}
		break;
		case Media_CfgDMASize:
		{
			// Sound_Configure
//			_swix(0x40140, _INR(0,4), 0, 208, 0, 0, 0);
		}
		break;
		default: ;
	}
}

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

	switch(type)
	{
		case Media_CfgVolume:
		{
			// info cannot be read
			return 0xffff;
		}
		break;
		default: ;
	}

	return 0xffffffff;
}

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

const MediaDriver XprDriver =
{
	  XprDriver_Name
	, throw_XprDriver_LoadObject
	, XprDriver_UnLoadObject
	, NULL
	, XprDriver_Play
	, XprDriver_Freeze
	, XprDriver_Stop
	, XprDriver_Seek
	, XprDriver_SetLoopMode
	, XprDriver_GetLoopMode
	, XprDriver_GetStatus
	, XprDriver_GetPosition
	, NULL
	, XprDriver_SetConfig
	, XprDriver_GetConfig
};
