#include "FSyslog.h"
#include "Loaders.h"
#include "Song.h"
#include "Instrument.h"
#include "Mem.h"

#include "kernel.h"
#include "swis.h"
#include "string.h"

static const _kernel_oserror Err_MemFileSetInvalidPosition =
{ErrNum_InputError, "Invalid offset in 'memory' file."};
static const _kernel_oserror Err_MemFileReadAfterEnd =
{ErrNum_InputError, "Reached end of 'memory' file."};

uint32_t FileLoad_GetSize(SongHdr* pSong)
{
	uint32_t size = 0;

	if (pSong->pLoaderData->pMemory)
		size = pSong->pLoaderData->MemSize;
	else
		_swix(OS_Args, _INR(0,1)|_OUT(2), 2, pSong->pLoaderData->File, &size);

	return size;
}

uint32_t FileLoad_GetPos(SongHdr* pSong)
{
	uint32_t pos = 0;

	if (pSong->pLoaderData->pMemory)
		pos = pSong->pLoaderData->MemPos;
	else
		_swix(OS_Args, _INR(0,1)|_OUT(2), 0, pSong->pLoaderData->File, &pos);

	return pos;
}

const _kernel_oserror* FileLoad_SetPos(SongHdr* pSong, int pos)
{
	if (pSong->pLoaderData->pMemory)
	{
		if (pos < 0)
		{
			pSong->pLoaderData->MemPos = 0;
			return &Err_MemFileSetInvalidPosition;
		}
		else if (pos > pSong->pLoaderData->MemSize)
		{
			pSong->pLoaderData->MemPos = pSong->pLoaderData->MemSize;
			return &Err_MemFileSetInvalidPosition;
		}
		pSong->pLoaderData->MemPos = pos;

		return NULL;
	}
	else
	{
		return _swix(OS_Args, _INR(0,2), 1, pSong->pLoaderData->File, pos);
	}
}

const _kernel_oserror* FileLoad_Skip(SongHdr* pSong, uint32_t size)
{
	return FileLoad_SetPos(pSong, FileLoad_GetPos(pSong) + size);
}

const _kernel_oserror* FileLoad_Read(SongHdr* pSong, void* p, int length)
{
	if (length <= 0) return NULL;

	if (pSong->pLoaderData->pMemory)
	{
		int max = pSong->pLoaderData->MemSize - pSong->pLoaderData->MemPos;

		if (length > max)
		{
			pSong->pLoaderData->MemPos += max;
			return &Err_MemFileReadAfterEnd;
		}
		else
		{
			memmove(p, pSong->pLoaderData->pMemory + pSong->pLoaderData->MemPos, length);
			pSong->pLoaderData->MemPos += length;
			return NULL;
		}
	}
	else
	{
		const _kernel_oserror* e;
		_kernel_swi_regs r;
		int carry;

		r.r[0] = 4;
		r.r[1] = (int) pSong->pLoaderData->File;
		r.r[2] = (int) p;
		r.r[3] = length;
		e = _kernel_swi_c(OS_GBPB, &r, &r, &carry);
		if (e) return e;

		if (!carry) return NULL;

		// if not all read set EOF flag then force error message
		_kernel_swi(OS_BGet, &r, &r);
		return _kernel_swi(OS_BGet, &r, &r);
	}
}

const _kernel_oserror* FileLoad_ReadInt(SongHdr* pSong, uint32_t* pval, int length)
{
	*pval = 0;
	return FileLoad_Read(pSong, pval, length);
}

const _kernel_oserror* FileLoad_ReadReverseInt(SongHdr* pSong, uint32_t* pval, int length)
{
	const _kernel_oserror* e = FileLoad_ReadInt(pSong, pval, length);

	*pval = ((*pval & 0xff) << 24)
	      | ((*pval & 0xff00) << 8)
	      | ((*pval & 0xff0000) >> 8)
	      | ((*pval & 0xff000000) >> 24);

	if (length < 4)
		*pval = *pval >> (32 - length*8);

	return e;
}

const _kernel_oserror* FileLoad_ReadByte(SongHdr* pSong, uint32_t* pval)
{
	if (pSong->pLoaderData->pMemory)
	{
		if (pSong->pLoaderData->MemSize <= pSong->pLoaderData->MemPos)
		{
			return &Err_MemFileReadAfterEnd;
		}
		else
		{
			*pval = pSong->pLoaderData->pMemory[pSong->pLoaderData->MemPos];
			pSong->pLoaderData->MemPos += 1;
			return NULL;
		}
	}
	else
	{
		const _kernel_oserror* e;
		_kernel_swi_regs r;
		int carry;

		r.r[1] = (int) pSong->pLoaderData->File;
		e = _kernel_swi_c(OS_BGet, &r, &r, &carry);
		if (e) return e;

		if (!carry)
		{
			*pval = r.r[0];
			return NULL;
		}

		// if EOF flag force error message
		return _kernel_swi(OS_BGet, &r, &r);
	}
}

const _kernel_oserror* FileLoad_ReadString(SongHdr* pSong, uint8_t** pp, uint32_t length, bool bKeepAll)
{
	const _kernel_oserror* e;
	int i;
	uint8_t* p;

	// allocate memory with an extra byte
	e = CMem_Alloc(pSong->pGlb, (void**) pp, length + 1);
	if (e) return e;
	p = *pp;
	e = FileLoad_Read(pSong, p, length);
	if (e) return e;

	p[length] = 0;
	if (bKeepAll)
	{
		// loop to set blanks
		for(i = length - 1; i >= 0; i--)
		{
			if (p[i] < 32) p[i] = ' ';
		}
	}

	return NULL;
}

const _kernel_oserror* FileLoad_ReadSample(SongHdr* pSong, Sample* pSample)
{
	const _kernel_oserror* e;
	int size = pSample->Size;
	int bsize;
	int toread;

	if (pSample->Type & Smp_Type_Stereo)
		size <<= 1;
	if (pSample->Type & Smp_Type_16bit)
		size <<= 1;

	// allocate memory with a few extra bytes
	e = CMem_Alloc(pSong->pGlb, &pSample->Ptr, size + 4);
	if (e) return e;

	if (pSong->pLoaderData->pMemory)
	{
		int max = pSong->pLoaderData->MemSize - pSong->pLoaderData->MemPos;

		if (max > size) max = size;

		if (max > 0)
			memmove(pSample->Ptr
			      , pSong->pLoaderData->pMemory + pSong->pLoaderData->MemPos
			      , max);
		pSong->pLoaderData->MemPos += max;
		toread = size - max;
	}
    else
    {
		e = _swix(OS_GBPB, _INR(0,3)|_OUT(3)
				, 4, pSong->pLoaderData->File, pSample->Ptr, size
				, &toread);
		if (e) toread = size;
    }

	// shorten sample size with number of bytes not read
	if (toread > 0) size -= toread;
	bsize = size;

	if (pSample->Type & Smp_Type_Stereo)
		size >>= 1;
	if (pSample->Type & Smp_Type_16bit)
		size >>= 1;

	if (pSample->Size != size)
	{
		SysLog_FileCorrupted
			( ((pSample->Size - bsize) > 50) ? SysLog_High : SysLog_Medium
			, pSong
			, FileLoad_GetPos(pSong) - bsize
		    , "%s%sSample size %d reduced to %d"
		    , (pSample->Type & Smp_Type_Stereo) ? "Stereo " : ""
		    , (pSample->Type & Smp_Type_16bit) ? "16-bit " : ""
		    , pSample->Size
		    , size
		    );
		pSample->Size = size;
	}

	return NULL;
}
