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

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

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

typedef struct
{
	char     id[4];
	uint32_t size;
	char     wavefmt[8];
	uint32_t fmtsize;
	uint16_t format;
	uint16_t channels;
	uint32_t samplerate;
	uint32_t bytespersec;
	uint16_t blocksize;
	uint16_t bitspersample;
	char     data[4];
	uint32_t datasize;
} File_Hdr;

typedef struct Converter
{
	const char* inf;
	const char* outf;
	FILE*       bitf;
	dinbuf*     bitb;
	FILE*       wavf;
	doutbuf*    wavb;
	File_Hdr    wavh;
	int         handle;
	stream*     s;
} converter;

static int inwavbuf(doutbuf* wavb, int channels)
{
	int n;

	n = wavb->real_free - wavb->real_start;
	if (n < 0)
		n += wavb->size - (wavb->size % channels);

	return n;
}

static void converter_WriteInitialHeader(converter* cvrt, int samplerate, int channels)
{
	strncpy(cvrt->wavh.id, "RIFF", 4);
	cvrt->wavh.size = sizeof(File_Hdr) - 8;
	strncpy(cvrt->wavh.wavefmt, "WAVEfmt ", 8);
	cvrt->wavh.fmtsize = 16;
	cvrt->wavh.format = 1; //PCM
	cvrt->wavh.channels = channels;
	cvrt->wavh.samplerate = samplerate;
	cvrt->wavh.bytespersec = cvrt->wavh.samplerate*cvrt->wavh.channels*2;
	cvrt->wavh.blocksize = cvrt->wavh.channels*2;
	cvrt->wavh.bitspersample = 16;
	strncpy(cvrt->wavh.data, "data", 4);
	cvrt->wavh.datasize = 0;

	if (cvrt->wavf)
	{
		fseek(cvrt->wavf, 0, SEEK_SET);
		fwrite(&cvrt->wavh, 1, sizeof(cvrt->wavh), cvrt->wavf);
	}
}

static void converter_WriteUpdatedHeader(converter* cvrt)
{
	cvrt->wavh.size = sizeof(File_Hdr) - 8 + cvrt->wavh.datasize;

	if (cvrt->wavf)
	{
		fseek(cvrt->wavf, 0, SEEK_SET);
		fwrite(&cvrt->wavh, 1, sizeof(cvrt->wavh), cvrt->wavf);
	}
}

static int converter_WriteWaveBuffer(converter* cvrt, int blocksize, int channels)
{
	int n;
	int size = inwavbuf(cvrt->wavb, channels);

	if (cvrt->wavb->finished)
	{
		if (cvrt->wavb->real_start == cvrt->wavb->real_free)
			return 0;
	}
	else if (size < blocksize)
		return 0;

	if (size > blocksize) size = blocksize;

	// Write block filled part from start to free
	if (cvrt->wavf)
	{
		n = fwrite(&cvrt->wavb->data[cvrt->wavb->real_start]
				, 2
				, size
				, cvrt->wavf);
	}
	else
		n = size;

	cvrt->wavb->real_start += n;
	if (cvrt->wavb->real_start >= (cvrt->wavb->size - (cvrt->wavb->size % channels)))
		cvrt->wavb->real_start = 0;

	n *= 2;

	cvrt->wavh.datasize += n;

	// Write problem, disc full?
	if (n < 2*size)
		return -1;

	return n;
}

static int converter_ReadBlock(converter* cvrt, int blksize)
{
	int start = cvrt->bitb->real_start; // Because value may change in the background
	int n, count = 0;

	n = start - cvrt->bitb->real_free;
	if (n <= 0)
		n += cvrt->bitb->size;

	if(feof(cvrt->bitf))
	{
		// If end of file reached, mark the input buffer as completed
		cvrt->bitb->finished = 1;
	}
	else if(n > blksize)
	{
		// Free part may actually wrap from end to beginning of the buffer
		if (blksize > (cvrt->bitb->size - cvrt->bitb->real_free))
			blksize = cvrt->bitb->size - cvrt->bitb->real_free;
		// If enough free space inf buffer, read a block of data
		// but beware of never filling the buffer completely
		// as it would be interpreted as an empty buffer
		count = fread(&cvrt->bitb->data[cvrt->bitb->real_free], 1, blksize, cvrt->bitf);
		// Update position of free pointer but use an intermediate variable
		// to avoid temporary corruption of free pointer (AudioMPEG
		// is working in the background and may work on the buffer
		// in the middle of the computation of the new free position
		n = count + cvrt->bitb->real_free;
		if (n >= cvrt->bitb->size)
			n = 0;
		cvrt->bitb->real_free = n;
	}

	Stream_BackgroundProcess(cvrt->s, 0);

	return count;
}

static int Keyboard_EscPressed(void)
{
	int pa, pb;

	_swix(OS_Byte, _INR(0,2)|_OUTR(1,2), 129, 0, 0, &pa, &pb);
	if (!pb)
	{
		if (pa == 27)
			return 1;
	}

	return 0;
}

