module main;

pragma (lib, "dallegro5");
pragma (lib, "allegro");
pragma (lib, "allegro_font");
pragma (lib, "allegro_primitives");
pragma (lib, "allegro_ttf");

import allegro5.allegro;
import allegro5.allegro_font;
import allegro5.allegro_primitives;
import allegro5.allegro_ttf;

import std.algorithm;
import std.c.stdlib;
import std.exception;
import std.math;
import std.stdio;
import std.string;
import std.typecons;

immutable int MAX_X = 800;
immutable int MAX_Y = 600;
immutable int NA    =  -1;
immutable string FONT_FILE_NAME = "data/Inconsolata.otf";

ALLEGRO_DISPLAY * display;
ALLEGRO_EVENT_QUEUE * event_queue;
ALLEGRO_FONT * global_font_24;

void do_nothing ()
{
// do nothing
}

void init (string [] args)
{
	enforce (al_init ());
	enforce (al_init_primitives_addon ());
	enforce (al_install_keyboard ());
	enforce (al_install_mouse ());
	al_init_font_addon ();
	enforce (al_init_ttf_addon ());

	display = al_create_display (MAX_X, MAX_Y);
	enforce (display);

	event_queue = al_create_event_queue ();
	enforce (event_queue);

	al_register_event_source (event_queue,
	                          al_get_keyboard_event_source ());
	al_register_event_source (event_queue,
	                          al_get_mouse_event_source ());
	al_register_event_source (event_queue,
	                          al_get_display_event_source (display));

	global_font_24 = al_load_ttf_font (FONT_FILE_NAME.toStringz (), 24, 0);
	enforce (global_font_24);
}

void happy_end ()
{
	al_destroy_display (display);
	al_destroy_event_queue (event_queue);
	al_destroy_font (global_font_24);

	al_shutdown_primitives_addon ();
	al_shutdown_font_addon ();
	al_shutdown_ttf_addon ();

	exit (EXIT_SUCCESS);
}


immutable int    MAX_S =  360;
immutable int    ADD   =    2;
immutable double EPS   = 1E-9;

alias Tuple !(double, "x", double, "y") point;
alias Tuple !(int, "x", int, "y", int, "a", bool, "on") laser;

double vp (point a, point b, point c)
{
	return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
}

double sp (point a, point b, point c)
{
	return (b.x - a.x) * (c.x - a.x) + (b.y - a.y) * (c.y - a.y);
}

bool possible (point a, point b, point c, point u, point v)
{
	if ((sp (a, b, u) < -EPS) && (sp (a, b, v) < -EPS) &&
	    (sp (a, c, u) < -EPS) && (sp (a, c, v) < -EPS))
	{
		return true;
	}
	double x = vp (a, b, u);
	double y = vp (a, c, v);
	return (abs (y) > EPS) && (x * y > EPS);
}

auto solve (laser [] p)
{
	int xlo = 0;
	int xhi = 0;
	int ylo = 0;
	int yhi = 0;
	foreach (i; 0..n)  if (p[i].on)
	{
		xlo = min (xlo, p[i].x - ADD);
		xhi = max (xhi, p[i].x + ADD);
		ylo = min (ylo, p[i].y - ADD);
		yhi = max (yhi, p[i].y + ADD);
	}
	int xsize = xhi - xlo + 1;
	int ysize = yhi - ylo + 1;

	auto q = new point [n];
	foreach (i; 0..n)  if (p[i].on)
	{
		q[i] = point (p[i].x - xlo, p[i].y - ylo);
	}

	auto r = new point [] [] (MAX_S + 1, n);
	foreach (s; 0..MAX_S + 1)
	{
		foreach (i; 0..n)  if (p[i].on)
		{
			double angle = 0.25 + p[i].a * s * 1.0 / MAX_S;
			angle *= 2.0 * PI;
			r[s][i] = point (p[i].x - xlo + cos (angle),
			                 p[i].y - ylo + sin (angle));
		}
	}

	auto f = new bool [] [] [] (MAX_S + 1, xsize, ysize);
	f[0][0 - xlo][0 - ylo] = true;
	foreach (s; 0..MAX_S)
	{
		foreach (x; 0..xsize)  foreach (y; 0..ysize)  if (f[s][x][y])
		{
			debug {writefln ("s = %s, (%s, %s)", s, x, y);}
			auto u = point (x, y);
			foreach (dx; -1..+2)  dy_loop: foreach (dy; -1..+2)
			{
				int nx = x + dx;
				int ny = y + dy;
				if (nx < 0 || nx >= xsize ||
				    ny < 0 || ny >= ysize)
				{
					continue dy_loop;
				}
				foreach (i; 0..n)  if (p[i].on)
				{
					if ((nx == p[i].x) && (ny == p[i].y))
					{
						continue dy_loop;
					}
				}
				foreach (i; 0..n)  if (p[i].on)
				{
					auto v = point (nx, ny);
					if (!possible (q[i], r[s][i],
					               r[s + 1][i],
					               u, v))
					{
						continue dy_loop;
					}
				}
				debug {writefln ("to (%s, %s)", nx, ny);}
				f[s + 1][nx][ny] = true;
			}
		}
	}

	return f;
}


