#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "kernel.h"
#include "bool.h"
#include "Event.h"
#include "Exception.h"
#include "Window.h"
#include "Task.h"

#include "DCDswi.h"

#define TASKNAME "PowerBars"
const char* TaskName = TASKNAME;
int block[64];

int LeftPower = 0;
int RightPower = 0;
HWind wleft = -1;
HWind wright = -1;
HWind DCDWindow = -1;
CSize spSize = {32, 128};
CSize spDSize = {32, 128};
CSpriteArea* pSprites = NULL;

void App_Log(const char* pformat, ...)
{
	va_list arg;
	char	filename[] = TASKNAME ":Log";
	FILE* file = fopen(filename, "a");

	if (file == NULL) file = fopen(filename, "w");

	if (file == NULL) return;

	va_start(arg, pformat);
	vfprintf(file, pformat, arg);
	va_end(arg);

	fclose(file);
}

void App_LogBinary(void* pdata, int len)
{
	char	filename[] = TASKNAME ":Log";
	FILE* file = fopen(filename, "a");

	if (file == NULL) file = fopen(filename, "w");

	if (file == NULL) return;

	fprintf(file, "Dump of %p\n", pdata);
	fwrite(pdata, len, 1, file);
	fprintf(file, "End of Dump\n");

	fclose(file);
}

typedef void (*Window_Drawer)(const CWindRedraw* pdraw, void* handle);

void Window_Update(HWind w, const CRect* pbox, Window_Drawer pdraw, void* handle)
{
	_kernel_swi_regs	regs;
	CWindRedraw		r;

	r.w = w;
	r.cvt.box = *pbox;

	/* Wimp_UpdateWindow */
	regs.r[1] = (int) &r;
	_kernel_swi(0x0600c9, &regs, &regs);

	while (regs.r[0])
	{
		if (pdraw) pdraw(&r, handle);

		/* Wimp_GetRectangle */
		regs.r[1] = (int) &r;
		_kernel_swi(0x0600ca, &regs, &regs);
	}
}

void Window_Redraw(HWind w, Window_Drawer pdraw, void* handle)
{
	_kernel_swi_regs	regs;
	CWindRedraw		r;

	r.w = w;

	/* Wimp_RedrawWindow */
	regs.r[1] = (int) &r;
	_kernel_swi(0x0600c8, &regs, &regs);

	while (regs.r[0])
	{
		if (pdraw) pdraw(&r, handle);

		/* Wimp_GetRectangle */
		regs.r[1] = (int) &r;
		_kernel_swi(0x0600ca, &regs, &regs);
	}
}

CRect Window_GetOutline(HWind id)
{
	_kernel_swi_regs	regs;
	struct
	{
		HWind	w;
		CRect	pos;
	}	outline;

	/* Wimp_GetWindowOutline */
	outline.w = id;
	regs.r[1] = (int) &outline;
	_kernel_swi(0x0600e0, &regs, &regs);
	return outline.pos;
}

void Desktop_SetStdColour(int c)
{
	_kernel_swi_regs	regs;

	regs.r[0] = c;
	_kernel_swi(0x600e6, &regs, &regs);
}

void Display_Plot(int code, int x, int y)
{
	_kernel_swi_regs	regs;

	/* OS_Plot  */
	regs.r[0] = code;
	regs.r[1] = x;
	regs.r[2] = y;
	_kernel_swi(0x020045, &regs, &regs);
}

void BarDrawer(const CWindRedraw* pdraw, void* handle)
{
	int power = (int) handle;
	int y = pdraw->cvt.box.y0 + (((pdraw->cvt.box.y1 - pdraw->cvt.box.y0)*power) >> 7);
	CPoint pt;
	CRect rect = {0, 0, 0, 0};

	pt.x = pdraw->cvt.box.x0;
	pt.y = pdraw->cvt.box.y0;

	Desktop_SetStdColour(7);
	Display_Plot(  4, pdraw->cvt.box.x0, y);
	Display_Plot(101, pdraw->cvt.box.x1, pdraw->cvt.box.y1);

	y -= pt.y;
	rect.x1 = spSize.cx;
	while (y > 0)
	{
		rect.y1 = (spSize.cy*y)/spDSize.cy;
		Desktop_PlotSprite(pSprites, "bar", &pt, &rect);
		y -= spDSize.cy;
		pt.y += spDSize.cy;
	}
}

