#include "Media.h"
#include "MediaLst.h"
#include "MediaDrv.h"

#include <stdlib.h>
#include <string.h>

#include "WimpLib:Exception.h"
#include "WimpLib:mem.h"
#include "WimpLib:Task.h"
#include "WimpLib:Utils.h"
#include "DigitalCD.h"
#include "Metadata.h"

const _kernel_oserror Media_ErrorModuleInUse = {1, "Module does not support multiple files"};

/*------------------------------------------------------------------------
 *- Media Object List ----------------------------------------------------
 *------------------------------------------------------------------------*/

MediaObjectList* throw_New_MediaObjectList(void)
{
	return New_List();
}

void Delete_MediaObjectList(MediaObjectList* plist)
{
	MediaObjectList_Clear(plist);
	Delete_List(plist);
}

int MediaObjectList_Count(const MediaObjectList* plist)
{
	return List_Count(plist);
}

void throw_MediaObjectList_Add(MediaObjectList* plist, struct MediaObject* pObj)
{
	List_InsertBefore(plist, NULL, pObj);
}

MediaObject* MediaObjectList_Get(const MediaObjectList* plist, int i) throws(index)
{
	return (MediaObject*) List_Get(plist, i);
}

int MediaObjectList_Find(const MediaDriverList* plist, const struct MediaObject* pobj)
{
	return List_Find(plist, 0, pobj);
}

void MediaObjectList_Del(MediaObjectList* plist, int i) throws(index)
{
	List_Remove(plist, i);
}
void MediaObjectList_Clear(MediaObjectList* plist)
{
	List_Clear(plist);
}

/*------------------------------------------------------------------------
 *- Media Driver List ----------------------------------------------------
 *------------------------------------------------------------------------*/


MediaDriverList* throw_New_MediaDriverList(void)
{
	return New_List();
}

void Delete_MediaDriverList(MediaDriverList* plist)
{
	MediaDriverList_Clear(plist);
	Delete_List(plist);
}

int MediaDriverList_Count(const MediaDriverList* plist)
{
	return List_Count(plist);
}

void throw_MediaDriverList_Add(MediaDriverList* plist, const struct MediaDriver* pObj)
{
	List_InsertBefore(plist, NULL, (void*) pObj);
}

const MediaDriver* MediaDriverList_Get(const MediaDriverList* plist, int i) throws(index)
{
	return (const MediaDriver*) List_Get(plist, i);
}

int MediaDriverList_Find(const MediaDriverList* plist, const struct MediaDriver* pobj)
{
	return List_Find(plist, 0, pobj);
}

void MediaDriverList_Del(MediaDriverList* plist, int i) throws(index)
{
	List_Remove(plist, i);
}
void MediaDriverList_Clear(MediaDriverList* plist)
{
	List_Clear(plist);
}

/*------------------------------------------------------------------------
 *- Media Object ---------------------------------------------------------
 *------------------------------------------------------------------------*/

static MediaObject* throw_New_MediaObject(const MediaDriver* pDriver)
{
	MediaObject* pObject = throw_mem_calloc(1, sizeof(*pObject));

	pObject->m_pDriver = pDriver;
	pObject->m_Status = status_empty;
	pObject->m_LoopMode = loop_volume_play;
	pObject->m_iSpeed = 44100;
	pObject->m_HardwareType = Media_HType_File;
	pObject->m_bSeekable = true;
	pObject->m_metas = throw_New_MetaList();

	return pObject;
}

static void Delete_MediaObject(MediaObject* pobj)
{
	pobj->m_pDriver->pUnLoadObject(pobj);
	mem_free(pobj->m_pFile);
	Delete_MetaList(pobj->m_metas);
	mem_free(pobj);
}

/*------------------------------------------------------------------------------
 * Metadata info management
 */

void MediaObject_SetTimestamp(MediaObject* pobj, unsigned int stamp)
{
	if (stamp)
		pobj->m_timestamp = stamp;
	else pobj->m_readtimestamp = pobj->m_timestamp;
}

const MetaData* MediaObject_FindMeta(MediaObject* pobj, EMetaId id)
{
	return MetaList_Find(pobj->m_metas, id);
}

const MetaData* MediaObject_GetMeta(MediaObject* pobj, int index) throws(index)
{
	return MetaList_Get(pobj->m_metas, index);
}

int MediaObject_CountMeta(MediaObject* pobj)
{
	return MetaList_Count(pobj->m_metas);
}

bool throw_MediaObject_MergeMeta(MediaObject* pobj, const MetaList* pOld)
{
	return throw_MetaList_Merge(pobj->m_metas, pOld);
}

