#include "GlobHdr.h"
#include "ICodec.h"
#include "IWriter.h"
#include "Format_WAVE.h"

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

#define OUT_SIZE (1<<14)

typedef struct
{
	unsigned int handle;
	char*        filename;
	int          volume;
	unsigned int filepos;
	// header to write
	struct
	{
		wave_t       wave;
		wav_tag_t    data;
		int32_t      datasize;
	} hdr;
	uint8_t buffer[OUT_SIZE];
} ToData;

/*-----------------
 * ToWAVEFile_Init
 */

static const _kernel_oserror* ToWAVEFile_Initialize(IStream* s, const _kernel_swi_regs* r)
{
	ToData* od;
	const _kernel_oserror* e;

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

	s->writer.data = od;
	memset(od, 0, sizeof(*od));
	od->volume = s->writer.volume;

	if (r->r[0])
	{
		e = IStream_AllocString(s, &od->filename, (const char*) r->r[0]);
		if (e) return e;

		// Open file in extend mode
		e = _swix(OS_Find, _INR(0,1)|_OUT(0), 0xcf, r->r[0], &od->handle);
		if (e)
		{
			// Create file
			e = _swix(OS_Find, _INR(0,1)|_OUT(0), 0x8f, r->r[0], &od->handle);
		}
		else
		{
			if (r->r[1])
			{
				// Move to end of file
				e = _swix(OS_Args, _INR(0,1)|_OUT(2), 2, od->handle, &od->filepos);
				if (e) return e;
				e = _swix(OS_Args, _INR(0,2), 1, od->handle, od->filepos);
				if (e) return e;
			}
			else
			{
				// Truncate file
				e = _swix(OS_Args, _INR(0,2), 3, od->handle, 0);
				if (e) return e;
			}
		}
	}

	return e;
}

/*-----------------
 * ToWAVEFile_Open
 */

static const _kernel_oserror* ToWAVEFile_Open(IStream* s)
{
	ToData* od = s->writer.data;

	IGNORE(od);

	return NULL;
}

/*----------------------
 * ToWAVEFile_SetParams
 */

static const _kernel_oserror* ToWAVEFile_SetParams(IStream* s)
{
	ToData* od = s->writer.data;
	const _kernel_oserror* e = NULL;

	// Need to fix the previous header
	if (od->hdr.datasize > 0)
	{
		od->hdr.wave.size += od->hdr.datasize;

		if (od->filename)
		{
			// Move to previous header
			e = _swix(OS_Args, _INR(0,2), 1, od->handle, od->filepos);
			if (e) return e;

			// Write to file
			e = _swix(OS_GBPB, _INR(0,3), 2, od->handle, &od->hdr, sizeof(od->hdr));
			if (e) return e;
		}
	}

	od->hdr.wave.riff.i  = TagRIFF.i;
	od->hdr.wave.size    = sizeof(od->hdr) - 8;
	od->hdr.wave.tag.i   = TagWAVE.i;
	od->hdr.wave.fmt.i         = Tagfmt.i;
	od->hdr.wave.fmtsize       = 16;
	od->hdr.wave.format        = 1;
	od->hdr.wave.channels      = s->info.Channels;
	od->hdr.wave.samplerate    = s->info.SampleRate;
	od->hdr.wave.bytespersec   = 2 * s->info.Channels * s->info.SampleRate;
	od->hdr.wave.blocksize     = 2 * s->info.Channels;
	od->hdr.wave.bitspersample = 16;
	od->hdr.data.i   = Tagdata.i;
	od->hdr.datasize = 0;

	if (od->filename)
	{
		// Move to end of file
		e = _swix(OS_Args, _INR(0,1)|_OUT(2), 2, od->handle, &od->filepos);
		if (e) return e;
		e = _swix(OS_Args, _INR(0,2), 1, od->handle, od->filepos);
		if (e) return e;

		// Write to file
		e = _swix(OS_GBPB, _INR(0,3), 2, od->handle, &od->hdr, sizeof(od->hdr));
	}

	return e;
}

/*-----------------
 * ToWAVEFile_Fill
 */

