#include "GlobHdr.h"
#include "IReader.h"
#include "Channels.h"
#include "Codecs.h"
#include "Log.h"

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

#undef TEST_FILE

#define BLOCK_SIZE 2352

typedef struct
{
	int	r[5];
#ifdef TEST_FILE
	int file; // Debug
#endif
} sCDControlBlock;

typedef struct
{
	sCDControlBlock handle;
	uint32_t        start;
	uint32_t        end;
	uint32_t        length;
	uint32_t        pos;
	uint8_t         buffer[BLOCK_SIZE];
} FromData;

/*------------------------------------------------------------------------------
 * CD related stuff
 */

static const _kernel_oserror* ConvertDriveToDevice(uint32_t drive, sCDControlBlock* pCDBlock)
{
	const _kernel_oserror* e;

	e = _swix(0x041e80, _IN(0)|_OUT(1), drive, &drive);
	if (e) return e;

	pCDBlock->r[0] = (drive & 0x00000007);
	pCDBlock->r[1] = (drive & 0x00000018) >> 3;
	pCDBlock->r[2] = (drive & 0x000000e0) >> 5;
	pCDBlock->r[3] = (drive & 0x0000ff00) >> 8;
	pCDBlock->r[4] = (drive & 0xffff0000) >> 16;

	return NULL;
}

static const _kernel_oserror* ReadAudio(int blockid, int nrblocks, uint8_t* pbuf, sCDControlBlock* pCDBlock)
{
#ifdef TEST_FILE
	const _kernel_oserror* e;

	if (!pCDBlock->file)
	{
		e = _swix(OS_Find, _INR(0,1)|_OUT(0)
				, 0x4f, "ADFS::HardDisc4.$.a/wav"
				, &pCDBlock->file);
		if (e) return e;
	}

	if (pCDBlock->file)
	{
Log("Read %d + %d", blockid, nrblocks);
		_swix(OS_Args, _INR(0,2), 1, pCDBlock->file, blockid*BLOCK_SIZE);
		_swix(OS_GBPB, _INR(0,3), 4, pCDBlock->file, pbuf, nrblocks*BLOCK_SIZE);
	}
	else
	{
		memset(pbuf, 0, nrblocks*BLOCK_SIZE);
	}

	return NULL;
#else
	return _swix(0x041266, _INR(0,4)|_IN(7), 0, blockid, nrblocks, pbuf, 0, pCDBlock);
#endif
}

/*-------------
 * FromCD_Open
 */

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

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

	s->reader.data = fd;
	memset(fd, 0, sizeof(*fd));

	e = ConvertDriveToDevice(r->r[0], &fd->handle);

	fd->start = r->r[1];
	fd->end = r->r[2] + 1;
	fd->length = fd->end - fd->start;
	s->source.start = fd->start * BLOCK_SIZE;
	s->source.end = fd->end * BLOCK_SIZE;
	s->source.length = s->source.end;
    s->source.pos = 0;

	// PCM
	pcm_params* params;

	s->codec.prefn = &PCM_Codec;

	e = IStream_Alloc(s, (void**) &params, sizeof(*params));
	if (e) return e;
	s->codec.params = params;

    params->flags = 0;
    params->channels = 2;
    params->channels_layout = Channels_GetDefaultLayout(2);
    params->samplerate = 44100;
   	params->bitspersample = 16;
   	params->blocksize = 4;

	s->state = EIStream_State_CodecLookup;

	return NULL;
}

/*-----------------
 * FromCD_DelayIO
 */

static bool FromCD_DelayIO(IStream* s)
{
	IGNORE(s);

	return false;
}

/*--------------
 * FromCD_Fill
 */

static const _kernel_oserror* FromCD_Fill(IStream* s)
{
	FromData* fd = s->reader.data;
	const _kernel_oserror* e;
	int toread, minread, size;
	uint8_t* buf;

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

	if (s->source.pos >= fd->length)
	{
		s->inb->finishflag = 1;
		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 / BLOCK_SIZE);
	if (toread > 255) toread = 255;
	buf = (uint8_t*)(s->inb + 1);

	if (toread > (fd->length - s->source.pos))
		toread = fd->length - s->source.pos;

	if (s->inb->start > s->inb->free)
		minread = toread;
	else
	{
		minread = s->inb->size - s->inb->free;
		minread /= BLOCK_SIZE;
		if (minread > toread) minread = toread;
	}

	if (minread)
	{
		e = ReadAudio(fd->start + s->source.pos, minread, buf + s->inb->free, &fd->handle);
		if (e) return e;
		s->source.pos += minread;
		s->inb->free += minread * BLOCK_SIZE;
		if (s->inb->free >= s->inb->size)
			s->inb->free -= s->inb->size;
		toread -= minread;
	}

	if (toread > 0)
	{
		if (s->inb->free != 0)
		{
			e = ReadAudio(fd->start + s->source.pos, 1, &fd->buffer[0], &fd->handle);
			if (e) return e;
			s->source.pos += 1;
			toread -= 1;
			size = s->inb->size - s->inb->free;
			memcpy(buf + s->inb->free, &fd->buffer[0], size);
			memcpy(buf, &fd->buffer[size], BLOCK_SIZE - size);
			s->inb->free = BLOCK_SIZE - size;
		}

		if (toread > 0)
		{
			e = ReadAudio(fd->start + s->source.pos, toread, buf + s->inb->free, &fd->handle);
			if (e) return e;
			s->source.pos += toread;
			s->inb->free += toread * BLOCK_SIZE;
		}
	}

	return NULL;
}

/*----------------
 * FromCD_SetPos
 */

static const _kernel_oserror* FromCD_SetPos(IStream* s, int pos)
{
	FromData* fd = s->reader.data;
	pos /= BLOCK_SIZE;

	if (pos < 0) pos = 0;

	if (pos >= fd->length)
		pos = fd->length - 1;

	s->source.pos = pos;

	return NULL;
}

/*---------------
 * FromCD_Close
 */

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

	if (!fd) return NULL;

#ifdef TEST_FILE
	if (fd->handle.file) _swi(OS_Find, _INR(0,1), 0, fd->handle.file);
#endif
	IStream_Free(s, &s->reader.data);

	return NULL;
}

IReader FromCD =
{
	  FromCD_Open
	, FromCD_DelayIO
	, FromCD_Fill
	, FromCD_SetPos
	, FromCD_SetPos
	, FromCD_Close
};
