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

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

extern void Seq_SkipProcessStream(MixStream*, int step /* 2^40/FMix */, int samples /* at FMix */);

typedef struct Converter
{
	const char* inf;
	const char* outf;
	FILE*       outh;
	int         handle;
	int         section;
	int         sections;
	int         flags;
	int         compatibility;
	int         channels;
	bool        usechannel[64];
	int         vars;
	int         varlist[256];
} converter;

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;
	uint32_t status;
	int th = 0, tm = 0, ts = 0, tt, time;
	int th2 = 0, tm2 = 0, ts2 = 0, time2;
	ISong* pSong;

	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;
	int verbose = 2;

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

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

		if (*p == '-')
		{
			switch(p[1])
			{
				case 'c':
				{
					Cvrt.compatibility = atoi(p + 2);
				}
				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 'l':
				{
					p += 2;
					const char* pv;
					do
					{
						Cvrt.varlist[Cvrt.vars] = atoi(p);
						Cvrt.vars++;
						pv = strchr(p, ',');
						p = pv + 1;
					} while (pv);
				}
				break;
				case 's':
					Cvrt.section = atoi(p + 2);
				break;
				case 't':
				{
					p += 2;
					const char* pv;
					do
					{
						int ch = atoi(p);
						if ((ch < 0) || (ch >= 64))
						{
							fprintf(stderr, "Invalid ignore flag\n");
							bOk = 0;
							break;
						}
						Cvrt.usechannel[ch] = true;
						Cvrt.channels++;
						pv = strchr(p, ',');
						p = pv + 1;
					} while (pv);
				}
				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)
		{
			fprintf(stderr, "%s v%s (%s) dumps for every frame in Tracker\n"
			      , Program_Name, Module_VersionString, Module_Date);
			fprintf(stderr, "the notes/channels params to a file.\n");
			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");
			fprintf(stderr, "                   3...255, finer conversion logging\n");
			fprintf(stderr, "  -s<n> Section to play [0, x[\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, "  -t<a,...,n> Dump info only for list channels <n>\n");
			fprintf(stderr, "  -l<a,...,n> List of channel variables to dump instead on notes\n");
			break;
		}

		// No ramping
		r.r[0] = 256;
		r.r[1] = 0;
		swi_Configure_Songs(Glb, &r);

		// Maximise notes
		r.r[0] = 13;
		r.r[1] = 128;
		CSeq_Configure(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];

		Song_GetSongHeader(Glb, Cvrt.handle, &pSong);

		if (Cvrt.outf)
		{
			Cvrt.outh = fopen(Cvrt.outf, "w");
			if (!Cvrt.outh)
			{
				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);
			}
		}

		// 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();

		// 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;
		if (Cvrt.channels == 0)
		{
			for (int ch = 0; ch < pSong->Hdr.Channels; ch++)
				Cvrt.usechannel[ch] = true;
		}

		// Loop till not played completely
		while (true)
		{
			bool usesep = false;

			// Get duration of frame in 1/256 us
			int frameduration = Song_Changer(pSong, 0x7fffffff);

			// 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");
					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();
			}

			// Done?
			if (status & 2)
				break;

			if (Cvrt.outh)
			{
				// Dump position info
				r.r[0] = Cvrt.handle;
				r.r[1] = -1;
				e = swi_Song_Position(Glb, &r);
				if (e) break;
				tt = r.r[5] % 1000;
				ts = r.r[5] / 1000;
				tm = ts / 60; ts = ts % 60;
				th = tm / 60; tm = tm % 60;
				fprintf(Cvrt.outh, "Time: %d:%02d:%02d.%03d ", th, tm, ts, tt);
				fprintf(Cvrt.outh, "Pos: %03d:%03d:%02d  ", r.r[1], r.r[2], pSong->FrameExtCount);
			}

			// Dump info for all active notes
			r.r[0] = frameduration;
			Song_Lister(&r, pSong);

			for (int i = 0; i < pSong->Notes.ActiveNotes; i++)
			{
				Note* pNote = pSong->Notes.ActiveNotesArray[i];
				if (Cvrt.outh && (Cvrt.vars == 0) && Cvrt.usechannel[pNote->pchannel->id])
				{
					if (usesep) fprintf(Cvrt.outh, " | ");
					fprintf
						( Cvrt.outh, "C%02d I%02X,%02X,%X % 4XV % 3XP % 7dHz"
						, pNote->pchannel->id
						, pNote->inst_id, pNote->inst_note, pNote->stream.flags
						, pNote->stream.volume, pNote->stream.panning, pNote->stream.frequency
						);

					usesep = true;
				}
				Seq_SkipProcessStream(&pNote->stream, 1099512 /* 2^40/1000000 */, frameduration >> 8);
			}

			if (Cvrt.outh)
			{
				if (Cvrt.vars > 0)
				{
					for (int ch = 0; ch < pSong->Hdr.Channels; ch++)
					{
						if (Cvrt.usechannel[ch])
						{
							fprintf(Cvrt.outh, "C%02d", ch);
							for (int v = 0; v < Cvrt.vars; v++)
							{
								r.r[0] = Cvrt.handle;
								r.r[1] = ch;
								r.r[2] = Cvrt.varlist[v];
								e = swi_Channel_Variable(Glb, &r);
								if (e) break;
								if (r.r[3] == 0)
									fprintf(Cvrt.outh, "{%02d,       -}", r.r[2]);
								else
									fprintf(Cvrt.outh, "{%02d,% 8x}", r.r[2], r.r[4]);
							}
						}
					}
				}
				fprintf(Cvrt.outh, "\n");
			}
		}
		if (verbose > 1) printf("\n");

		if (!Cvrt.outh)
			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);

	if (Cvrt.outh)
	{
		fclose(Cvrt.outh);
		// Give it type Text
		_swix(8, _INR(0,2), 18, Cvrt.outf, 0xfff);
	}

	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;
}