immutable int SX  =  40;
immutable int SY  = 550;
immutable int SW  = MAX_X - SX * 2;
immutable int SH  =  35;

immutable int BX  =  50;
immutable int BY  =  50;
immutable int BW  = 450;
immutable int BH  = 450;

immutable int TX  = 550;
immutable int TY  =  50;
immutable int TW  = 200;
immutable int TH  = 150;

immutable int TONX =    1;
immutable int TONY =    1;
immutable int TONW =   18;
immutable int TONH =   18;

immutable int TAXX =    1;
immutable int TAXY =   30;
immutable int TAXW =  150;
immutable int TAXH =   20;
immutable int TAXLO =   2;
immutable int TAXHI =  10;
immutable int TAXSTEP = 2;

immutable int TAYX =    1;
immutable int TAYY =   60;
immutable int TAYW =  150;
immutable int TAYH =   20;
immutable int TAYLO =   2;
immutable int TAYHI =  10;
immutable int TAYSTEP = 2;

immutable int TAAX =    1;
immutable int TAAY =   90;
immutable int TAAW =  150;
immutable int TAAH =   20;
immutable int TAALO =  -3;
immutable int TAAHI =  +3;
immutable int TAASTEP = 1;

immutable double MAX_SCALE = 125.0;

ALLEGRO_COLOR [] color;
bool [] [] [] f;
laser [] p;
double sscale;
double bscale;
double radius;
double linewidth;
int cur_step;
int n;

void init_colors ()
{
	color.length = 0;
	color ~= al_map_rgb_f (0.2, 0.1, 0.4); // background
	color ~= al_map_rgb_f (0.8, 0.8, 0.8); // selector
	color ~= al_map_rgb_f (0.5, 0.5, 0.5); // source
	color ~= al_map_rgb_f (0.8, 0.2, 0.2); // bad
	color ~= al_map_rgb_f (0.2, 0.8, 0.2); // good
}

void init_sizes ()
{
	sscale = SW / (f.length - 1);
	bscale = MAX_SCALE;
	bscale = min (bscale, BW * 1.0 / (f[0].length - 1));
	bscale = min (bscale, BH * 1.0 / (f[0][0].length - 1));
	radius = bscale * 0.25;
	linewidth = radius * 0.5;
	cur_step = 0;
}

void draw_selector ()
{
	foreach (i; 0..f.length)
	{
		al_draw_line (SX + sscale * (i + 0.5),
		              SY + SH * (0.0 + 0.5 * (i & 1)),
		              SX + sscale * (i + 0.5),
		              SY + SH * (0.5 + 0.5 * (i & 1)),
		              color[1], 1.0);
	}
	al_draw_line (SX + sscale * (cur_step + 0.5),
	              SY + SH * (0.0 + 0.5 * (cur_step & 1)),
	              SX + sscale * (cur_step + 0.5),
	              SY + SH * (0.5 + 0.5 * (cur_step & 1)),
	              color[4], 3.0);
}

void draw_board ()
{
	int xsize = f[0].length;
	int ysize = f[0][0].length;
	foreach (x; 0..xsize)  foreach (y; 0..ysize)
	{
		al_draw_filled_circle (BX + x * bscale, BY + BH - y * bscale,
		                       radius, color[3 + f[cur_step][x][y]]);
	}
	foreach (i; 0..n)  if (p[i].on)
	{
		al_draw_filled_circle (BX + p[i].x * bscale,
		                       BY + BH - p[i].y * bscale,
		                       radius, color[2]);
	}
	int cr = cast (int) radius + 1;
	al_set_clipping_rectangle (BX - cr, BY - cr, BW + cr * 2, BH + cr * 2);
	foreach (i; 0..n)  if (p[i].on)
	{
		double x1 = BX + p[i].x * bscale;
		double y1 = BY + BH - p[i].y * bscale;
		double angle = 0.25 + p[i].a * cur_step * 1.0 / MAX_S;
		angle *= 2.0 * PI;
		double xd = cos (angle);
		double yd = sin (angle);
		double mult = 1000.0;
		al_draw_line (x1, y1, x1 + xd * mult, y1 - yd * mult,
		              color[3], linewidth);
	}
	al_set_clipping_rectangle (0, 0, MAX_X, MAX_Y);
}

