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

typedef struct MidiDriver_ObjectData
{
	unsigned int	songtime;
} MidiDriver_ObjectData;

#define MidiPlay_File	0x045340
#define MidiPlay_Start	0x045341
#define MidiPlay_Stop	0x045342
#define MidiPlay_Volume	0x045343
#define MidiPlay_Info	0x045345

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

static const char MidiDriver_Name[] = "MidiPlay";

static MediaObject* inUse = NULL;

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

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

	pData = throw_mem_alloc(sizeof(MidiDriver_ObjectData));

	Media_Attach(pobj, pData, Media_HType_Midi);

	// Ensure Midi modules are loaded
	throw_os(RMEnsure(MidiDriver_Name, 15, "<DigitalCD$Dir>.RMStore.MPlay"));

	// stop playing and load a new file
	throw_os(_swix(MidiPlay_File, _IN(0)|_IN(1), 0, pObjectName));
	inUse = pobj;

	// extract title info
	throw_os(_swix(MidiPlay_Info, _OUT(0), &pData->songtime));

	throw_MediaObject_SetText(pobj, EMetaId_StreamType, EMetaOrigin_Stream, "Midi");
	throw_MediaObject_SetText(pobj, EMetaId_ModuleName, EMetaOrigin_Stream, MidiDriver_Name);
	throw_MediaObject_SetMeta(pobj, EMetaId_ModuleHandle, EMetaOrigin_Stream, 0, 0);

	_swix(MidiPlay_Start, _IN(0)|_IN(1), 0, 0);

	return true;
}

static void MidiDriver_UnLoadObject(MediaObject* pobj)
{
	MidiDriver_ObjectData* pData = pobj->m_pData;

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

	if (inUse == pobj)
	{
		inUse = NULL;
		_swix(MidiPlay_Stop, 0);

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

static void MidiDriver_Play(MediaObject* pobj, uint64_t currentpos)
{
	unsigned int pos = (unsigned int) currentpos;

	// if pos = 0, song is either started or restarted
	if (pos == 0)
	{
		_swix(MidiPlay_Stop, 0);
	}
	pobj->m_Status = status_play;
	_swix(MidiPlay_Start, _IN(0)|_IN(1), 0, pos);
	_swix(MidiPlay_Start, _IN(0), 1);
}

static void MidiDriver_Freeze(MediaObject* pobj, bool on)
{
	if (on)
	{
		_swix(MidiPlay_Stop, 0);
		pobj->m_Status = status_pause;
	}
	else
	{
		_swix(MidiPlay_Start, _IN(0), 1);
		pobj->m_Status = status_play;
	}
}

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

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

			_swix(MidiPlay_Info, _OUT(1), &pos);
			if (pos < 100)
				pos = 0;
			else
				pos -= 100;
			MidiDriver_Play(pobj, (uint64_t) pos);
		}
		break;
		case EMedia_Seek_NextPos:
		{
			unsigned int pos;
			unsigned int length;

			_swix(MidiPlay_Info, _OUT(0)|_OUT(1), &length, &pos);
			pos += 100;
			if (pos < length)
				MidiDriver_Play(pobj, (uint64_t) pos);
		}
		break;
		default: ;
	}
}

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

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

static Media_Status MidiDriver_GetStatus(MediaObject* pobj)
{
	unsigned int length = 0;
	unsigned int pos = 0;
	unsigned int play = 0;
	const _kernel_oserror* e;

	if (pobj->m_Status == status_play)
	{
		e = _swix(MidiPlay_Info, _OUT(0)|_OUT(1)|_OUT(2), &length, &pos, &play);
		if (e) goto error;

		if ((pobj->m_LoopMode == loop_section_loop)
		||  (pobj->m_LoopMode == loop_volume_loop))
		{
			if (!play)
			{
				e = _swix(MidiPlay_Start, _IN(0)|_IN(1), 0, 0);
				if (e) goto error;
			}
		}
		else if (!play)
		{
			MidiDriver_Stop(pobj);
			return status_stop;
		}
		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 MidiDriver_GetPosition(MediaObject* pobj, MediaPosition* pos)
{
	unsigned int pos1 = 0;
	unsigned int len;

	// Stop warnings
	IGNORE(pobj);

	if (_swix(MidiPlay_Info, _OUT(1), &pos1))
		pos1 = 0;
	_swix(MidiPlay_Info, _OUT(0), &len);

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

static void MidiDriver_SetConfig(MediaObject* pobj, Media_Config type, const void* value)
{
	IGNORE(pobj);

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

			if (val > 0xffff) val = 0xffff;
			_swix(MidiPlay_Volume, _IN(0), val >> 8);
		}
		break;
		default: ;
	}
}

static unsigned int MidiDriver_GetConfig(MediaObject* pobj, Media_Config type)
{
	IGNORE(pobj);

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

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

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

	return 0xffffffff;
}

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

const MediaDriver MidiDriver =
{
	  MidiDriver_Name
	, throw_MidiDriver_LoadObject
	, MidiDriver_UnLoadObject
	, NULL
	, MidiDriver_Play
	, MidiDriver_Freeze
	, MidiDriver_Stop
	, MidiDriver_Seek
	, MidiDriver_SetLoopMode
	, MidiDriver_GetLoopMode
	, MidiDriver_GetStatus
	, MidiDriver_GetPosition
	, NULL
	, MidiDriver_SetConfig
	, MidiDriver_GetConfig
};