static const _kernel_oserror* ToWAVEFile_Fill(IStream* s)
{
	ToData* od = s->writer.data;
	const _kernel_oserror* e = NULL;
	MixStream* stream = &s->ChannelsArray[0];
	int remain, i;
	uint64_t duration;

	// Check if song is playing
	if ((s->status & (swrk_status_playing|swrk_status_ready|swrk_status_buffering))
	    != (swrk_status_playing|swrk_status_ready))
		return NULL;

	remain = s->codec.fn->Lister(s);

	// We will consume all of it
	duration = remain;
	duration *= UINT64_C(256000000);
	duration /= stream->frequency;
	s->info.PlayingTime += duration;

	if (stream->flags & mixstream_flag_16bit)
		od->hdr.datasize += 2 * remain * s->info.Channels;
	else
		od->hdr.datasize += remain * s->info.Channels;

	if (od->filename)
	{
		// Need to interleave output
		while (remain)
		{
			if (stream->flags & mixstream_flag_16bit)
			{
				uint16_t* p = (uint16_t*) od->buffer;
				uint16_t* p_end = (uint16_t*) &od->buffer[OUT_SIZE];
				int step = (stream->flags & mixstream_flag_interlaced)
				         ? s->info.Channels
				         : 1;

				p_end = p + (((p_end - p) / s->info.Channels) * s->info.Channels);
				p_end -= s->info.Channels - 1;

				while (remain && (p < p_end))
				{
					for (i = 0; i < s->info.Channels; i++)
					{
						uint16_t* r = (uint16_t*) s->ChannelsArray[i].smp_ptr;
						*p++ = r[stream->pos*step];
					}

					stream->pos++;
					if (stream->pos >= stream->smp_lend)
						stream->pos = 0;
					remain--;
				}

				// Write to file
				e = _swix( OS_GBPB, _INR(0,3)
				         , 2, od->handle
			    	     , od->buffer, ((uint8_t*) p) - od->buffer
			        	 );
				if (e) return e;
			}
			else
			{
				uint8_t* p = od->buffer;
				uint8_t* p_end = &od->buffer[OUT_SIZE];
				int step = (stream->flags & mixstream_flag_interlaced)
				         ? s->info.Channels
				         : 1;

				p_end = p + (((p_end - p) / s->info.Channels) * s->info.Channels);
				p_end -= s->info.Channels - 1;

				while (remain && (p < p_end))
				{
					for (i = 0; i < s->info.Channels; i++)
					{
						uint8_t* r = (uint8_t*) s->ChannelsArray[i].smp_ptr;
						*p++ = r[stream->pos*step];
					}

					stream->pos++;
					if (stream->pos >= stream->smp_lend)
						stream->pos = 0;
					remain--;
				}

				// Write to file
				e = _swix( OS_GBPB, _INR(0,3)
				         , 2, od->handle
				         , od->buffer, p - od->buffer
				         );
				if (e) return e;
			}
		}
	}
	else
	{
		stream->pos += remain;
		if (stream->pos >= stream->smp_lend)
			stream->pos -= stream->smp_lend;
	}

	// Update the other channels
	for (i = 1; i < s->info.Channels; i++)
		s->ChannelsArray[i].pos = stream->pos;

	s->status |= swrk_status_outputdone;

	// If completely decoded and nothing more to play, set as played
	if (s->status & swrk_status_decoded)
		s->status |= swrk_status_played;
	else
	{
		// If sub-stream decoded, prepare for next sub-stream
		if (s->status & swrk_status_sub_decoded)
			s->status |= swrk_status_sub_init;

		s->status |= swrk_status_buffering;
	}

	return NULL;
}

/*-------------------
 * ToWAVEFile_Volume
 */

static const _kernel_oserror* ToWAVEFile_Volume(IStream* s, int* volume)
{
	ToData* od = s->writer.data;

	if (*volume >= 0)
		od->volume = *volume;

	*volume = od->volume;

	return NULL;
}

/*------------------
 * ToWAVEFile_Close
 */

static const _kernel_oserror* ToWAVEFile_Close(IStream* s)
{
	ToData* od = s->writer.data;
	const _kernel_oserror* e = NULL;

	IGNORE(od);

	// Need to fix the previous header
	if (od->hdr.datasize > 0)
	{
		od->hdr.wave.size += od->hdr.datasize;

		if (od->filename)
		{
			// Move to previous header
			e = _swix(OS_Args, _INR(0,2), 1, od->handle, od->filepos);
			if (e) return e;

			// Write to file
			e = _swix(OS_GBPB, _INR(0,3), 2, od->handle, &od->hdr, sizeof(od->hdr));
			od->hdr.datasize = 0;
		}
	}

	return e;
}

/*---------------------
 * ToWAVEFile_Finalize
 */

static const _kernel_oserror* ToWAVEFile_Finalize(IStream* s)
{
	ToData* od = s->writer.data;
	const _kernel_oserror* e = NULL;

	if (!od) return NULL;

	if (od->handle)
	{
		_swi(OS_Find, _INR(0,1), 0, od->handle);
		od->handle = 0;
		e = _swix(OS_File, _INR(0,2), 18, od->filename, 0xfb1);
	}

	IStream_Free(s, (void**) &od->filename);
	IStream_Free(s, &s->writer.data);

	return e;
}

IWriter ToWAVEFile =
{
	  ToWAVEFile_Initialize
	, ToWAVEFile_Volume
	, ToWAVEFile_Open
	, ToWAVEFile_SetParams
	, ToWAVEFile_Fill
	, ToWAVEFile_Close
	, ToWAVEFile_Finalize
};