bool App_ProcessKey(int key)
{
	return false;
}

void BarOpenLeft(void)
{
	_kernel_swi_regs	regs;
	CWindOpen		open;
	CRect			box = Window_GetOutline(DCDWindow);

	open.w = wleft;
	open.cvt.box.y0 = box.y0;
	open.cvt.box.y1 = box.y1;
	open.cvt.box.x0 = box.x0 - spDSize.cx;
	open.cvt.box.x1 = box.x0;
	open.cvt.s.cx = 0;
	open.cvt.s.cy = 0;
	open.behind = DCDWindow;

	regs.r[1] = (int) &open;

	_kernel_swi(0x600C5, &regs, &regs);
}

void BarOpenRight(void)
{
	_kernel_swi_regs	regs;
	CWindOpen		open;
	CRect			box = Window_GetOutline(DCDWindow);

	open.w = wright;
	open.cvt.box.y0 = box.y0;
	open.cvt.box.y1 = box.y1;
	open.cvt.box.x0 = box.x1;
	open.cvt.box.x1 = box.x1 + spDSize.cx;
	open.cvt.s.cx = 0;
	open.cvt.s.cy = 0;
	open.behind = DCDWindow;

	regs.r[1] = (int) &open;
	_kernel_swi(0x600C5, &regs, &regs);
}

#define SND_WIDTH 128
char2 Buffer[SND_WIDTH];

void App_ProcessFrame(void)
{
	CRect	box = {0, 0, 32, 1024};
	int	i, size;
	char	left, right;
	/* DCDWindow tracking, send WindowOpen Requests */

	/* Read wave */
	LeftPower = 0;
	RightPower = 0;
	size = DCDUtils_GetBuffer(Buffer, Buffer + SND_WIDTH);
	for (i = 0; i < size; i++)
	{
		left = Buffer[i][0];
		right = Buffer[i][1];
		/* Get abs */
		if (left & 0x80) left = -left;
		if (right & 0x80) right = - right;

		if (LeftPower < left)
			LeftPower = left;
		if (RightPower < right)
			RightPower = right;
	}
	BarOpenLeft();
	BarOpenRight();
	Window_Update(wleft, &box, BarDrawer, (void*) LeftPower);
	Window_Update(wright, &box, BarDrawer, (void*) RightPower);
}