bool throw_MediaObject_SetMeta
	( MediaObject* pobj
	, EMetaId id
	, EMetaOrigin origin
	, const void* data
	, unsigned int size
	)
{
	bool b = throw_MetaList_SetData(pobj->m_metas, id, origin, data, size);

	if (b)
	{
		switch (id)
		{
			case EMetaId_StreamType:
				pobj->m_Updates |= Media_Updated_Type;
			break;
			case EMetaId_StreamTitle:
				pobj->m_Updates |= Media_Updated_Title;
			// nobreak;
			case EMetaId_StreamStation:
			case EMetaId_StreamArtist:
			case EMetaId_StreamAlbum:
			case EMetaId_StreamTrackNumber:
			case EMetaId_StreamDate:
				pobj->m_Updates |= Media_Updated_Meta;
			break;
		}
	}

	return b;
}

bool throw_MediaObject_SetText
	( MediaObject* pobj
	, EMetaId id
	, EMetaOrigin origin
	, const char* data
	)
{
	// Store only consistant date
	if (EMetaId_StreamDate == id)
	{
		int len = strlen(data);

		// Valid date?
		switch(len)
		{
			case 10: // YYYY-MM-DD
			{
				if ((data[9] < '0')
				||  (data[9] > '9')
				||  (data[8] < '0')
				||  (data[8] > '3')
				||  (data[7] != '-')
				||  ((data[8] == '0') && (data[9] < '1'))
				||  ((data[8] == '3') && (data[9] > '1')))
					len = 0;
			}
			// no break;
			case 7: // YYYY-MM
			{
				if ((data[6] < '0')
				||  (data[6] > '9')
				||  (data[5] < '0')
				||  (data[5] > '1')
				||  (data[4] != '-')
				||  ((data[5] == '0') && (data[6] < '1'))
				||  ((data[5] == '1') && (data[6] > '2')))
					len = 0;
			}
			// no break;
			case 4: // YYYY
			{
				if ((data[3] < '0')
				||  (data[3] > '9')
				||  (data[2] < '0')
				||  (data[2] > '9')
				||  (data[1] < '0')
				||  (data[1] > '9')
				||  (data[0] < '1')
				||  (data[0] > '2'))
					len = 0;
			}
			break;
			default:
			{
				len = 0;
			}
		}

		if (0 >= len)
			return false;
	}

	return throw_MediaObject_SetMeta(pobj, id, origin, data, 0);
}

/*------------------------------------------------------------------------
 *- Media ----------------------------------------------------------------
 *------------------------------------------------------------------------*/

static Media TheMedia;

void throw_Media_Media(void)
{
	TheMedia.m_pDrivers = throw_New_MediaDriverList();
	TheMedia.m_pObjects = throw_New_MediaObjectList();
}

void Media_NotMedia(void)
{
	Delete_MediaDriverList(TheMedia.m_pDrivers);
	TheMedia.m_pDrivers = NULL;
	Delete_MediaObjectList(TheMedia.m_pObjects);
	TheMedia.m_pObjects = NULL;
}

void throw_Media_RegisterDriver(const MediaDriver* pDriver)
{
	throw_MediaDriverList_Add(TheMedia.m_pDrivers, pDriver);
}

void Media_UnregisterDriver(const MediaDriver* pDriver)
{
	int i = MediaDriverList_Find(TheMedia.m_pDrivers, pDriver);

	if (i < 0) return;

	MediaDriverList_Del(TheMedia.m_pDrivers, i);
}

const _kernel_oserror* Media_LoadObject(MediaObject** ppobj, const char* pObjectName, unsigned int Params, const MediaDriver* pDriver)
{
	MediaObject* volatile pobj = NULL;

	if (MediaDriverList_Find(TheMedia.m_pDrivers, pDriver) < 0) return NULL;

	try
	{
		pobj = throw_New_MediaObject(pDriver);
		throw_MediaObjectList_Add(TheMedia.m_pObjects, pobj);
		pobj->m_pFile = throw_mem_allocstring(pObjectName);
		pobj->m_Params = Params;

		pobj->m_pDriver = pDriver;
		if (!pobj->m_pDriver->pthrow_LoadObject(pobj, pObjectName))
		{
			// Exit silently
			Media_UnLoadObject(pobj);
			pobj = NULL;
		}
	}
	catch
	{
		if (pobj)
		{
			Media_UnLoadObject(pobj);
			pobj = NULL;
		}
		*ppobj = NULL;
		return &exception_current()->error;
	}
	catch_end

	*ppobj = pobj;

	return NULL;
}

void Media_UnLoadObject(MediaObject* pobj)
{
	if (pobj)
	{
		int i = MediaObjectList_Find(TheMedia.m_pObjects, pobj);

		if (i >= 0) MediaObjectList_Del(TheMedia.m_pObjects, i);
		Delete_MediaObject(pobj);
	}
}

void Media_Chain(MediaObject* pobj, MediaObject* pnext)
{
	int i = MediaObjectList_Find(TheMedia.m_pObjects, pobj);

	if (i < 0) return;

	try
	{
	if (pobj->m_pDriver->pChain != NULL)
		pobj->m_pDriver->pChain(pobj, pnext);
	}
	catch
	{
		App_ReportException();
	}
	catch_end
}

