#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include "kernel.h"
#include "swis.h"

#include "Mem.h"
#include "Header.h"
#include "FSong.h"
#include "FSysLog.h"
#include "GlobHdr.h"
#include "TimLib:Seq.h"

/*
 * Big warning: SoundDMA 16-bit filler routines store right channel first.
 *
 * This means BALANCE and STEREO SEPERATION must be inverted.
 */

#define BUFF_SIZE (4*1024) // May not exceed this value cf fixed size of Buffer32 in Seq

extern void ASM_SaveStackLimit(void);

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*       wavf;
	uint8_t*    buffer;
	File_Hdr    wavh;
	int         handle;
	int         frequency;
	int         section;
	int         sections;
	int         passes;
	int         balance;
	int         stereo;
	int         flags;
	int         compatibility;
} converter;

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 void converter_WriteBlock(converter* cvrt)
{
	cvrt->wavh.datasize += BUFF_SIZE;
	if (cvrt->wavf) fwrite(cvrt->buffer, 1, BUFF_SIZE, cvrt->wavf);
}

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)
{
	_kernel_swi_regs r;
	const _kernel_oserror* e = NULL;
	const char* Program_Name = argv[0];
	const char* p;
	converter Cvrt;
	int n, t, tin;
	bool bOk;
	bool depack = false;
	uint32_t status;
	int th, tm, ts, time;
	int th2 = 0, tm2 = 0, ts2 = 0, time2;

	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;
	Cvrt.buffer = malloc(BUFF_SIZE);
	Cvrt.balance = 128;
	Cvrt.stereo = -256;

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

	int verbose = 2;

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

		if (*p == '-')
		{
			switch(p[1])
			{
				case 'b':
				{
					Cvrt.balance = atoi(p + 2);
					if ((Cvrt.balance < 0) || (Cvrt.balance > 255))
					{
						fprintf(stderr, "Invalid balance, range is [0, 255]\n");
						bOk = 0;
					}
					if (Cvrt.balance != 128)
						Cvrt.balance = 255 - Cvrt.balance;
				}
				break;
				case 'c':
				{
					Cvrt.compatibility = atoi(p + 2);
				}
				break;
				case 'd':
				{
					depack = true;
				}
				break;
				case 'i':
				{
					for (p = p + 2; *p; p++)
					{
						switch(*p)
						{
							case 'l': Cvrt.flags |= 2; break;
							case 'e': Cvrt.flags |= 4; break;
							case 'c': Cvrt.flags |= 16; break;
							default:
								fprintf(stderr, "Invalid ignore flag\n");
								bOk = 0;
						}
					}
				}
				break;
				case 'f':
				{
					Cvrt.frequency = atoi(p + 2);
					if ((Cvrt.frequency < 5000) || (Cvrt.frequency > 192000))
					{
						fprintf(stderr, "Invalid frequency, range is [5000, 192000]\n");
						bOk = 0;
					}
				}
				break;
				case 'r':
					Cvrt.passes = 1;
				break;
				case 's':
					Cvrt.section = atoi(p + 2);
				break;
				case 't':
				{
					if (!strcmp(p + 1, "test"))
					{
						Cvrt.frequency = 1; // no output
					}
					else
					{
						Cvrt.stereo = atoi(p + 2);
						if ((Cvrt.stereo < -1024) || (Cvrt.stereo > 1024))
						{
							fprintf(stderr, "Invalid stereo seperation, range is [-1024, 1024]\n");
							bOk = 0;
						}
						Cvrt.stereo = -Cvrt.stereo;
					}
				}
				break;
				case 'v':
					verbose = atoi(p + 2);
					if (verbose < 0) verbose = 0;
				break;
				default:
					bOk = 0;
			}
		}
		else if (!Cvrt.inf)
		{
			Cvrt.inf = p;
		}
		else if (!Cvrt.outf)
		{
			Cvrt.outf = p;
		}
		else
			bOk = 0;
	}

	SysLog_SetLogLevel(verbose);


	// False loop for error catching
	while (!e)
	{
		if (!bOk || !Cvrt.inf || (!depack && !Cvrt.frequency) || (depack && !Cvrt.outf))
		{
			fprintf(stderr, "%s v%s (%s) is a Tracker to WAVE convertor.\n"
		    	  , Program_Name, Module_VersionString, Module_Date);
			fprintf(stderr, "Syntax: %s [options] -f<n> <infile> [<outfile>]\n", Program_Name);
			fprintf(stderr, "Options:\n");
			fprintf(stderr, "  -f<n> Mandatory output frequency between 5000 and 192000 Hz\n");
			fprintf(stderr, "  -v<n> Verbosity: 0 Silent, 1 Input info, 2 Progress indicator\n");
			fprintf(stderr, "                   3...255, finer conversion logging\n");
			fprintf(stderr, "  -s<n> Section to play [0, x[\n");
			fprintf(stderr, "  -r    Perform a first pass to normalize output\n");
			fprintf(stderr, "  -b<n> Balance [left 0, ..., centre 128, ..., right 255]\n");
			fprintf(stderr, "  -t<n> Stereo separation [-1024, 1024], 256 is default, - inverts stereo\n");
			fprintf(stderr, "  -c<n> Compatibility (see SWI TimPlayer_SongLoad2)\n");
			fprintf(stderr, "  -i<flags>\n");
			fprintf(stderr, "        l  Ignore loops in last pattern\n");
			fprintf(stderr, "        e  Ignore end of sequence markers\n");
			fprintf(stderr, "        c  Do NOT ignore channel muting flags\n");
			fprintf(stderr, "\n");
			fprintf(stderr, "%s can also be used to depack Tracker files\n", Program_Name);
			fprintf(stderr, "Syntax: %s -d <infile> [<outfile>]\n", Program_Name);
			break;
		}

		if (depack)
		{
			r.r[0] = (int) Cvrt.outf;
			r.r[1] = (int) Cvrt.inf;
			r.r[2] = 0;
			r.r[3] = 0;
			e = swi_Song_Decompress(Glb, &r);
			bOk = (e != NULL);

			break;
		}

		// User options
		r.r[0] = 2;
		r.r[1] = Cvrt.balance;
		CSeq_Configure(Glb, &r);
		r.r[0] = 3;
		r.r[1] = Cvrt.stereo;
		CSeq_Configure(Glb, &r);

		// Maximise notes
		r.r[0] = 13;
		r.r[1] = 128;
		CSeq_Configure(Glb, &r);

		// Get max quality
		r.r[0] = 1;
		r.r[1] = 1;
		CSeq_Configure(Glb, &r);
		r.r[0] = 8;
		r.r[1] = 1;
		CSeq_Configure(Glb, &r);
		r.r[0] = 256;
		r.r[1] = 1;
		swi_Configure_Songs(Glb, &r);

		r.r[0] = 8 | (Cvrt.compatibility & 7);
		r.r[1] = (int) Cvrt.inf;
		r.r[4] = Cvrt.flags;
		e = swi_Song_Load2(Glb, &r);
		if (e) break;
		Cvrt.handle = r.r[0];

		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)
		{
			const char* type;
			int channels, sections;

			r.r[0] = Cvrt.handle;
			e = swi_Song_Texts(Glb, &r);
			if (e) break;
			type = (const char*) r.r[3];

			r.r[0] = Cvrt.handle;
			e = swi_Song_Info(Glb, &r);
			if (e) break;
			channels = r.r[1];

			r.r[0] = Cvrt.handle;
			r.r[1] = -1;
			e = swi_Song_SectionInfo(Glb, &r);
			if (e) break;
			sections = 1 + r.r[1];
			time = r.r[4];

			printf("File is %s (%d channels, %d sections)\n", type, channels, sections);

			ts = time /1000;
			tm = ts / 60; ts = ts % 60;
			th = tm / 60; tm = tm % 60;
			printf("Total Time: % 3d:%02d:%02d\n", th, tm, ts);
			th2 = th; tm2 = tm; ts2 = ts;

			if ((Cvrt.section > 0)
			||  ((Cvrt.section == 0) && Cvrt.sections > 1))
			{
				r.r[0] = Cvrt.handle;
				r.r[1] = Cvrt.section;
				e = swi_Song_SectionInfo(Glb, &r);
				if (e) break;
				time = r.r[3];
				time2 = r.r[4];

				ts = time /1000;
				tm = ts / 60; ts = ts % 60;
				th = tm / 60; tm = tm % 60;
				ts2 = time2 /1000;
				tm2 = ts2 / 60; ts2 = ts2 % 60;
				th2 = tm2 / 60; tm2 = tm2 % 60;
				printf("Section %d starts at % 3d:%02d:%02d and ends at % 3d:%02d:%02d\n"
					, Cvrt.section, th, tm, ts, th2, tm2, ts2);
			}
		}

		if (Cvrt.frequency != 1)
		{
			// We don't want to play file normally but instead use the WAV filler
			r.r[0] = 254;
			r.r[1] = 1;
			CSeq_Configure(Glb, &r);

			// Don't loop when reaching end of file
			r.r[0] = Cvrt.handle;
			r.r[1] = 0x10;
			r.r[2] = 0x10;
			e = swi_Song_Status(Glb, &r);
			if (e) break;

			t = tin = clock();

			converter_WriteInitialHeader(&Cvrt, Cvrt.frequency, 2);

			for (int pass = Cvrt.passes; pass >= 0; pass--)
			{
				if (verbose > 1)
				{
					if (pass)
						printf("\nCalculating maximal amplitude\n");
					else
						printf("\nWriting WAVE data\n");
				}
				// Start playing
				r.r[0] = Cvrt.handle;
				e = swi_Song_Play(Glb, &r);
				if (e) break;

		        // Move to section
				r.r[0] = Cvrt.handle;
				r.r[1] = Cvrt.section;
				e = swi_Song_SectionInfo(Glb, &r);
				if (e) break;

				r.r[0] = Cvrt.handle;
				r.r[1] = r.r[2];
				e = swi_Song_Position(Glb, &r);
				if (e) break;

				status = 1;

				// Loop till not played completely
				while (!(status & 2))
				{
					r.r[1] = (int) Cvrt.buffer;
					r.r[2] = r.r[1] + BUFF_SIZE;
					r.r[3] = 0;
					r.r[4] = Cvrt.frequency << 10;
					ASM_SaveStackLimit();
					CSeq_Filler_WAV(Glb, &r);

					// Only write data on last pass
					if (pass == 0)
						converter_WriteBlock(&Cvrt);

					// Checking status
					r.r[0] = Cvrt.handle;
					r.r[1] = 0;
					r.r[2] = 0;
					e = swi_Song_Status(Glb, &r);
					if (e) break;
					status = r.r[1];

					// Show current playing time
					if (((clock() - t)> 100) || (status & 2))
					{
						if (Keyboard_EscPressed())
						{
							printf("\nStop requested by user\n");
							pass = -1;
							break;
						}

						if (verbose > 1)
						{
							if (status & 2)
							{
								printf("\rTime: % 3d:%02d:%02d", th2, tm2, ts2);
							}
							else
							{
								r.r[0] = Cvrt.handle;
								r.r[1] = -1;
								e = swi_Song_Position(Glb, &r);
								if (e) break;

								ts = r.r[5] /1000;
								tm = ts / 60; ts = ts % 60;
								th = tm / 60; tm = tm % 60;
								printf("\rTime: % 3d:%02d:%02d", th, tm, ts);
							}
						}

						t = clock();
					}
				}

				if ((Cvrt.passes > 0) && (pass > 0))
				{
					// Read Max AGC to adjust volume for next pass
					r.r[0] = 255;
					CSeq_Configure(Glb, &r);

					r.r[0] = Cvrt.handle;
					r.r[1] = (256 * r.r[3]) / 100;
					if (r.r[1] > 1024) r.r[1] = 1024;
					e = swi_Song_Volume(Glb, &r);
					if (e) break;

					if (verbose > 1)
						printf("\rAdjusting volume to %d%%", (100 * r.r[1])/256);
				}

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

			if (verbose)
				printf("Decoded in %d cs\n", clock() - tin);

			r.r[0] = Cvrt.handle;
			e = swi_Song_Stop(Glb, &r);
			if (e) break;
		}

		// End of decoding
		break;
	}

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

	converter_WriteUpdatedHeader(&Cvrt);

	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_Song_Unload(Glb, &r);
		if (e) fprintf(stderr, "%s\n", e->errmess);
	}

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

	return (bOk) ? EXIT_SUCCESS : EXIT_FAILURE;
}
