#include "GlobHdr.h"
#include "Header.h"
#include "FStream.h"

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

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

#include "common.h"
#include "buffer.h"
#include "decoder.h"

static _kernel_oserror Err_Memory =
{0, "Out of memory."};
static _kernel_oserror Err_TooManyStreamHandlers =
{0, "Too many AudioMPEG streams."};
static _kernel_oserror Err_StillHandler =
{0, "AudioMPEG stream(s) still in use."};
static _kernel_oserror Err_Bad_Stream_Handle =
{0, "Invalid stream handle."};
static _kernel_oserror err_config =
{0, "Invalid configuration code."};

#ifdef MAKEABS
static int mp3b_size=256*1024;
static int sndb_size=256*1024;
#else
static int mp3b_size=128*1024;
static int sndb_size=1024*1024/2;
#endif

static _kernel_oserror* CMem_Alloc(GlobHdr* g, void** pp, int size)
{
	IGNORE(g);

	*pp = malloc(size);

	return (*pp) ? NULL: &Err_Memory;
}

static _kernel_oserror* CMem_Free(GlobHdr* g, void* p)
{
	IGNORE(g);

	free(p);

	return NULL;
}

_kernel_oserror* Streams_Init(GlobHdr* g, void* pw)
{
	memset(&g->streamsptr, 0, sizeof(g->streamsptr[0])*max_streams);

#ifdef MAKEABS
	IGNORE(pw);
	return NULL;
#else
	return _swix(OS_AddCallBack, _INR(0,1), streams_callback_veneer, pw);
#endif
}

void Streams_CloseAll(GlobHdr* g)
{
	int i;

	for (i = 0; i < max_streams; i++)
	{
		if (g->streamsptr[i])
		{
			_kernel_swi_regs r;

			r.r[0] = g->streamsptr[i]->code;
			swi_StreamQuit(g, &r);
		}
	}
}

_kernel_oserror* Streams_Finalize(GlobHdr* g, void* pw)
{
	int i;

	for (i = 0; i < max_streams; i++)
	{
		if (g->streamsptr[i])
			return &Err_StillHandler;
	}

#ifdef MAKEABS
	IGNORE(pw);
#else
	// Remove possible pending CallAfter
	_swix(OS_RemoveTickerEvent, _INR(0,1), streams_callevery_veneer, pw);

	// Remove possible pending Callback
	if (g->flags & glb_flag_callback)
	{
		_swix(OS_RemoveCallBack, _INR(0,1), streams_callback_veneer, pw);
		g->flags &= ~glb_flag_callback;
	}
#endif

	return NULL;
}

#ifndef MAKEABS
_kernel_oserror* Streams_CalledEvery(_kernel_swi_regs* r, void* pw)
{
	GlobHdr* g = Glb;

	IGNORE(r);

	g->flags |= glb_flag_callback;
	_swix(OS_AddCallBack, _INR(0,1), streams_callback_veneer, pw);

	return NULL;
}

_kernel_oserror* Streams_Callback(_kernel_swi_regs* r, void* pw)
{
	GlobHdr* g = Glb;
	int i;

	IGNORE(r);

	for (i = 0; i < max_streams; i++)
	{
		if (g->streamsptr[i] && !(g->streamsptr[i]->flags & Stream_Flag_ForegroundProcess))
		{
			Stream_BackgroundProcess(g->streamsptr[i], 0);
		}
	}

	g->flags &= ~glb_flag_callback;

	_swix(OS_CallAfter, _INR(0,2), 2, streams_callevery_veneer, pw);

	return NULL;
}
#endif

static int freesndbuf(sndbuf* sndb)
{
	int n;

	n = sndb->start - sndb->free;
	if (n <= 0) n += sndb->size;

	return n;
}

