#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/wb.h>
#include <proto/icon.h>
#include <proto/intuition.h>

#include <exec/devices.h>
#include <devices/timer.h>
#include <workbench/startup.h>
#include <intuition/intuition.h>

#include <dos/dos.h>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "evtypes.h"

void error(char *s)
{
	struct EasyStruct es;
	
	es.es_StructSize = sizeof(es);
	es.es_Flags = 0;
	es.es_Title = "WB Clock";
	es.es_TextFormat = s;
	es.es_GadgetFormat = "Ok";
	
	EasyRequest(0, &es, 0, 0);
}

struct DiskObject *wb_disk_object(struct WBStartup *wbs)
{
	struct WBArg *wba;
	BPTR olddir;
	struct DiskObject *dobj;
	
	wba = wbs->sm_ArgList;
	olddir = CurrentDir(wba[0].wa_Lock);
	dobj = GetDiskObject(wba[0].wa_Name);
	CurrentDir(olddir);
	
	return dobj;
}

void wb_put_disk_object(struct WBStartup *wbs, struct DiskObject *dobj)
{
	struct WBArg *wba;
	BPTR olddir;
	
	wba = wbs->sm_ArgList;
	olddir = CurrentDir(wba[0].wa_Lock);
	PutDiskObject(wba[0].wa_Name, dobj);
	CurrentDir(olddir);
	
	return;
}

typedef struct {
	unsigned short *d;
	unsigned short width, height, ww;
} drawsurface;

void draw_line(drawsurface *ds, int x1, int y1, int x2, int y2)
{
	int tmp, x, y, ww;
	unsigned short *d;
	
	ww = ds->ww;

	if (x2 < x1) {
		tmp = x1;
		x1 = x2;
		x2 = tmp;
		
		tmp = y1;
		y1 = y2;
		y2 = tmp;
	}
	
	if (y1 == y2) {
		d = &ds->d[ww * y1];
		for (; x1 <= x2; x1++) {
			d[x1 >> 4] |= 1 << (15 - (x1 & 15));
		}
		return;
	}
	
	if (x1 == x2) {
		if (y2 < y1) {
			for (; y2 <= y1; y2++) {
				d = &ds->d[ww * y2];
				d[x1 >> 4] |= 1 << (15 - (x1 & 15));
			}
		} else {
			for (; y1 <= y2; y1++) {
				d = &ds->d[ww * y1];
				d[x1 >> 4] |= 1 << (15 - (x1 & 15));
			}
		}
		return;
	}
	
	if (y2 < y1) {
		if (y1 - y2 < x2 - x1) {
			for (x = x1; x <= x2; x++) {
				tmp = ((x - x1) * (y2 - y1) + (x2 - x1)/2) / (x2 - x1) + y1;
				d = &ds->d[ww * tmp];
				d[x >> 4] |= 1 << (15 - (x & 15));
			}
		} else {
			for (y = y1; y >= y2; y--) {
				tmp = ((y - y1) * (x2 - x1) + (y2 - y1)/2) / (y2 - y1) + x1;
				d = &ds->d[ww * y];
				d[tmp >> 4] |= 1 << (15 - (tmp & 15));
			}
		}
	} else {
		if (y2 - y1 < x2 - x1) {
			for (x = x1; x <= x2; x++) {
				tmp = ((x - x1) * (y2 - y1) + (x2 - x1)/2) / (x2 - x1) + y1;
				d = &ds->d[ww * tmp];
				d[x >> 4] |= 1 << (15 - (x & 15));
			}
		} else {
			for (y = y1; y <= y2; y++) {
				tmp = ((y - y1) * (x2 - x1) + (y2 - y1)/2) / (y2 - y1) + x1;
				d = &ds->d[ww * y];
				d[tmp >> 4] |= 1 << (15 - (tmp & 15));
			}
		}
	}
}