void Media_Attach(MediaObject* pobj, void* pdata, Media_HType type)
{
	pobj->m_pData = pdata;
	pobj->m_HardwareType = type;
}

void Media_Play(MediaObject* pobj, uint64_t pos)
{
	int i = MediaObjectList_Find(TheMedia.m_pObjects, pobj);

	if (i < 0) return;

	try
	{
		pobj->m_pDriver->pPlay(pobj, pos);
	}
	catch
	{
		App_ReportException();
	}
	catch_end
}

void Media_Freeze(MediaObject* pobj, bool on)
{
	int i = MediaObjectList_Find(TheMedia.m_pObjects, pobj);

	if (i < 0) return;

	try
	{
		pobj->m_pDriver->pFreeze(pobj, on);
	}
	catch
	{
		App_ReportException();
	}
	catch_end
}

void Media_Stop(MediaObject* pobj)
{
	int i = MediaObjectList_Find(TheMedia.m_pObjects, pobj);

	if (i < 0) return;

	try
	{
		pobj->m_pDriver->pStop(pobj);
	}
	catch
	{
		App_ReportException();
	}
	catch_end
}

void Media_Seek(MediaObject* pobj, EMedia_Seek set)
{
	int i = MediaObjectList_Find(TheMedia.m_pObjects, pobj);

	if (i < 0) return;

	try
	{
		pobj->m_pDriver->pSeek(pobj, set);
	}
	catch
	{
		App_ReportException();
	}
	catch_end
}

void Media_SetSeekable(MediaObject* pobj, bool able)
{
	int i = MediaObjectList_Find(TheMedia.m_pObjects, pobj);

	if (i < 0) return;

	pobj->m_bSeekable = able;
}

bool Media_GetSeekable(MediaObject* pobj)
{
	return pobj->m_bSeekable;
}

void Media_SetConfig(MediaObject* pobj, Media_Config type, const void* value)
{
	int i = MediaObjectList_Find(TheMedia.m_pObjects, pobj);

	if (i < 0) return;

	try
	{
		pobj->m_pDriver->pSetConfig(pobj, type, value);
	}
	catch
	{
		App_ReportException();
	}
	catch_end
}

unsigned int Media_GetConfig(MediaObject* pobj, Media_Config type)
{
	int i = MediaObjectList_Find(TheMedia.m_pObjects, pobj);
	unsigned int ret = 0xffffffff;

	if (i < 0) return ret;

	if (type == Media_CfgHardwareType)
		return pobj->m_HardwareType;

	try
	{
		ret = pobj->m_pDriver->pGetConfig(pobj, type);
	}
	catch
	{
		App_ReportException();
	}
	catch_end

	return ret;
}

void Media_SetLoopMode(MediaObject* pobj, Media_LoopMode mode)
{
	int i = MediaObjectList_Find(TheMedia.m_pObjects, pobj);

	if (i < 0) return;

	try
	{
		pobj->m_pDriver->pSetLoopMode(pobj, mode);
	}
	catch
	{
		App_ReportException();
	}
	catch_end
}

Media_LoopMode Media_GetLoopMode(MediaObject* pobj)
{
	int i = MediaObjectList_Find(TheMedia.m_pObjects, pobj);
	Media_LoopMode ret = loop_volume_play;

	if (i < 0) return ret;

	try
	{
		ret = pobj->m_pDriver->pGetLoopMode(pobj);
	}
	catch
	{
		App_ReportException();
	}
	catch_end

	return ret;
}

Media_Status Media_GetStatus(MediaObject* pobj)
{
	int i = MediaObjectList_Find(TheMedia.m_pObjects, pobj);
	Media_Status ret = status_empty;

	if (i < 0) return ret;

	try
	{
		ret = pobj->m_pDriver->pGetStatus(pobj);
	}
	catch
	{
		App_ReportException();
		ret = status_error;
	}
	catch_end

	return ret;
}

unsigned int Media_GetUpdates(MediaObject* pobj)
{
	int i = MediaObjectList_Find(TheMedia.m_pObjects, pobj);
	unsigned int ret = 0;

	if (i < 0) return ret;

	ret = pobj->m_Updates;
	// Reset updates;
	pobj->m_Updates = 0;

	return ret;
}

void Media_GetPosition(MediaObject* pobj, MediaPosition* pos)
{
	try
	{
		pobj->m_pDriver->pGetPosition(pobj, pos);
	}
	catch
	{
		App_ReportException();
	}
	catch_end
}

uint64_t Media_GetSectionPos(MediaObject* pobj, unsigned int section)
{
	int i = MediaObjectList_Find(TheMedia.m_pObjects, pobj);
	uint64_t pos = 0;

	if (i < 0) return 0;

	try
	{
		if (pobj->m_pDriver->pGetSectionPos)
			pos = pobj->m_pDriver->pGetSectionPos(pobj, section);
	}
	catch
	{
		App_ReportException();
	}
	catch_end

	return pos;
}
