/*
 * Copyright © 2012 Keith Packard <keithp@keithp.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 */

autoload Nichrome;
autoload RR;
autoload Client;
autoload Client::Draw;
autoload Mutex;
autoload Nichrome::Timer;

extend namespace Nichrome {

	public namespace RRboard {

		import Client;
		import RR;

		public int	border_width = ceil(Draw::wall_thickness / 2);
		public int	board_width = RR::Width * Draw::cell_width;
		public int	total_width = board_width + border_width * 2;
		public int	board_height = RR::Height * Draw::cell_height;
		public int	total_height = board_height + border_width * 2;

		public typedef widget_t + struct {
			RR::Board		board;
			RR::RobotOrNone		active_robot;
			*Timer::timer_t		timer;
			void (RR::Color color,
			      RR::Direction direction) move_callback;
			int			button_x, button_y;
		} rrboard_widget_t;

		real dimension(&rrboard_widget_t widget) = min (widget.geometry.width, widget.geometry.height);

		Draw::transform_t transform(&rrboard_widget_t widget) {
			real	dim = dimension(&widget);
			real	xscale = dim / total_width;
			real	yscale = dim / total_height;
			return (Draw::transform_t) {
				.xscale = xscale,
				.yscale = yscale,
				.xoff = (widget.geometry.width - dim) // 2 + ceil(border_width * xscale),
				.yoff = (widget.geometry.height - dim) // 2 + ceil(border_width * yscale)
				};
		}

		bool is_middle(int x, int y) {
			if (x < RR::Width / 2 - 1)
				return false;
			if (x >= RR::Width / 2 + 1)
				return false;
			if (y < RR::Height / 2 - 1)
				return false;
			if (y >= RR::Height / 2 + 1)
				return false;
			return true;
		}

		void draw (cairo_t cr, &rrboard_widget_t widget) {
			Draw::transform_t	t = transform(&widget);
			RR::TargetOrNone	active_target = RR::active_target(&widget.board);

			save(cr);
			for (int y = 0; y < RR::Height; y++)
				for (int x = 0; x < RR::Width; x++)
					if (!is_middle(x, y))
						Draw::background(cr, x, y, widget.board[x,y], &t);
			for (int y = 0; y < RR::Height; y++)
				for (int x = 0; x < RR::Width; x++) {
					Draw::walls(cr, x, y, widget.board[x,y], &t);
					Draw::contents(cr, x, y, widget.board[x,y],
						       active_target, widget.active_robot, &t);
				}
			Draw::target(cr, RR::Width / 2 - 1, RR::Height / 2 - 1,
				     active_target, &t);
			restore(cr);
		}

		void outline (cairo_t cr, &rrboard_widget_t widget) {
			rectangle(cr, 0, 0, widget.geometry.width, widget.geometry.height);
		}

		void natural (cairo_t cr, &rrboard_widget_t widget) {
			rectangle(cr, 0, 0, total_width, total_height);
		}

		/* Override default widget configure function to also reposition
		 * the timer widget
		 */
		void configure (&rrboard_widget_t widget, rect_t geometry) {
			Widget::configure(&widget, geometry);

			/* Configure timer to sit over the central
			 * region of the board
			 */
			real board_dim = dimension(&widget);
			real timer_dim = board_dim * 2 / RR::Width;
			real timer_pos = board_dim / RR::Width * 7;
			widget.timer->configure (widget.timer,
					 (rect_t) {
						 .x = geometry.x + timer_pos,
						 .y = geometry.y + timer_pos,
						 .width = timer_dim,
						 .height = timer_dim
						 });
		}

		void set_active_robot(&rrboard_widget_t widget, RR::Robot robot) {
			widget.active_robot = (RR::RobotOrNone.robot) robot;
			Widget::redraw(&widget);
		}
		
		void set_active (&rrboard_widget_t widget, string color) {
			try {
				set_active_robot(&widget, (RR::Robot) { .color = RR::color(color) });
			} catch RR::rr_error(RR::Error error) {
			}
		}

		void move_active (&rrboard_widget_t widget, string dir) {
			try {
				RR::Direction direction = RR::direction(dir);
				union switch (widget.active_robot) {
				case robot r:
					widget.move_callback(r.color, direction);
					break;
				default:
				}
			} catch RR::rr_error(RR::Error error) {
			}
		}

		protected void key (&rrboard_widget_t widget, &key_event_t event) {

			if (event.type != key_type_t.press)
				return;

			switch (event.key) {
			case "r": case "R":
			case "g": case "G":
			case "b": case "B":
			case "y": case "Y":
				set_active (&widget, event.key);
				break;
			case " ":
				set_active (&widget, "whirl");
				break;
			case "Left": case "w": case "W":
				move_active(&widget, "west");
				break;
			case "Right": case "e": case "E":
				move_active(&widget, "east");
				break;
			case "Up": case "n": case "N":
				move_active(&widget, "north");
				break;
			case "Down": case "s": case "S":
				move_active(&widget, "south");
				break;
			}
		}

		typedef struct { int x, y; } position_t;

		/*
		 * A bit expensive, but it's more reliable than trying to
		 * keep track of robot positions separately
		 */
		position_t find_robot (&rrboard_widget_t widget, RR::Robot robot) {
			for (int y = 0; y < RR::Height; y++)
				for (int x = 0; x < RR::Width; x++) {
					union switch (widget.board[x,y].robot) {
					case robot r:
						if (r.color == robot.color)
							return (position_t) { .x = x, .y = y };
						break;
					default:
					}
				}
			return (position_t) { .x = 0, .y = 0 };
		}

		protected void button (&rrboard_widget_t widget, &button_event_t event) {

			/* Convert button position to board location */
			Draw::transform_t	t = transform(&widget);
			int			x = floor ((event.x - t.xoff) / t.xscale / Draw::cell_width);
			int			y = floor ((event.y - t.yoff) / t.yscale / Draw::cell_height);

			enum switch (event.type) {
			case press:
				RR::Object	object = widget.board[x,y];

				/* Clicking on a robot selects that robot
				 */
				union switch (object.robot) {
				case robot r:
					set_active_robot(&widget, r);
					break;
				default:
				}
				break;
			case release:

				/* Releasing with an active robot moves the robot
				 * towards the point of release
				 */
				union switch (widget.active_robot) {
				case robot r:
					position_t	robot_pos = find_robot(&widget, r);
					int	dx = x - robot_pos.x;
					int	dy = y - robot_pos.y;

					if (abs (dx) > abs (dy)) {
						if (dx < 0)
							move_active(&widget, "west");
						else if (dx > 0)
							move_active(&widget, "east");
					} else {
						if (dy < 0)
							move_active(&widget, "north");
						else if (dy > 0)
							move_active(&widget, "south");
					}
					break;
				default:
					break;
				}
				break;
			default:
			}
		}

		protected void set_timer (&rrboard_widget_t widget, real time) {
			Timer::set_timer(widget.timer, time);
		}
		
		protected void stop_timer (&rrboard_widget_t widget) {
			Timer::stop_timer(widget.timer);
		}

		protected void hide_timer (&rrboard_widget_t widget) {
			Timer::stop_timer(widget.timer);
			Timer::hide(widget.timer);
		}

		protected void show_timer (&rrboard_widget_t widget) {
			Timer::show(widget.timer);
		}

		public *rrboard_widget_t new(&nichrome_t nichrome,
					     void(RR::Color color, RR::Direction dir) move_callback){
			&rrboard_widget_t	widget = &(rrboard_widget_t) {};

			widget.timer = Timer::new(&nichrome);	/* make sure timer is above rrboard */
			Widget::init(&nichrome, &widget);
			widget.draw = draw;
			widget.outline = outline;
			widget.natural = natural;
			widget.configure = configure;
			widget.key = key;
			widget.button = button;
			widget.active_robot = RobotOrNone.none;
			widget.move_callback = move_callback;
			widget.board = (RR::Board) { { { .robot = RobotOrNone.none,
							 .target = TargetOrNone.none,
							 .walls = { .left = false, .right = false,
								  .above = false, .below = false }
					} ... } ... };
			return &widget;
		}
	}
}