static _kernel_oserror* createdynamicareas(stream* S, dinbuf* in)
{
#ifdef MAKEABS
	IGNORE(in);
	S->inb = malloc(mp3b_size+16*4);
	S->inb->size = mp3b_size;
#else
	_kernel_oserror* err;

	if (!in)
	{
		err = _swix(OS_DynamicArea, _INR(0,8)|_OUT(1)|_OUT(3)
			, 0, -1, mp3b_size+16*4, -1, 0x80
			, mp3b_size+16*4, 0, 0, "AudioMPEG (Input)"
			, &S->inb_area, &S->inb);
		if (err) return err;
		S->inb->size = mp3b_size;
	}
	else S->inb = in;
#endif

#ifdef MAKEABS
	S->outb = malloc(sndb_size*2+16*4);
#else
	err = _swix(OS_DynamicArea, _INR(0,8)|_OUT(1)|_OUT(3)
		, 0, -1, sndb_size*2+16*4, -1, 0x80
		, sndb_size*2+16*4, 0, 0, "AudioMPEG (Output)"
		, &S->outb_area, &S->outb);
	if (err)
	{
		_swix(OS_DynamicArea, _INR(0,1), 1, S->inb_area);
		return err;
	}
#endif
	S->outb->size = sndb_size;

	return NULL;
}

static void removedynamicareas(stream* S)
{
#ifdef MAKEABS
	free(S->inb);
	free(S->outb);
#else
	if (S->inb_area) _swix(OS_DynamicArea, _INR(0,1), 1, S->inb_area);
	if (S->outb_area) _swix(OS_DynamicArea, _INR(0,1), 1, S->outb_area);
#endif
}

static void stream_clearinput(stream* S, int bbuftoo)
{
	int ioff = _kernel_irqs_disabled();

	S->skiptype = Stream_SkipType_Init;
	if (S->mp3i.version >= -1)
		S->decoding = Stream_Decode_Seek;
	else	S->decoding = Stream_Decode_Skip;
	S->bytestoskip = 0;
	memset(&S->fr_ps, 0, sizeof(S->fr_ps));
	S->frameNum = 0;
	// subbandsynthesis
	memset(&S->Hanbuf, 0, sizeof(S->Hanbuf));
	S->HanOffset[0] = 1;
	S->HanOffset[1] = 1;

	if (S->mp3i.version >= -1)
	{
		if (S->mp3i.layer == 3)
			init_layer3(&S->l.lay3);
		else
			init_layer12(&S->l.lay12);
	}

	if (!ioff) _kernel_irqs_off();
	bs_init(&S->mp3b, S->inb->data, S->inb->size);

	if (bbuftoo)
	{
		S->inb->real_start = 0;
		S->inb->real_free = 0;
		S->inb->finished = 0;
	}

	S->outb->finished = 0;
	if (!ioff) _kernel_irqs_on();
}

static void stream_clearoutput(stream* S)
{
	int ioff = _kernel_irqs_disabled();

	if (!ioff) _kernel_irqs_off();
	S->sndb.data = S->outb->data;
	S->sndb.size = S->outb->size;
	S->sndb.start = S->outb->data;
	S->sndb.last = S->outb->data + S->outb->size - 1;
	S->sndb.free = S->outb->data;

	S->outb->real_start = 0;
	S->outb->real_free = 0;
	S->outb->finished = 0;
	if (!ioff) _kernel_irqs_on();
}

static void stream_clearinfo(stream* S)
{
	S->mp3i.version = -2;
	S->mp3i.layer = -1;
	S->mp3i.bitrate = -1;
	S->mp3i.samplerate = -1;
	S->mp3i.channels = -1;
	S->mp3i.vbr_frames = 0;
}

static _kernel_oserror* Stream_New(GlobHdr* g, stream** ps, int code, dinbuf* in)
{
	_kernel_oserror* e;
	int i;
	stream* s;

	for (i = 0; i < max_streams; i++)
	{
		if (!g->streamsptr[i])
			break;
	}

	if (i == max_streams)
		return &Err_TooManyStreamHandlers;

	e = CMem_Alloc(g, (void**) &s, sizeof(*s));
	if (e) return e;

	*ps = s;

	memset(s, 0, sizeof(*s));
	s->pGlobHdr = g;

	e = createdynamicareas(s, in);
	if (e) goto Error;

	s->code = code;
	s->bgcount = 0;

	stream_clearinfo(s);
	stream_clearinput(s, (in == NULL));
	stream_clearoutput(s);
	g->streamsptr[i] = s;

	return NULL;

Error:
	g->streamsptr[i] = NULL;
	removedynamicareas(s);
	CMem_Free(g, s);

	return e;
}

