#include <math.h>
#include <string.h>
#include "Log.h"

#include "swis.h"

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

#define M_PI 3.14159265358979323846

void Equalizer_Configure(int swi, EqualizerParams* pParams);

extern const MediaDriver TimDriver;

typedef struct TimDriver_ObjectData
{
	unsigned int	handle;
	unsigned int	length;
	unsigned int	songtime;
	unsigned int	section;
	unsigned int	version;
} TimDriver_ObjectData;

#define TimPlayer_Version 			0x051380
#define TimPlayer_Configure			0x051381
#define TimPlayer_SongLoad			0x051382
#define TimPlayer_SongUnload		0x051383
#define TimPlayer_SongLoad2			0x051385
#define TimPlayer_SongPlay			0x051388
#define TimPlayer_SongPause			0x051389
#define TimPlayer_SongStop			0x05138A
#define TimPlayer_SongPosition		0x05138B
#define TimPlayer_SongVolume		0x05138C
#define TimPlayer_SongStatus		0x05138D
#define TimPlayer_SongConfigure		0x05138E
#define TimPlayer_SongInfo			0x051390
#define TimPlayer_SongTexts			0x051391
#define TimPlayer_SongPlayInfo		0x051394
#define TimPlayer_SongSectionInfo	0x051395

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

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

static const char TimDriver_Name[] = "TimPlayer";

static bool throw_TimDriver_LoadObject(MediaObject* pobj, const char* pObjectName)
{
	TimDriver_ObjectData* volatile	pData = NULL;
	const char   *title,*artist,*type;
	unsigned int channels = 0;
	unsigned int compatibility = 0;
	unsigned int config = 0;

	pData = throw_mem_alloc(sizeof(TimDriver_ObjectData));
	pData->handle = -1;

	Media_Attach(pobj, pData, Media_HType_File);

	// Ensure Tim modules are loaded
	throw_os(RMEnsure(TimDriver_Name, 101, "System:Modules.Audio.Trackers.TimPlayer"));
	_swix(TimPlayer_Version, _OUT(0), &pData->version);

	if (pData->version < 123)
	{
		_swix(TimPlayer_Configure, _IN(0)|_IN(1), 257, (pobj->m_Params & Flag_TimIgnoreLastPatternLoops) != 0);
		_swix(TimPlayer_Configure, _IN(0)|_IN(1), 258, (pobj->m_Params & Flag_TimIgnoreSequenceEndMarkers) != 0);
		_swix(TimPlayer_Configure, _IN(0)|_IN(1), 259, (pobj->m_Params & Flag_TimIgnoreRestartPos) != 0);
		_swix(TimPlayer_Configure, _IN(0)|_IN(1), 260, (pobj->m_Params & Flag_TimDoNotPlayMutedChannels) == 0);
	}
	else
	{
		compatibility |= 0x08;
		if (pobj->m_Params & Flag_TimIgnoreSequenceEndMarkers)
			config |= 0x02;
		if (pobj->m_Params & Flag_TimIgnoreLastPatternLoops)
			config |= 0x04;
		if (pobj->m_Params & Flag_TimIgnoreRestartPos)
			config |= 0x08;
		if (pobj->m_Params & Flag_TimDoNotPlayMutedChannels)
			config |= 0x10;
	}
	_swix(TimPlayer_Configure, _IN(0)|_IN(1), 8, Options()->Drivers.TimPlayer.bFreeVolume);
	_swix(TimPlayer_Configure, _IN(0)|_IN(1), 10, Options()->Drivers.TimPlayer.bEnableInterrupts);

	if (pData->version >= 115)
	{
		if (pobj->m_Params & Flag_TimVBlankMode)
			compatibility |= 0x01;
		if (pobj->m_Params & Flag_TimNoInstSwap)
			compatibility |= 0x02;
		if (pobj->m_Params & Flag_TimUltimateSTK)
			compatibility |= 0x04;
	}

	// load a new file
	if (compatibility > 0)
		throw_os(_swix(TimPlayer_SongLoad2, _INR(0,1)|_IN(4)|_OUT(0), compatibility, pObjectName, config, &pData->handle));
	else
		throw_os(_swix(TimPlayer_SongLoad, _IN(1)|_OUT(0), pObjectName, &pData->handle));

	// extract info
	throw_os(_swix(TimPlayer_SongPlayInfo, _IN(0)|_OUT(4), pData->handle, &pData->songtime));
	throw_os(_swix(TimPlayer_SongInfo, _IN(0)|_OUT(1)|_OUT(2), pData->handle, &channels, &pData->length));

	throw_os(_swix(TimPlayer_SongTexts, _IN(0)|_OUTR(1,3), pData->handle, &title, &artist, &type));

	if (strlen(title) > 0)
		throw_MediaObject_SetText(pobj, EMetaId_StreamTitle, EMetaOrigin_Stream, title);

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

	throw_MediaObject_SetText(pobj, EMetaId_StreamType, EMetaOrigin_Stream
		, SPrintf("%s - %d Ch", type, channels));
	throw_MediaObject_SetText(pobj, EMetaId_ModuleName, EMetaOrigin_Stream, TimDriver_Name);
	throw_MediaObject_SetMeta(pobj, EMetaId_ModuleHandle, EMetaOrigin_Stream, (void*) pData->handle, 0);

	throw_os(_swix(TimPlayer_SongPosition, _IN(0)|_IN(1), pData->handle, 0));

	return true;
}