CWind w = {0,0,0,32,1024,0,0,-1,0,0xff,0,0,0xff,0,0,0,0,0,0,256,1024,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

static int messages[] =
{
	0x52780, /* Message_UpdatePlugInsPosition */
	0
};

int main(int argc, char* argv[])
{
	_kernel_oserror Error_Signal;
	_kernel_swi_regs regs;
	const _kernel_oserror* err = NULL;
	const char* errfile = NULL;
	int errline = 0;
	int Time =0;
	int NewTime = 0;
	const char* pDCDTitle;
	int count = -1;
	int oldcount;
	int dummy;
	HTask TaskId;
	bool bLoop = true;

	exception_initialise();

	if (argc > 1)
	{
		DCDUtils_SetPlayer(atoi(argv[1]));
	}
	else
		DCDUtils_SetPlayer(0);

/*	Choices_Choices("DigitalCD.Plugins." TASKNAME ".Choices");
*/
	/* Wimp_Initialise */
	regs.r[0] = 310;
	regs.r[1] = 0x4B534154;
	regs.r[2] = (int) TaskName;
	regs.r[3] = (int) messages;
	throw_os(_kernel_swi(0x0600C0, &regs, &regs));
	TaskId = regs.r[1];
	throw_os(DCDUtils_RegisterPlugIn(TaskId, false));
	Task_SetModeInfo();

	try
	{
		if ((pSprites = Sprites_LoadFile("PowerBars:Sprites")) == NULL)
			throw_string("Failed to load sprites");

		spSize = GetSpriteSize(pSprites, "bar");
		spDSize = Desktop_GetSpriteSize(pSprites, "bar");
		w.o.o.cvt.box.x1 = spDSize.cx;
		w.ex.x1 = spDSize.cx;

		/* Wimp_CreateWindow */
		regs.r[1] = (int) &w.o.o.cvt;
		throw_os(_kernel_swi(0x0600C1, &regs, &regs));
		wleft = regs.r[0];
		/* Wimp_CreateWindow */
		regs.r[1] = (int) &w.o.o.cvt;
		throw_os(_kernel_swi(0x0600C1, &regs, &regs));
		wright = regs.r[0];

		/*
		 * Main loop
		 */
		bLoop = true;

		do
		{
			bool bWait = true;

			/* XOS_ReadMonotonicTime */
			_kernel_swi(0x020042, &regs, &regs);
			Time = regs.r[0];

			/* Get song info */
			oldcount = count;
			DCDUtils_GetInfo(&DCDWindow, &count, &dummy, &pDCDTitle);
			/* Process frame */
			if (count)
				App_ProcessFrame();
			else
				bLoop = false;

			/* Wimp_PollIdle */
			NewTime = Time + 5;
			while (bWait && bLoop)
			{
				regs.r[0] = 0;
				regs.r[1] = (int) &block;
				regs.r[2] = NewTime;
				_kernel_swi(0x600E1, &regs, &regs);
				switch(regs.r[0])
				{
					case EEvent_Null:
					{
						bWait = false;
					}
					break;
					case EEvent_WindowRedraw:
					{
						if (block[0] == wleft)
							Window_Redraw(wleft, BarDrawer, (void*) LeftPower);
						else if (block[0] == wright)
							Window_Redraw(wright, BarDrawer, (void*) RightPower);
						else Window_Redraw(block[0], NULL, NULL);
					}
					break;
					case EEvent_WindowOpen:
					{
						CWindOpen* popen = (CWindOpen*) &block;

						if (popen->w == wleft)
							BarOpenLeft();
						else if (popen->w == wright)
							BarOpenRight();
						else
							_kernel_swi(0x600C5, &regs, &regs);
					}
					break;
					case EEvent_WindowClose:
					{
						_kernel_swi(0x600C6, &regs, &regs);
					}
					break;
					case EEvent_Key:
					{
						Event_Key* pkey = (Event_Key*) &block;

						if (!App_ProcessKey(pkey->code))
						{
							pkey->caret.pos.w = DCDWindow;
							pkey->caret.pos.i = -1;
							/* Wimp_SendMessage */
							regs.r[0] = EEvent_Key;
							regs.r[1] = (int) pkey;
							regs.r[2] = DCDWindow;
							regs.r[3] = -1;
							_kernel_swi(0x0600e7, &regs, &regs);
						}
					}
					break;
					case EEvent_Message:
					case EEvent_MessageWantAck:
					{
						switch(block[4])
						{
							case EMsg_Quit:
							{
								bLoop = false;
							}
							break;
							case EMsg_ModeChange:
							{
								/* Preprocessing */
								Task_SetModeInfo();
							}
							break;
							case 0x52780:
							{
								/* Player may send us it's new position */
								BarOpenLeft();
								BarOpenRight();
							}
							break;
						}
					}
					break;
				}
			}
		} while (bLoop);
	}
	catch
	{
		const exception* e = exception_current();

		switch(e->type)
		{
			case exception_os:
			{
				/* Save error as it could be overwritten */
				Error_Signal = *e->error.os;
				err = &Error_Signal;
				errfile = e->file;
				errline = e->line;
			}
			break;
/*			case exception_user:	err = &Task_Error_NoMem; break;
*/			default:
			{
				if (e->error.signal_id != SIGINT)
				{
					err = &Error_Signal;
					sprintf(&Error_Signal.errmess[0], "Signal:%d\n\0", e->error.signal_id);
					errfile = NULL;
					errline = 0;
				}
			}
		}
	}
	catch_end

	DCDUtils_DeregisterPlugIn();

	/* Wimp_CloseDown */
	regs.r[0] = TaskId;
	regs.r[1] = 0x4b534154;
	_kernel_swi(0x0600DD, &regs, &regs);

	if (err)
	{
		if (errfile)
			App_Log("%s at line %d of file %s\n", err->errmess, errline, errfile);
		else
			App_Log("%s\n", err->errmess);

		printf("%s\n", err->errmess);
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}
