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

#include "DCDswi.h"

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

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

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

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

static void Window_Update(HWind w, const CRect* pbox, Window_Drawer pdraw, void* handle)
{
	CWindRedraw		r;
	int more;

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

	_swix(Wimp_UpdateWindow, _IN(1)|_OUT(0), &r, &more);

	while (more)
	{
		if (pdraw) pdraw(&r, handle);

		_swix(Wimp_GetRectangle, _IN(1)|_OUT(0), &r, &more);
	}
}

static void Window_Redraw(HWind w, Window_Drawer pdraw, void* handle)
{
	CWindRedraw		r;
	int more;

	r.w = w;

	_swix(Wimp_RedrawWindow, _IN(1)|_OUT(0), &r, &more);

	while (more)
	{
		if (pdraw) pdraw(&r, handle);

		_swix(Wimp_GetRectangle, _IN(1)|_OUT(0), &r, &more);
	}
}

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

	outline.w = id;
	_swix(Wimp_GetWindowOutline, _IN(1), &outline);

	return outline.pos;
}

static void Desktop_SetStdColour(int c)
{
	_swix(Wimp_SetColour, _IN(0), c);
}

static void Display_Plot(int code, int x, int y)
{
	_swix(OS_Plot, _INR(0,2), code, x, y);
}

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

static void BarOpenLeft(void)
{
	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;

	_swix(Wimp_OpenWindow, _IN(1), &open);
}

static void BarOpenRight(void)
{
	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;

	_swix(Wimp_OpenWindow, _IN(1), &open);
}

#define SND_WIDTH 128
static char2 Buffer[SND_WIDTH];

static void App_ProcessFrame(void)
{
	CRect	box = {0, 0, 32, 1024};
	int	i, size;
	char	left, right;

	/* Don nothing while a full screen plug-in is active */
	if (DCDUtils_GetFullScreenPlugIn() != -1)
		return;

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

static 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;
	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;
	int code;
	HTask TaskId;
	bool bLoop = true;

	exception_initialise();

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

	_swi(Wimp_Initialise, _INR(0,3)|_OUT(1), 310, 0x4B534154, TaskName, messages, &TaskId);
	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;

		_swi(Wimp_CreateWindow, _IN(1)|_OUT(0), &w.o.o.cvt, &wleft);
		_swi(Wimp_CreateWindow, _IN(1)|_OUT(0), &w.o.o.cvt, &wright);

		/*
		 * Main loop
		 */
		bLoop = true;

		do
		{
			bool bWait = true;

			_swix(OS_ReadMonotonicTime, _OUT(0), &Time);

			/* 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)
			{
				_swix(Wimp_PollIdle, _INR(0,2)|_OUT(0), 0, &block, NewTime, &code);

				switch(code)
				{
					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
							_swix(Wimp_OpenWindow, _IN(1), &block);
					}
					break;
					case EEvent_WindowClose:
					{
						_swix(Wimp_CloseWindow, _IN(1), &block);
					}
					break;
					case EEvent_Key:
					{
						Event_Key* pkey = (Event_Key*) &block;

						pkey->caret.pos.w = DCDWindow;
						pkey->caret.pos.i = -1;
						_swix(Wimp_SendMessage, _INR(0,3), EEvent_Key, pkey, DCDWindow, -1);
					}
					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();

		// Save error as it could be overwritten
		Error_Signal = e->error;
		err = &Error_Signal;
		errfile = e->file;
		errline = e->line;
	}
	catch_end

	DCDUtils_DeregisterPlugIn();

	_swix(Wimp_CloseDown, _INR(0,1), TaskId, 0x4b534154);

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