static void TimDriver_UnLoadObject(MediaObject* pobj)
{
	TimDriver_ObjectData* pData = pobj->m_pData;

	if (pData)
	{
		_swix(TimPlayer_SongUnload, _IN(0), pData->handle);

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

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

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

	// if pos = 0, song is either started or restarted
	if (pos == 0)
	{
		_swix(TimPlayer_SongPause, _IN(0), pData->handle);
		TimDriver_SetConfig(pobj, Media_CfgFrequency, &pobj->m_iSpeed);
		TimDriver_SetConfig(pobj, Media_CfgDMASize, &Options()->Music_Files.DMASize);
	}
	pobj->m_Status = status_play;
	_swix(TimPlayer_SongPosition, _IN(0)|_IN(1), pData->handle, pos);
	_swix(TimPlayer_SongPlay, _IN(0), pData->handle);
	_swix(TimPlayer_SongSectionInfo, _IN(0)|_IN(1)|_OUT(1), pData->handle, -2, &pData->section);
}

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

	if (on)
	{
		_swix(TimPlayer_SongPause, _IN(0), pData->handle);
		pobj->m_Status = status_pause;
	}
	else
	{
		_swix(TimPlayer_SongPlay, _IN(0), pData->handle);
		pobj->m_Status = status_play;
		TimDriver_SetConfig(pobj, Media_CfgFrequency, &pobj->m_iSpeed);
		TimDriver_SetConfig(pobj, Media_CfgDMASize, &Options()->Music_Files.DMASize);
	}
}

static void TimDriver_Stop(MediaObject* pobj)
{
	TimDriver_ObjectData* pData = pobj->m_pData;

	_swix(TimPlayer_SongStop, _IN(0), pData->handle);
	pobj->m_Status = status_stop;
}

static void TimDriver_Seek(MediaObject* pobj, EMedia_Seek set)
{
	TimDriver_ObjectData*	pData = pobj->m_pData;
	int			pos;

	switch(set)
	{
		case EMedia_Seek_Start:
		{
			pos = 0;
		}
		break;
		case EMedia_Seek_SectionStart:
		{
			if (_swix(TimPlayer_SongSectionInfo, _IN(0)|_IN(1)|_OUT(2), pData->handle, -2, &pos))
				return;
		}
		break;
		case EMedia_Seek_PreviousSection:
		{
			if (_swix(TimPlayer_SongSectionInfo, _IN(0)|_IN(1)|_OUT(1), pData->handle, -2, &pos)
			||  (pos <= 0))
				return;

			_swix(TimPlayer_SongSectionInfo, _IN(0)|_IN(1)|_OUT(2), pData->handle, pos - 1, &pos);
		}
		break;
		case EMedia_Seek_NextSection:
		{
			if (_swix(TimPlayer_SongSectionInfo, _IN(0)|_IN(1)|_OUT(1), pData->handle, -2, &pos)
			||  _swix(TimPlayer_SongSectionInfo, _IN(0)|_IN(1)|_OUT(2), pData->handle, pos + 1, &pos))
				return;
		}
		break;
		case EMedia_Seek_PreviousPos:
		{
			_swix(TimPlayer_SongPosition, _IN(0)|_IN(1)|_OUT(1), pData->handle, -1, &pos);
			pos -= 1;
		}
		break;
		case EMedia_Seek_NextPos:
		{
			_swix(TimPlayer_SongPosition, _IN(0)|_IN(1)|_OUT(1), pData->handle, -1, &pos);
			pos += 1;
		}
		break;
		default:
			return;
	}

	if ((pos >= 0) && (pos < (int) pData->length))
	{
		pos <<= 8;
		TimDriver_Play(pobj, (uint64_t) pos);
	}
}