static _kernel_oserror* Stream_Destroy(GlobHdr* g, unsigned int code)
{
	stream* s;
	int i;

	for (i = 0; i < max_streams; i++)
	{
		s = g->streamsptr[i];
		if (s
		&&  (s->code == code))
			goto Found;
	}

	return &Err_Bad_Stream_Handle;

Found:
	g->streamsptr[i] = NULL;
	removedynamicareas(s);
	CMem_Free(g, s);

	return NULL;
}

_kernel_oserror* Stream_GetHeader(GlobHdr* g, unsigned int code, stream** pps)
{
	stream* s;
	int i;

	for (i = 0; i < max_streams; i++)
	{
		s = g->streamsptr[i];
		if (s
		&&  (s->code == code))
		{
			*pps = s;
			return NULL;
		}
	}

	return &Err_Bad_Stream_Handle;
}

static const char TAG_RIFF[5] = {"RIFF"};
static const char TAG_WAVE[5] = {"WAVE"};
static const char TAG_ID3[4]  = {"ID3"};
static const union
{
	char	str[5];
	int	id;
} TAG_data = {"data"};

static int one_piece(stream* S, int* count, int more, int remain)
{
	unsigned int N;

	switch(S->decoding)
	{
		case Stream_Decode_Skip:
		{
			switch(S->skiptype)
			{
				case Stream_SkipType_Init:
				{
					char* pc = &S->l.skipbuf[0];

					if (!bs_peekBytes(&S->mp3b, pc, 20))
					{
						if (S->inb->finished)
						{
							S->outb->finished = 3;
							S->decoding = Stream_Decode_Finished;
						}

						return 0;
					}

					// Nothing is decoded yet
					// We just check that there isn't an header to skip:
					// AIFF, RIFF, ID3v2
					if ((TAG_RIFF[0] == pc[0])
					&&  (TAG_RIFF[1] == pc[1])
					&&  (TAG_RIFF[2] == pc[2])
					&&  (TAG_RIFF[3] == pc[3])
					&&  (TAG_WAVE[0] == pc[8])
					&&  (TAG_WAVE[1] == pc[9])
					&&  (TAG_WAVE[2] == pc[10])
					&&  (TAG_WAVE[3] == pc[11])
					   )
					{
						bs_skipBytes(&S->mp3b, 12);
						S->bytestoskip = 0;
						S->skiptype = Stream_SkipType_WaveData;
					}
					else if (   (TAG_ID3[0] == pc[0])
					         && (TAG_ID3[1] == pc[1])
					         && (TAG_ID3[2] == pc[2]))
					{
						// size is given as 4 bytes where top bit is not set
						S->bytestoskip = pc[6] << 21;
						S->bytestoskip += pc[7] << 14;
						S->bytestoskip += pc[8] << 7;
						S->bytestoskip += pc[9];
						bs_skipBytes(&S->mp3b, 10);
						S->skiptype = Stream_SkipType_Bytes;
					}
					else
						S->decoding = Stream_Decode_Seek;

					return more;
				}
				break;
				case Stream_SkipType_Bytes:
				{
					// Just skip a given amount of bytes
					N = bs_bytecount(&S->mp3b);

					if (N > S->bytestoskip)
						N = S->bytestoskip;

					bs_skipBytes(&S->mp3b, N);
					S->bytestoskip -= N;

					if (S->bytestoskip > 0)
					{
						if (S->inb->finished)
						{
							S->outb->finished = 3;
							S->decoding = Stream_Decode_Finished;
						}

						return 0;
					}

					S->decoding = Stream_Decode_Seek;
					return more;
				}
				break;
				case Stream_SkipType_WaveData:
				{
					// skip a given amount of bytes and read next tag
					N = bs_bytecount(&S->mp3b);

					if (S->bytestoskip)
					{
						if (N > S->bytestoskip)
							N = S->bytestoskip;

						bs_skipBytes(&S->mp3b, N);
						S->bytestoskip -= N;

						if (S->bytestoskip > 0)
						{
							if (S->inb->finished)
							{
								S->outb->finished = 3;
								S->decoding = Stream_Decode_Finished;
							}

							return 0;
						}

						return more;
					}
					else if (N >= 8)
					{
						N = bs_getbits8(&S->mp3b, 8);
						N += bs_getbits8(&S->mp3b, 8) << 8;
						N += bs_getbits8(&S->mp3b, 8) << 16;
						N += bs_getbits8(&S->mp3b, 8) << 24;
						if (N == TAG_data.id)
						{
							bs_skipBytes(&S->mp3b, 4);
							S->decoding = Stream_Decode_Seek;
						}
						else
						{
							S->bytestoskip = bs_getbits8(&S->mp3b, 8);
							S->bytestoskip += bs_getbits8(&S->mp3b, 8) << 8;
							S->bytestoskip += bs_getbits8(&S->mp3b, 8) << 16;
							S->bytestoskip += bs_getbits8(&S->mp3b, 8) << 24;
						}

						return more;
					}

					if (S->inb->finished)
					{
						S->outb->finished = 3;
						S->decoding = Stream_Decode_Finished;
					}

					return 0;
				}
				break;
			}
		}
		break;
		case Stream_Decode_Seek:
		{
			if (seek_header(S, &S->mp3b, &S->fr_ps, &S->mp3i))
			{
				S->decoding = Stream_Decode_Decode;
				return more;
			}
			else if (S->inb->finished)
			{
				S->outb->finished = 3;
				S->decoding = Stream_Decode_Finished;
			}
		}
		break;
		case Stream_Decode_Decode:
		{
			if (freesndbuf(&S->sndb) > (S->fr_ps.samplesPerFrame*S->fr_ps.channels))
			{
				int bytes = bs_bytecount(&S->mp3b);

				if (!S->inb->finished && (bytes <= remain))
					return 0;

				if (bytes >= S->fr_ps.bytesInFrame)
				{
					oneframe(S);
					*count += S->fr_ps.samplesPerFrame*S->fr_ps.channels;
					S->decoding = Stream_Decode_Seek;
					return more;
				}

				if(S->inb->finished)
				{
					S->outb->finished = 3;
					S->decoding = Stream_Decode_Finished;
					return 0;
				}
			}
		}
		break;
	}

	return 0;
}