void undraw_line(drawsurface *ds, int x1, int y1, int x2, int y2)
{
	int tmp, x, y, ww;
	unsigned short *d;
	
	ww = ds->ww;

	if (x2 < x1) {
		tmp = x1;
		x1 = x2;
		x2 = tmp;
		
		tmp = y1;
		y1 = y2;
		y2 = tmp;
	}
	
	if (y1 == y2) {
		d = &ds->d[ww * y1];
		for (; x1 <= x2; x1++) {
			d[x1 >> 4] &=~ (1 << (15 - (x1 & 15)));
		}
		return;
	}
	
	if (x1 == x2) {
		if (y2 < y1) {
			for (; y2 <= y1; y2++) {
				d = &ds->d[ww * y2];
				d[x1 >> 4] &=~ (1 << (15 - (x1 & 15)));
			}
		} else {
			for (; y1 <= y2; y1++) {
				d = &ds->d[ww * y1];
				d[x1 >> 4] &=~ (1 << (15 - (x1 & 15)));
			}
		}
		return;
	}
	
	if (y2 < y1) {
		if (y1 - y2 < x2 - x1) {
			for (x = x1; x <= x2; x++) {
				tmp = ((x - x1) * (y2 - y1) + (x2 - x1)/2) / (x2 - x1) + y1;
				d = &ds->d[ww * tmp];
				d[x >> 4] &=~ (1 << (15 - (x & 15)));
			}
		} else {
			for (y = y1; y >= y2; y--) {
				tmp = ((y - y1) * (x2 - x1) + (y2 - y1)/2) / (y2 - y1) + x1;
				d = &ds->d[ww * y];
				d[tmp >> 4] &=~ (1 << (15 - (tmp & 15)));
			}
		}
	} else {
		if (y2 - y1 < x2 - x1) {
			for (x = x1; x <= x2; x++) {
				tmp = ((x - x1) * (y2 - y1) + (x2 - x1)/2) / (x2 - x1) + y1;
				d = &ds->d[ww * tmp];
				d[x >> 4] &=~ (1 << (15 - (x & 15)));
			}
		} else {
			for (y = y1; y <= y2; y++) {
				tmp = ((y - y1) * (x2 - x1) + (y1 - y2)/2) / (y1 - y2) + x1;
				d = &ds->d[ww * y];
				d[tmp >> 4] &=~ (1 << (15 - (tmp & 15)));
			}
		}
	}
}

void clear_circle(drawsurface *ds, int x, int y, int r)
{
	int i, dx;
	
	for (i = y - r; i <= y + r; i++) {
		dx = (int)sqrt((float)(r * r - (i - y) * (i - y)));
		
		undraw_line(ds, x - dx, i, x + dx, i);
	}
}

void draw_radius(drawsurface *d1, drawsurface *d2, int x, int y, int r, double theta)
{
	int x2, y2, dx, dy;
	double rcos, rsin;
	
	rcos = r * cos(theta);
	rsin = r * sin(theta);
	
	x2 = (int)rcos + x;
	y2 = -(int)rsin + y;

	if (abs(rsin) > abs(rcos)) {
		dx = 1;
		dy = 0;
	} else {
		dx = 0;
		dy = 1;
	}
	
	draw_line(d1, x + dx, y + dy, x2 + dx, y2 + dy);
	undraw_line(d2, x + dx, y + dy, x2 + dx, y2 + dy);
	
	draw_line(d2, x, y, x2, y2);
	undraw_line(d1, x, y, x2, y2);
}

void clear_clock(struct DiskObject *dobj)
{
	struct Gadget *g;
	struct Image *i1, *i2;
	drawsurface d1a, d1b, d2a, d2b;
		
	g = &dobj->do_Gadget;
	i1 = (struct Image *)g->GadgetRender;
	i2 = (struct Image *)g->SelectRender;
	
	d1a.width = i1->Width;
	d1a.height = i1->Height;
	d1a.ww = (i1->Width + 15) / 16;
	
	d2a.width = i2->Width;
	d2a.height = i2->Height;
	d2a.ww = (i2->Width + 15) / 16;
	
	d1b = d1a;
	d2b = d2a;
	
	d1a.d = i1->ImageData;
	d2a.d = i2->ImageData;
	
	d1b.d = d1a.d + d1a.height * d1a.ww;
	d2b.d = d2a.d + d2a.height * d2a.ww;
	
	clear_circle(&d1a, d1a.width / 2, d1a.height / 2, 15);
	clear_circle(&d1b, d1b.width / 2, d1b.height / 2, 15);

	clear_circle(&d2a, d2a.width / 2 + 1, d2a.height / 2 + 1, 15);
	clear_circle(&d2b, d2b.width / 2 + 1, d2b.height / 2 + 1, 15);
}