static void TimDriver_SetLoopMode(MediaObject* pobj, Media_LoopMode mode)
{
	TimDriver_ObjectData* pData = pobj->m_pData;

	pobj->m_LoopMode = mode;

	// TimPlayer_SongStatus
	// Loop or pause at song end
	_swix(TimPlayer_SongStatus, _IN(0)|_IN(1)|_IN(2), pData->handle
		, (pobj->m_LoopMode != loop_section_loop)
		? 0x10 : 0, 0x10);
}

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

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

	e = _swix(TimPlayer_SongStatus, _IN(0)|_IN(2)|_OUT(1), pData->handle, 0, &status);
	if (e) goto error;

	if (status & 1)
		pobj->m_Status = status_play;
	else if (status & 2)
	{
		// forced pause? We may have asked it, but it could also come from stepping (debugging)
		if (pobj->m_Status != status_pause)
		{
			if (status & 0xE0)
				pobj->m_Status = status_pause;
			else
			{
				if ((pobj->m_LoopMode == loop_volume_loop)
				||  (pobj->m_LoopMode == loop_volume_play))
				{
					int pos;
					// goto next section
					e = _swix(TimPlayer_SongSectionInfo, _IN(0)|_IN(1)|_OUT(2), pData->handle, pData->section + 1, &pos);
					if (e && (pobj->m_LoopMode == loop_volume_loop))
						// goto first section
						e = _swix(TimPlayer_SongSectionInfo, _IN(0)|_IN(1)|_OUT(2), pData->handle, 0, &pos);

					if (e)
					{
						pobj->m_Status = status_stop;
						e = NULL;
					}
					else
					{
						pos <<= 8;
						TimDriver_Play(pobj, (uint64_t) pos);
						pobj->m_Status = status_play;
					}
				}
				else pobj->m_Status = status_stop;
			}
		}
	}
	else
		pobj->m_Status = status_stop;

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

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

	return pobj->m_Status;
}

static void TimDriver_GetPosition(MediaObject* pobj, MediaPosition* pos)
{
	TimDriver_ObjectData* pData = pobj->m_pData;
	unsigned int pos1 = 0;
	unsigned int pos2 = 0;
	unsigned int len = 0;

	if (_swix(TimPlayer_SongPosition
				, _INR(0,1)|_OUT(1)|_OUT(2)|_OUT(4)|_OUT(5)
				, pData->handle, -1
				, &pos1, &pos2, &len, &pos->volume.pos))
	{
		pos1 = 0;
		pos->volume.pos = 0;
	}
	else
	{
		pos1 = (pos1 << 8) + ((256*pos2)/len);
	}
	len = pData->length << 8;

	pos->driver.start = 0;
	pos->driver.pos = (uint64_t) pos1;
	pos->driver.end = (uint64_t) len;
	pos->volume.sections = 0;
	_swix(TimPlayer_SongSectionInfo
		, _INR(0,1)|_OUT(1)
		, pData->handle, -1
		, &pos->volume.sections);
	pos->volume.sections += 1;
	pos->volume.len = pData->songtime / 10;

	_swix(TimPlayer_SongSectionInfo
		, _INR(0,1)|_OUTR(2,4)
		, pData->handle, -2
		, &pos->section.nr, &pos2, &pos->section.len);
	pos->section.pos = pos->volume.pos - pos2;
	pos->section.len -= pos2;
	pos->section.pos /= 10;
	pos->section.len /= 10;
	pos->volume.pos /= 10;
}