void Stream_BackgroundProcess(stream* s, int remain)
{
	int c,more;
	int ioff = _kernel_irqs_disabled();

	s->bgcount++;

	if (s->decoding == Stream_Decode_Finished)
		return;

	// copy buffers info so that we are not bothered by interrupts
	if (!ioff) _kernel_irqs_off();
	s->mp3b.start = s->mp3b.data + s->inb->real_start;
	if (s->mp3b.start < s->mp3b.data) s->mp3b.start = s->mp3b.data;
	if (s->mp3b.start > s->mp3b.last) s->mp3b.start = s->mp3b.last;
	s->mp3b.free  = s->mp3b.data + s->inb->real_free;
	if (s->mp3b.free < s->mp3b.data) s->mp3b.free = s->mp3b.data;
	if (s->mp3b.free > s->mp3b.last) s->mp3b.free = s->mp3b.last;
	bs_fixcount(&s->mp3b);
	s->sndb.start = s->sndb.data + s->outb->real_start;
	if (s->sndb.start < s->sndb.data) s->sndb.start = s->sndb.data;
	if (s->sndb.start > s->sndb.last) s->sndb.start = s->sndb.last;
	s->sndb.free  = s->sndb.data + s->outb->real_free;
	if (s->sndb.free < s->sndb.data) s->sndb.free = s->sndb.data;
	if (s->sndb.free > s->sndb.last) s->sndb.free = s->sndb.last;
	if (!ioff) _kernel_irqs_on();

/*
	if(!s->fr_ps.samplesPerFrame)
		c = 10;
	else
	{
		c = freesndbuf(&s->sndb);
		if (c > (s->mp3i.samplerate>>3))
			c = s->mp3i.samplerate >> 3;
		c = 1 + (c / s->fr_ps.samplesPerFrame);
	}
*/
	c = 0;
	more = 1000;

	do
	{
		more = one_piece(s, &c, more, remain) - 1;
	}
	while((more > 0) && (c < 10000));

	// update buffers
	if (!ioff) _kernel_irqs_off();
	s->inb->real_start = s->mp3b.start - s->mp3b.data;
	s->outb->real_free = s->sndb.free - s->sndb.data;
	if (!ioff) _kernel_irqs_on();
}