void draw_tuning ()
{
	foreach (i; 0..n)
	{
		immutable int CTX = TX;
		immutable int CTY = TY + i * TH;
		al_draw_filled_rectangle (CTX + TONX, CTY + TONY,
		                          CTX + TONX + TONW,
		                          CTY + TONY + TONH,
		                          color[3 + p[i].on]);
		al_draw_rectangle (CTX + TONX, CTY + TONY,
		                   CTX + TONX + TONW, CTY + TONY + TONH,
		                   color[2], 2);

		void draw_adjuster (int x, int y, int w, int h,
		                    int lo, int hi, int step, int value)
		{
			int num = (hi - lo) / step + 1;
			enforce (num > 1);
			double scale = w * 1.0 / (num - 1);
			foreach (j; 0..num)
			{
				al_draw_line (CTX + x + scale * j, CTY + y,
				              CTX + x + scale * j,
				              CTY + y + h,
				              color[2], 3);
			}
			int k = (value - lo) / step;
			al_draw_line (CTX + x + scale * k, CTY + y,
			              CTX + x + scale * k,
			              CTY + y + h,
			              color[4], 5);
		}
		
		draw_adjuster (TAXX, TAXY, TAXW, TAXH,
		               TAXLO, TAXHI, TAXSTEP, p[i].x);
		draw_adjuster (TAYX, TAYY, TAYW, TAYH,
		               TAYLO, TAYHI, TAYSTEP, p[i].y);
		draw_adjuster (TAAX, TAAY, TAAW, TAAH,
		               TAALO, TAAHI, TAASTEP, p[i].a);
	}
}

void draw ()
{
	al_clear_to_color (color[0]);
	draw_selector ();
	draw_board ();
	draw_tuning ();
}

void main_loop ()
{
	draw ();
	al_flip_display ();
	
	bool to_end = false;
	while (!to_end)
	{
		ALLEGRO_EVENT current_event;

		void process_keyboard ()
		{
			switch (current_event.keyboard.keycode)
			{
				case ALLEGRO_KEY_ESCAPE:
					to_end = true;
					break;

				default:
					do_nothing ();
					break;	
			}
		}

		void process_mouse_selector ()
		{
			int i = cast (int)
			        ((current_event.mouse.x - SX + sscale) /
			         sscale) - 1;
			int j = (current_event.mouse.y - SY + (SH + 1)) /
			        (SH + 1) - 1;

			if (i < 0 || f.length <= i || j != 0)
			{
				return;
			}
			cur_step = i;
		}	

		void process_mouse_tuning ()
		{
			if (current_event.mouse.button != 1)
			{
				return;
			}

			foreach (i; 0..n)
			{
				immutable int CTX = TX;
				immutable int CTY = TY + i * TH;
				int cx = current_event.mouse.x - CTX;
				int cy = current_event.mouse.y - CTY;
				if (TONX <= cx && cx <= TONX + TONW &&
				    TONY <= cy && cy <= TONY + TONH)
				{
					p[i].on ^= 1;
				}

				void process_adjuster (int x, int y,
				                       int w, int h,
				                       int lo, int hi,
				                       int step,
				                       ref int value)
				{
					if (cy < y || y + h < cy)
					{
						return;
					}

					int num = (hi - lo) / step + 1;
					enforce (num > 1);
					double scale = w * 1.0 / (num - 1);
					foreach (j; 0..num)
					{
						double tx = cx - x -
						            j * scale;
						if (abs (tx) <= scale * 0.4)
						{
							value = lo +
							        j * step;
						}
					}
				}
		
				process_adjuster (TAXX, TAXY, TAXW, TAXH,
				                  TAXLO, TAXHI, TAXSTEP,
				                  p[i].x);
				process_adjuster (TAYX, TAYY, TAYW, TAYH,
				                  TAYLO, TAYHI, TAYSTEP,
				                  p[i].y);
				process_adjuster (TAAX, TAAY, TAAW, TAAH,
				                  TAALO, TAAHI, TAASTEP,
				                  p[i].a);
			}

			f = solve (p);
		}	

	al_wait_for_event (event_queue, &current_event);

	switch (current_event.type)
		{
			case ALLEGRO_EVENT_DISPLAY_CLOSE:
				to_end = true;
				break;

			case ALLEGRO_EVENT_DISPLAY_SWITCH_IN:
				do_nothing ();
				break;

			case ALLEGRO_EVENT_KEY_DOWN:
				process_keyboard ();
				break;

			case ALLEGRO_EVENT_MOUSE_AXES:
				process_mouse_selector ();
				break;

			case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN:
				process_mouse_tuning ();
				break;

			default:
				do_nothing ();
				break;
		}

		draw ();
		al_flip_display ();
	}
}

void main_menu ()
{
	stdin = File ("data/visual.in", "rt");
	init_colors ();

	while (readf (" %s", &n) > 0)
	{
		p = new laser [n];
		foreach (i; 0..n)
		{
			readf (" %s %s %s", &p[i].x, &p[i].y, &p[i].a);
			p[i].on = true;
		}
		f = solve (p);
		init_sizes ();
		main_loop ();
		break;
	}
}

int main (string [] args)
{
	return al_run_allegro (
	{
		init (args);
		main_menu ();
		happy_end ();
		return 0;
	});
}