static uint64_t TimDriver_GetSectionPos(MediaObject* pobj, unsigned int section)
{
	TimDriver_ObjectData* pData = pobj->m_pData;
	unsigned int sections = 0, pos = 0;

	_swix(TimPlayer_SongSectionInfo, _IN(0)|_IN(1)|_OUT(1), pData->handle, -1, &sections);

	if (sections > 0)
	{
		if (_swix(TimPlayer_SongSectionInfo, _IN(0)|_IN(1)|_OUT(2), pData->handle, section, &pos))
			pos = 0;
		else
			pos = pos << 8;
	}

	return (uint64_t) pos;
}

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

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

			if (val >= 0x40000) val = 0x40000;
			_swix(TimPlayer_SongVolume, _IN(0)|_IN(1), pData->handle, val >> 8);
		}
		break;
		case Media_CfgFrequency:
		{
			pobj->m_iSpeed = *(unsigned int*) value;
			// Is 16-bit disabled ?
			_swix(TimPlayer_Configure, _IN(0)|_IN(1)|_OUT(1), 4, -1, &dummy);
			if (dummy)
				_swix(TimPlayer_Configure, _IN(0)|_IN(1), 0, Quality_FromFrequency(pobj->m_iSpeed));
			else
				_swix(TimPlayer_Configure, _IN(0)|_IN(1), 5, pobj->m_iSpeed);
		}
		break;
		case Media_CfgInterpol:
		{
			bool val = *(bool*) value;
			_swix(TimPlayer_Configure, _IN(0)|_IN(1), 1, (val != 0));
		}
		break;
		case Media_CfgBalance:
		{
			unsigned int val = *(unsigned int*) value;
			_swix(TimPlayer_Configure, _IN(0)|_IN(1), 2, val);
		}
		break;
		case Media_CfgStereoSeparation:
		{
			unsigned int val = *(unsigned int*) value;
			_swix(TimPlayer_Configure, _IN(0)|_IN(1), 3, val);
		}
		break;
		case Media_CfgVolumeRamping:
		{
			bool val = *(bool*) value;
			_swix(TimPlayer_Configure, _IN(0)|_IN(1), 256, (val != 0));
		}
		break;
		case Media_CfgEqualizer:
		{
			COptions* popt = (COptions*) value;
			if (popt->Equalizer.bActive)
				Equalizer_Configure(0x071381, &popt->Equalizer.Params);
			else
				Equalizer_Configure(0x071381, NULL);
		}
		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;
		case Media_CfgParams:
		{
			pobj->m_Params = *(unsigned int*) value;

			if (pData->version < 123)
			{
				_swix(TimPlayer_Configure, _INR(0,1), 257, (pobj->m_Params & Flag_TimIgnoreLastPatternLoops) != 0);
				_swix(TimPlayer_Configure, _INR(0,1), 258, (pobj->m_Params & Flag_TimIgnoreSequenceEndMarkers) != 0);
				_swix(TimPlayer_Configure, _INR(0,1), 259, (pobj->m_Params & Flag_TimIgnoreRestartPos) != 0);
				_swix(TimPlayer_Configure, _INR(0,1), 260, (pobj->m_Params & Flag_TimDoNotPlayMutedChannels) == 0);
			}
			else
			{
				_swix(TimPlayer_SongConfigure, _INR(0,2), pData->handle, 1, (pobj->m_Params & Flag_TimIgnoreLastPatternLoops) != 0);
				_swix(TimPlayer_SongConfigure, _INR(0,2), pData->handle, 2, (pobj->m_Params & Flag_TimIgnoreSequenceEndMarkers) != 0);
				_swix(TimPlayer_SongConfigure, _INR(0,2), pData->handle, 3, (pobj->m_Params & Flag_TimIgnoreRestartPos) != 0);
				_swix(TimPlayer_SongConfigure, _INR(0,2), pData->handle, 4, (pobj->m_Params & Flag_TimDoNotPlayMutedChannels) != 0);
			}
		}
		break;
		default: ;
	}
}

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

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

			if (_swix(TimPlayer_SongVolume, _IN(0)|_IN(1)|_OUT(1), pData->handle, -1, &volume)) return 0xffff;

			return volume << 8;
		}
		break;
		case Media_CfgVolumeScale:
		{
			unsigned int scale;

			if (_swix(TimPlayer_Configure, _IN(0)|_OUT(3), 255, &scale))
				return 100;

			return scale;
		}
		break;
		default: ;
	}

	return 0xffffffff;
}

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

