#include "GlobHdr.h"
#include "IReader.h"
#include "MetaCodecs.h"

#include "swis.h"
#include <stdio.h>

#define ErrNum_LanMan98_InUse 0x100A0

static FooterCodec footers[] =
{
	  Meta_ID3v1
	, Meta_APE
	, NULL
};

typedef struct
{
	unsigned int handle;
	char*        name;
	bool         bCheckDelay;
} FromData;

/*------------------------------------------------------------------------------
 * File related stuff
 */

const _kernel_oserror* FromFile_Read(IStream* s, void* p, int size)
{
	FromData* fd = s->reader.data;
	const _kernel_oserror* e;

	e = _swix(OS_GBPB, _INR(0,3), 4, fd->handle, p, size);

	return e;
}

static const _kernel_oserror* IStream_ProcessFooters(IStream* s)
{
	const _kernel_oserror* e;
	int old_end;

	// Only for seekable streams
	if (!IStream_IsSeekable(s))
		return NULL;

	// Loop till no footer is found
	do
	{
		FooterCodec* meta = &footers[0];

		old_end = s->source.end;

		// Loop on codecs till a footer was found
		while ((*meta != NULL) && (old_end == s->source.end))
		{
			e = (*meta)(s);
			if (e) return e;

			meta++;
		}

	} while (s->source.end < old_end);

	return NULL;
}

/*------------------
 * FromFile_OpenBase
 */

static const _kernel_oserror* FromFile_OpenBase(IStream* s, const _kernel_swi_regs* r)
{
	FromData* fd;
	const _kernel_oserror* e;
	int len;

	e = IStream_Alloc(s, (void**) &fd, sizeof(*fd));
	if (e) return e;

	s->reader.data = fd;
	fd->handle = 0;
	fd->name = NULL;

	// Open file in read mode
	e = _swix(OS_Find, _INR(0,1)|_OUT(0), 0x4f, r->r[0], &fd->handle);
	if (e) return e;

	e = _swix(OS_Args, _INR(0,1)|_OUT(2), 2, fd->handle, &s->source.length);
	if (e) return e;

	s->source.start = 0;
	s->source.end = s->source.length;

	// Read canonical filename size
	e = _swix(OS_Args, _INR(0,2)|_IN(5)|_OUT(5), 7, fd->handle, 0, 0, &len);
	if (e) return e;
	len = 1 - len;
	e = IStream_Alloc(s, (void**) &fd->name, len);
	if (e) return e;
	// Read canonical filename
	e = _swix(OS_Args, _INR(0,2)|_IN(5), 7, fd->handle, fd->name, len);
	if (e) return e;
	fd->name[len - 1] = 0;

	// check if delays are to be processed
	if (_swix(OS_FSControl, _INR(0,1), 47, fd->name) != NULL)
		fd->bCheckDelay = false;
	else
		fd->bCheckDelay = true;

	e = IStream_ProcessFooters(s);
	if (e) return e;

	// Reset just in case it was modified
    s->source.pos = 0;

	// Move to start of file
	e = FromFile_SetPos(s, 0);
	if (e) return e;

	s->state = EIStream_State_MetaDataLookup;

	return NULL;
}

/*--------------
 * FromFile_Open
 */

static const _kernel_oserror* FromFile_Open(IStream* s, const _kernel_swi_regs* r)
{
	const _kernel_oserror* e;

	// Get file type
	e = _swix(OS_File, _INR(0,1)|_OUT(6), 23, r->r[0], &s->source.os_filetype);
	if (e) return e;

	return FromFile_OpenBase(s, r);
}

/*--------------
 * FromFile_Open
 */

static const _kernel_oserror* FromFile_OpenExt(IStream* s, const _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	Desc* desc = (Desc*) r->r[1];

	if (desc->flags & DiskSample_Desc_UseTypeString)
	{
		e = IStream_Accept(s, desc);
		if (e) return e;
	}
	else
	{
		s->source.os_filetype = (int) desc->filetype;
	}

	return FromFile_OpenBase(s, r);
}

/*-----------------
 * FromFile_DelayIO
 */

static bool FromFile_DelayIO(IStream* s)
{
	FromData* fd = s->reader.data;

	if (fd->bCheckDelay == true)
	{
		const _kernel_oserror* e;

		e =_swix(OS_FSControl, _INR(0,1), 47, fd->name);
		if (e) return true;
	}

	return false;
}

/*--------------
 * FromFile_Fill
 */

static const _kernel_oserror* FromFile_Fill(IStream* s)
{
	FromData* fd = s->reader.data;
	const _kernel_oserror* e;
	int toread, fpos, pos, buf;

	// if input is not finished (EOF) then we load another chunk of data
	if (s->inb->finishflag)
		return NULL;

	// load a chunk of data
	toread = s->inb->start - s->inb->free;
	if (toread <= 0)
		toread += s->inb->size;

	if (toread <= s->reader.chunksize)
		return NULL;

	toread = s->reader.chunksize;
	buf = (int)(s->inb + 1);
	e =_swix(OS_Args, _INR(0,1)|_OUT(2), 0, fd->handle, &fpos);
	if (e)
	{
		if (e->errnum == ErrNum_LanMan98_InUse)
			return NULL;

		return NULL; //e; to cope with SunFish
	}

	if (fpos >= s->source.end)
	{
		s->inb->finishflag = 1;
		return NULL;
	}

	if (toread > (s->source.end - fpos))
	toread = s->source.end - fpos;

	e =_swix(OS_GBPB, _INR(0,3)|_OUTR(2,3), 4, fd->handle
		, buf + s->inb->free, toread
		, &pos, &toread);
	if (e)
	{
		if (e->errnum == ErrNum_LanMan98_InUse)
			return NULL;

		return NULL;//e; to cope with SunFish
	}

    // Read current pos
	e =_swix(OS_Args, _INR(0,1)|_OUT(2), 0, fd->handle, &s->source.pos);
	if (e) return e;

	// update pointer in input buffer
	pos -= buf;
	if (pos >= s->inb->size) pos = 0;
	s->inb->free = pos;

	return NULL;
}

/*----------------
 * FromFile_SetPos
 */

const _kernel_oserror* FromFile_SetPos(IStream* s, int pos)
{
	FromData* fd = s->reader.data;
	const _kernel_oserror* e;

	if (pos < 0) pos = 0;
	pos += s->source.start;

	if (pos >= s->source.end)
		pos = s->source.start;

	e = _swix(OS_Args, _INR(0,2), 1, fd->handle, pos);
	if (e) _swix(OS_Args, _INR(0,2), 1, fd->handle, s->source.start);

    // Read current pos
	e =_swix(OS_Args, _INR(0,1)|_OUT(2), 0, fd->handle, &s->source.pos);
	if (e) return e;

	return e;
}

/*---------------
 * FromFile_Close
 */

static const _kernel_oserror* FromFile_Close(IStream* s)
{
	FromData* fd = s->reader.data;

	if (!fd) return NULL;

	if (fd->handle) _swi(OS_Find, _INR(0,1), 0, fd->handle);
	IStream_Free(s, (void**) &fd->name);

	IStream_Free(s, &s->reader.data);

	return NULL;
}

IReader FromFile =
{
	  FromFile_Open
	, FromFile_DelayIO
	, FromFile_Fill
	, FromFile_SetPos
	, FromFile_SetPos
	, FromFile_Close
};

IReader FromFileExt =
{
	  FromFile_OpenExt
	, FromFile_DelayIO
	, FromFile_Fill
	, FromFile_SetPos
	, FromFile_SetPos
	, FromFile_Close
};