void draw_time(struct timerequest *tr, struct DiskObject *dobj)
{
	int hours, mins;
	struct Gadget *g;
	struct Image *i1, *i2;
	drawsurface d1a, d1b, d2a, d2b;
	double mtheta, htheta;
		
	mins = (tr->tr_time.tv_secs / 60) % 60;
	hours = (tr->tr_time.tv_secs / 60) % 720;
	
	g = &dobj->do_Gadget;
	i1 = (struct Image *)g->GadgetRender;
	i2 = (struct Image *)g->SelectRender;
	
	d1a.width = i1->Width;
	d1a.height = i1->Height;
	d1a.ww = (i1->Width + 15) / 16;
	
	d2a.width = i2->Width;
	d2a.height = i2->Height;
	d2a.ww = (i2->Width + 15) / 16;
	
	d1b = d1a;
	d2b = d2a;
	
	d1a.d = i1->ImageData;
	d2a.d = i2->ImageData;
	
	d1b.d = d1a.d + d1a.height * d1a.ww;
	d2b.d = d2a.d + d2a.height * d2a.ww;
	
	mtheta = PID2 - 2 * PI * mins / 60;
	htheta = PID2 - 2 * PI * hours / 720;

	clear_circle(&d1a, d1a.width / 2, d1a.height / 2, 15);
	clear_circle(&d1b, d1b.width / 2, d1b.height / 2, 15);

	clear_circle(&d2a, d2a.width / 2 + 1, d2a.height / 2 + 1, 15);
	clear_circle(&d2b, d2b.width / 2 + 1, d2b.height / 2 + 1, 15);

	draw_radius(&d1a, &d1b, d1b.width / 2, d1b.height / 2, 14, mtheta);
	draw_radius(&d1a, &d1b, d1b.width / 2, d1b.height / 2, 8, htheta);

	draw_radius(&d2a, &d2b, d1b.width / 2 + 1, d1b.height / 2 + 1, 14, mtheta);
	draw_radius(&d2a, &d2b, d1b.width / 2 + 1, d1b.height / 2 + 1, 8, htheta);
}

#define DIE_MESSAGE 0xaaff

void main(int argc, char **argv)
{
	struct DiskObject *dobj;
	struct MsgPort *timeport;
	struct timerequest *timer, *intr;
	boolean quit = false;
	
	IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 0);
	if (!IntuitionBase) {
		exit(15);
	}
	
	if (timeport = FindPort("wb_clock")) {
		timer = (struct timerequest *)CreateExtIO(timeport, sizeof(*timer));
		if (!timer) {
			error("Can't create time request");
			CloseLibrary((struct Library *)IntuitionBase);
			exit(10);
		}
		timer->tr_node.io_Command = DIE_MESSAGE;
		PutMsg(timeport, (struct Message *)timer);
		CloseLibrary((struct Library *)IntuitionBase);
		exit(0);
	}
	
	IconBase = OpenLibrary("icon.library", 0);
	if (!IconBase) {
		error("Can't open icon.library");
		CloseLibrary((struct Library *)IntuitionBase);
		exit(10);
	}
	
	WorkbenchBase = OpenLibrary("workbench.library", 0);
	if (WorkbenchBase) {
		if (argc) dobj = GetDiskObject(argv[0]);
		else dobj = wb_disk_object((struct WBStartup *)argv);
		
		if (dobj) {
			timeport = CreatePort("wb_clock", 0);
			if (timeport) {
				timer = (struct timerequest *)CreateExtIO(timeport, sizeof(*timer));
				if (timer) {
					if (OpenDevice("timer.device", UNIT_VBLANK, (void *)timer, 0) == 0) {
						timer->tr_node.io_Command = TR_ADDREQUEST;
						timer->tr_time.tv_secs = 5;
						timer->tr_time.tv_micro = 0;
						
						SendIO((void *)timer);
						while (1) {
							Wait(1 << timeport->mp_SigBit);
							
							while (intr = (struct timerequest *)GetMsg(timeport)) {
								if (intr->tr_node.io_Command == DIE_MESSAGE) {
									quit = true;
									DeleteExtIO((void *)intr);
									AbortIO((void *)timer);
									continue;
								}
								
								if (quit) {
									clear_clock(dobj);
									
									if (argc) PutDiskObject(argv[0], dobj);
									else wb_put_disk_object((struct WBStartup *)argv, dobj);
									
									error("Exiting ...");
									goto quit_loop;
								}
								
								timer->tr_node.io_Command = TR_GETSYSTIME;
								DoIO((void *)timer);
								
								draw_time(timer, dobj);
								
								if (argc) PutDiskObject(argv[0], dobj);
								else wb_put_disk_object((struct WBStartup *)argv, dobj);

								timer->tr_node.io_Command = TR_ADDREQUEST;
								timer->tr_time.tv_secs = 60 - timer->tr_time.tv_secs % 60;
								timer->tr_time.tv_micro = 0;
								SendIO((void *)timer);
							}
						}
			quit_loop:
						CloseDevice((void *)timer);
					} else error("Can't open timer.device");
					DeleteExtIO((void *)timer);
				} else error("Can't create timer request");
				DeletePort(timeport);
			} else error("Can't create port");
			FreeDiskObject(dobj);
		} else error("Can't get disk object");
		CloseLibrary(WorkbenchBase);
	} else error("Can't open workbench.library");
	
	CloseLibrary(IconBase);
	CloseLibrary((struct Library *)IntuitionBase);
}