const MediaDriver TimDriver =
{
	  TimDriver_Name
	, throw_TimDriver_LoadObject
	, TimDriver_UnLoadObject
	, NULL
	, TimDriver_Play
	, TimDriver_Freeze
	, TimDriver_Stop
	, TimDriver_Seek
	, TimDriver_SetLoopMode
	, TimDriver_GetLoopMode
	, TimDriver_GetStatus
	, TimDriver_GetPosition
	, TimDriver_GetSectionPos
	, TimDriver_SetConfig
	, TimDriver_GetConfig
};

void Equalizer_Configure(int swi, EqualizerParams* pParams)
{
	_kernel_swi_regs	regs;

	if (pParams)
	{
		int i, fs; // fs = f mixing
		double a0, b1, b2, f, fp, s;

		// Find current mixing frequency
		regs.r[0] = 1;
		// Sound_SampleRate
		if (_kernel_swi(0x060146, &regs, &regs))
		{
			regs.r[0] = 0;
			regs.r[1] = 0;
			regs.r[2] = 0;
			regs.r[3] = 0;
			regs.r[4] = 0;
			// Sound_Configure
			_kernel_swi(0x060140, &regs, &regs);
			fs = 1000000/regs.r[2];
		}
		else fs = regs.r[2]>>10;

		// First lowpass filter
		i = 0;
		f = (2*M_PI * pParams->Freq[i]) / fs;
		s = sin(f) / sqrt(2);
		b2 = (1. - s) / (1. + s);
		b1 = -2 * cos(f) / (1. + s);
		a0 = (1. + b2 + b1) / 4.;
		regs.r[0] = 7;
		regs.r[1] = i;
		regs.r[2] = (int)(pParams->Gain[i]*a0*256.);
		regs.r[3] = 2*regs.r[2];
		regs.r[4] = regs.r[2];
		regs.r[5] = (int)(-b1*65536*256);
		regs.r[6] = (int)(-b2*65536*256);
		// xxx_Configure
		if (_kernel_swi(swi, &regs, &regs)) return;

		// Then bandpass filters
		for (i = 1; (i < Opt_MAX_EQ_BANDS) && (pParams->Freq[i] < (fs*.45)); i++,f=fp)
		{
			double d0, d1, s0, s1;
			fp = (2*M_PI*pParams->Freq[i])/fs;
			d0 = 1 + cos(fp) + sin(fp);
			d1 = 1 + cos(f) + sin(f);
			s0 = sin(fp) - 1 - cos(fp);
			s1 = sin(f) - 1 - cos(f);
			s = d0 * d1;
			b2 = -(s0 * s1) / s;
			b1 = (s0 * d1 + s1 * d0) / s;
			a0 = (sin(f) / (1 + cos(fp))) / s;
			regs.r[0] = 7;
			regs.r[1] = i;
			regs.r[2] = (int)(pParams->Gain[i]*a0*256.);
			regs.r[3] = 0;
			regs.r[4] = -regs.r[2];
			regs.r[5] = (int)(b1*65536*256);
			regs.r[6] = (int)(b2*65536*256);
			// xxx_Configure
			if (_kernel_swi(swi, &regs, &regs)) return;
		}

		// Finally, highpass filter
		if (i < Opt_MAX_EQ_BANDS)
		{
			f = (2*M_PI*pParams->Freq[i-1])/(int)(fs);
			s = sin(f) / sqrt(2);
			b2 = (1. - s) / (1. + s);
			b1 = -2 * cos(f) / (1. + s);
			a0 = (1. + b2 - b1) / 4.;
			regs.r[0] = 7;
			regs.r[1] = i;
			regs.r[2] = (int)(pParams->Gain[i]*a0*256.);
			regs.r[3] = -2*regs.r[2];
			regs.r[4] = regs.r[2];
			regs.r[5] = (int)(-b1*65536*256);
			regs.r[6] = (int)(-b2*65536*256);
			// xxx_Configure
			if (_kernel_swi(swi, &regs, &regs)) return;
			i++;
		}

		regs.r[0] = 6;
		regs.r[1] = i;
		// xxx_Configure
		if (_kernel_swi(swi, &regs, &regs)) return;
	}
	else
	{
		regs.r[0] = 6;
		regs.r[1] = 0;
		// xxx_Configure
		if (_kernel_swi(swi, &regs, &regs)) return;
	}
}