int main(int argc, char** argv)
{
	int t, tin, tout;
	_kernel_swi_regs r;
	const _kernel_oserror* e = NULL;
	const char* Program_Name = argv[0];
	const char* p;
	converter Cvrt;
	int bOk;
	int verbose = 7;
	int oblock = 4096*16;
	int blksize = 4096*4;
	int xcvb = 0;
	int readb = 0;
	int n;
	stream_info* info = NULL;

	e = Module_Initialize(NULL, 0, NULL);
	if (e)
	{
		fprintf(stderr, "%s\n", e->errmess);
		return EXIT_FAILURE;
	}

	p = strrchr(Program_Name, ':');
	if (p) Program_Name = p + 1;
	p = strrchr(Program_Name, '.');
	if (p) Program_Name = p + 1;

	memset(&Cvrt, 0, sizeof(Cvrt));
	Cvrt.handle = -1;

	// Disable Escape
	_swix(OS_Byte, _INR(0,2), 0xe5, 1, 0);

	for (n = 1, bOk = 1; (n < argc) && bOk; n++)
	{
		char* p = argv[n];

		if (*p == '-')
		{
			if (p[1] == 'v')
			{
				verbose = atoi(p + 2);
				if (verbose < 0) verbose = 0;
			}
			else
				bOk = 0;
		}
		else if (!Cvrt.inf)
		{
			Cvrt.inf = p;
		}
		else if (!Cvrt.outf)
		{
			Cvrt.outf = p;
		}
		else
			bOk = 0;
	}

	LogVerbosity(verbose);

	// False loop for error catching
	while (!e)
	{
		if (!bOk || !Cvrt.inf)
		{
			fprintf(stderr, "%s v%s (%s) is an AC3 to WAVE convertor.\n"
			      , Program_Name, Module_VersionString, Module_Date);
			fprintf(stderr, "Syntax: %s [options] <infile> [<outfile>]\n", Program_Name);
			fprintf(stderr, "Options:\n");
			fprintf(stderr, "-v<n> Verbosity: 0 Silent, 1 Input info, 2 Progress indicator\n");
			break;
		}

		Cvrt.bitf = fopen(Cvrt.inf, "r");

		if (!Cvrt.bitf)
		{
			fprintf(stderr, "Could not open input file %s\n", Cvrt.inf);
			break;
		}

		// Set Input buffer size
		r.r[0] = 1;
		r.r[1] = 256;
		e = swi_Configure(Glb, &r);
		if (e) break;

		r.r[1] = 0;
		e = swi_StreamOpen(Glb, &r);
		if (e) break;

		Cvrt.handle = r.r[0];
		Cvrt.bitb = (dinbuf*) r.r[1];
		Cvrt.wavb = (doutbuf*) r.r[2];
		e = Stream_GetHeader(Glb, Cvrt.handle, &Cvrt.s);
		if (e) break;

		// Wait (with a timeout) till it has extracted playing info (samplerate, channels, ...)
		t = clock(); tout = t + 10*CLK_TCK;

		while ((clock() < tout) && !info && !e)
		{
			readb += converter_ReadBlock(&Cvrt, blksize);

			// We only have to wait till it's is ready
			r.r[0] = Cvrt.handle;
			e = swi_StreamInfo(Glb, &r);
			if (e) break;
			info = (stream_info*) r.r[1];
		}

		if (e) break;

		// Play till end of source
		t = tin = clock();
		if (info)
		{
			if (Cvrt.outf)
			{
				Cvrt.wavf = fopen(Cvrt.outf, "w");
				if (!Cvrt.wavf)
				{
					fprintf(stderr, "Could not open output file %s\n", Cvrt.outf);
					break;
				}
			}

			if (verbose)
			{
				printf("File is AC3 (%d channels, %dHz, %dkbit/s)\n"
					, info->channels
					, info->samplerate
					, info->bitrate);
			}

			converter_WriteInitialHeader(&Cvrt, info->samplerate, info->channels);

			// Loop till the stream is completely played
			while(!Cvrt.wavb->finished || inwavbuf(Cvrt.wavb, info->channels) != 0)
			{
				readb += converter_ReadBlock(&Cvrt, blksize);

				n = converter_WriteWaveBuffer(&Cvrt, oblock, info->channels);
				if (n < 0)
				{
					fprintf(stderr, "\nProblem writing to output file\n");
					bOk = 0;
					break;
				}

				xcvb += n;

				// Show current recording info
				if ((clock() - t) > 100)
				{
					if (Keyboard_EscPressed())
					{
						printf("\nStop requested by user\n");
						break;
					}
					if (verbose > 1) printf("\r%d KB -> %d KB", readb >> 10, xcvb >> 10);

					t = clock();
				}
			}
			if (verbose > 1) printf("\n");

			t = clock() - tin;

			if (verbose)
			{
				if (bOk)
					printf("Song Duration %d cs\n"
					     , Cvrt.wavh.datasize / (Cvrt.wavh.bytespersec/100));
				printf("Decoded in %d cs\n", t);
			}
		}
		else
		{
			fprintf(stderr, "Input file doesn't appear to be a valid stream\n");
		}

		// End of decoding
		break;
	}

	if (e) fprintf(stderr, "Error: %s\n", e->errmess);

	converter_WriteUpdatedHeader(&Cvrt);

	if (Cvrt.bitf)
		fclose(Cvrt.bitf);
	if (Cvrt.wavf)
	{
		fclose(Cvrt.wavf);
		// Give it type WAVE
		_swix(8, _INR(0,2), 18, Cvrt.outf, 0xfb1);
	}

	if (Cvrt.handle != -1)
	{
		r.r[0] = Cvrt.handle;
		e = swi_StreamClose(Glb, &r);
		if (e) fprintf(stderr, "%s\n", e->errmess);
	}

	Module_Finalize(0, 0, Glb->pw);

	return (bOk) ? EXIT_SUCCESS : EXIT_FAILURE;
}