_kernel_oserror* swi_StreamInit(GlobHdr* g, _kernel_swi_regs* r)
{
	_kernel_oserror* e;
	stream* s;
	e = Stream_New(g, &s, r->r[0], NULL);
	if (e) return e;

	r->r[0] = (int) s->inb;
	r->r[1] = (int) s->outb;

	return NULL;
}

_kernel_oserror* swi_StreamAttach(GlobHdr* g, _kernel_swi_regs* r)
{
	_kernel_oserror* e;
	stream* s;

	e = Stream_New(g, &s, r->r[0], (dinbuf*) r->r[1]);
	if (e) return e;

	r->r[1] = (int) s->inb;
	r->r[2] = (int) s->outb;

	return NULL;
}

_kernel_oserror* swi_StreamQuit(GlobHdr* g, _kernel_swi_regs* r)
{
	_kernel_oserror* e;

	e = Stream_Destroy(g, r->r[0]);
	r->r[0] = 0;

	return NULL;
}

_kernel_oserror* swi_StreamInfo(GlobHdr* g, _kernel_swi_regs* r)
{
	_kernel_oserror* e;
	stream* s;

	e = Stream_GetHeader(g, r->r[0], &s);
	if (e) return e;

	if(s->mp3i.version < 0)
	{
		if (s->decoding == Stream_Decode_Finished)
			r->r[0] = 1;
		else
			r->r[0] = 0;
	}
	else
		r->r[0] = (int) &s->mp3i;

	return NULL;
}

_kernel_oserror* swi_StreamClear(GlobHdr* g, _kernel_swi_regs* r)
{
	_kernel_oserror* e;
	stream* s;

	e = Stream_GetHeader(g, r->r[0], &s);
	if (e) return e;

	if(r->r[1] & 1)     // Clear input buffer
		stream_clearinput(s, 1);
	if(r->r[1] & 2)     // Clear output buffer
		stream_clearoutput(s);
	if(r->r[1] & 4)     // Clear info
		stream_clearinfo(s);
	r->r[0] = 0;

	return NULL;
}

_kernel_oserror* swi_StreamProcess(GlobHdr* g, _kernel_swi_regs* r)
{
	_kernel_oserror* e;
	stream* s;

	e = Stream_GetHeader(g, r->r[0], &s);
	if (e) return e;

	s->flags |= Stream_Flag_ForegroundProcess;
	Stream_BackgroundProcess(s
		, (s->flags & Stream_Flag_LimitedProcess) ? r->r[1] : 0);

	return NULL;
}

_kernel_oserror* swi_Configure(GlobHdr* g, _kernel_swi_regs* r)
{
	switch(r->r[0])
	{
		case 0: // Input buffer size in KBs
		{
			if (r->r[1] != 0)
			{
				int s = 4;

				if (r->r[1] < s)
					r->r[1] = s;
				else if (r->r[1] > 512)
					r->r[1] = 512;

				// Round to power of 2
				while (r->r[1] > s)
					s <<= 1;

				mp3b_size = s << 10;
			}

			r->r[1] = mp3b_size >> 10;
		}
		break;
		case 1: // Output buffer size in KBs
		{
			if (r->r[1] != 0)
			{
				int s = 64;

				if (r->r[1] < s)
					r->r[1] = s;
				else if (r->r[1] > 4096)
					r->r[1] = 4096;

				// Round to power of 2
				while (r->r[1] > s)
					s <<= 1;

				sndb_size =s << 9;
			}

			r->r[1] = sndb_size >> 9;
		}
		break;
		case 256: // Force stream to limited foreground process
		{
			_kernel_oserror* e;
			stream* s;

			e = Stream_GetHeader(g, r->r[1], &s);
			if (e) return e;

			s->flags |= Stream_Flag_ForegroundProcess | Stream_Flag_LimitedProcess;
		}
		break;
		case -1: // Reset
		{
			Streams_CloseAll(g);
		}
		break;
		default:
			return &err_config;
	}

	return NULL;
}
