using System;

using Gtk;
using Gdk;
using Gnome;


public class Fifteen
{
	const int SCRAMBLE_MOVES = 256;
	static Gtk.Window window = null;
	static Canvas canvas = null;
	static BoardPiece [] board;
		
	static void Main (string [] args)
	{
		if (args.Length > 0 && args [0] == "debug")
			BoardPiece.Debug = true;
		Program fifteen = new Program ("Fifteen", "0.1", Modules.UI, args);
		
		window = new Gtk.Window ("Fifteen #");
		VBox box = new VBox (false, 0);
		window.Add (box);
		window.DefaultWidth = 300;
		window.DefaultHeight = 300;
		
		VBox vbox = new VBox (false, 4);
		vbox.BorderWidth = 4;

		MenuBar menu = Create_Menu ();
		box.PackStart (menu, false, false, 0);
		box.PackStart (vbox, false, false, 0);
		window.DeleteEvent += new DeleteEventHandler (Window_Delete);

		Alignment alignment = new Alignment (0.5f, 0.5f, 0.0f, 0.0f);
		vbox.PackStart (alignment, true, true, 4);

		Frame frame = new Frame (null);
		frame.ShadowType = ShadowType.In;
		alignment.Add (frame);

		canvas = new Canvas ();
		canvas.SetSizeRequest (BoardPiece.PIECE_SIZE * 4 + 1, BoardPiece.PIECE_SIZE * 4 + 1);
		canvas.SetScrollRegion (0, 0, BoardPiece.PIECE_SIZE * 4 + 1, BoardPiece.PIECE_SIZE * 4 + 1);
		frame.Add (canvas);
		
		board = new BoardPiece [16];

		for (int i = 0; i < 15; i ++) {					
			int y = i /4;
			int x = i % 4;
			board [i] = new BoardPiece (canvas.Root (), board, x, y, i);
		}

		box.PackStart (new HSeparator (), false, false, 0);
		HBox hbox = new HBox (false, 4);
		box.PackStart (hbox, true, false, 4);

		// Scramble button here.
		Button scramble = new Button ("_Scramble");
		scramble.BorderWidth = 8;
		scramble.Clicked += new EventHandler (Scramble);
		scramble.CanDefault = true;
		hbox.PackStart (scramble, true, true, 4);
		scramble.GrabDefault ();

		// Close button
		Button close = new Button ("_Close");
		close.BorderWidth = 8;
		close.Clicked += new EventHandler (Quit);
		hbox.PackStart (close, true, true, 4);
		
		window.ShowAll ();
		fifteen.Run ();
	}

	static void Quit (object o, EventArgs args)
	{
		Application.Quit ();
	}

	static Gtk.MenuBar Create_Menu ()
	{
		MenuBar mb = new MenuBar ();
		Menu file_menu = new Menu ();

		ImageMenuItem scramble_item = new ImageMenuItem ("_Scramble");
		scramble_item.Image = new Gtk.Image (Gtk.Stock.Refresh, IconSize.Menu);
		scramble_item.Activated += new EventHandler (Scramble);
		
		ImageMenuItem quit_item = new ImageMenuItem ("_Quit");
		quit_item.Image = new Gtk.Image (Gtk.Stock.Quit, IconSize.Menu);
		quit_item.Activated += new EventHandler (Quit);

		file_menu.Append (scramble_item);
		file_menu.Append (new SeparatorMenuItem ());
		file_menu.Append (quit_item);
	
		MenuItem file_item = new MenuItem ("_File");
		file_item.Submenu = file_menu;
		mb.Append (file_item);

		Menu help_menu = new Menu ();
		MenuItem help_item = new MenuItem ("_Help");
		help_item.Submenu = help_menu;

		ImageMenuItem about = new ImageMenuItem (Gnome.Stock.About, new AccelGroup ());
		about.Activated += new EventHandler (About_Box);
		help_menu.Append (about);
		mb.Append (help_item);

		return mb;
	}

	static void About_Box (object o, EventArgs args)
	{
		string [] authors = new string [] {
			"Duncan Mak (duncan@ximian.com)",
		};

		string [] documenters = new string [] {};
		string translaters = null;
		Pixbuf pixbuf = new Pixbuf ("pixmaps/gnome-color-browser.png");

		Gnome.About about = new Gnome.About ("Fifteen #", "0.1",
						     "Copyright (C) 2002, Ximian Inc.",
						     "A C# port of Fifteen, a gnomecanvas demo",
						     authors, documenters, translaters,
						     pixbuf);
		about.Show ();
	}


	static void Scramble (object o, EventArgs args)
	{
		Random rand = new Random ();
		int blank;
		int position;		

		// find blank spot
		for (position = 0; position < 16; position ++)
			if (board [position] == null)
				break;

		for (int i = 0; i < SCRAMBLE_MOVES; i++) {
		retry:
			int dir = rand.Next (4);
			int x = 0, y = 0;

			if ((dir == 0) && (position > 3)) // up
				y = -1;
			else if ((dir == 1) && (position < 12)) // down
				y = 1;
			else if ((dir == 2) && ((position % 4) != 0)) // left
				x = -1;
			else if ((dir == 3) && ((position % 4) != 3)) // right
				x = 1;
			else 
				goto retry;

			int old_position = position + y * 4 + x;
			board [position] = board [old_position];
			board [position].Position = position;
			board [old_position] = null;
			board [position].Move (-x * BoardPiece.PIECE_SIZE, -y * BoardPiece.PIECE_SIZE);
			canvas.UpdateNow ();
			position = old_position;
		}			
	}

	static void Window_Delete (object o, DeleteEventArgs args)
	{
		Application.Quit ();
		args.RetVal = true;
	}
}

public class BoardPiece : Gnome.CanvasGroup
{
	public static int PIECE_SIZE = 50;
	public int Number;
	int position;
	public CanvasText Text;
	public BoardPiece [] Board;
	public static bool Debug = false;
	
	public BoardPiece (CanvasGroup group, BoardPiece [] board, int x, int y, int i)
		: base (group, CanvasGroup.GType)
	{
		this.X = (x * PIECE_SIZE);
		this.Y = (y * PIECE_SIZE);
		this.Board = board;
		this.Number = i;
		this.Position = i;		

		CanvasRect rect = new CanvasRect (this);
		rect.X1 = 0.0;
		rect.Y2 = 0.0;
		rect.X2 = PIECE_SIZE;
		rect.Y2 = PIECE_SIZE;
		rect.FillColor = Color;
		rect.OutlineColor = "black";
		rect.WidthPixels = 0;
		
		CanvasText text = new CanvasText (this);
		text.Text = (i + 1).ToString ();
		text.X = PIECE_SIZE / 2.0;
		text.Y = PIECE_SIZE / 2.0;
		text.Font = "Sans Bold 24";
		text.Anchor = AnchorType.Center;
		text.FillColor = "black";

		this.Text = text;
		this.CanvasEvent += new Gnome.CanvasEventHandler (Piece_Event);
	}

	public string Color {
		get {
			int y = Number / 4;
			int x = Number % 4;

			int r = ((4 - x) * 255) / 4;
			int g = ((4 - y) * 255) / 4;
			int b = 128;

			return String.Format ("#{0:x2}{1:x2}{2:x2}", r, g, b);
		}
	}

	public int Position {
		get {
			return position;
		}

		set {
			Board [value] = this;
			position = value;
		}
	}

	static void Piece_Event (object o, Gnome.CanvasEventArgs args)
	{
		BoardPiece piece = (BoardPiece) o;
		Canvas canvas = piece.Canvas;
		double dx = 0.0, dy = 0.0;

		switch (args.Event.Type) {
		case EventType.EnterNotify:
			piece.Text.FillColor = "white";
			args.RetVal = true;
			break;

		case EventType.LeaveNotify:
			piece.Text.FillColor = "black";
			args.RetVal = true;
			break;

		case EventType.ButtonPress:
			int y = piece.Position / 4; 
			int x = piece.Position % 4;
			Print_Position ("from", piece.Position, true);
			
			bool toMove = true;

			if ((y > 0) && (piece.Board [(y - 1) * 4 + x] == null)) {
				dx = 0.0;
				dy = -1.0;
				y --;
			} else if ((y < 3) && (piece.Board [(y + 1) * 4 + x] == null)) {
				dx = 0.0;
				dy = 1.0;
				y ++;
			} else if ((x > 0) && (piece.Board [y * 4 + x - 1] == null)) {
				dx = -1.0;
				dy = 0.0;
				x --;
			} else if ((x < 3) && (piece.Board [y * 4 + x + 1] == null)) {
				dx = 1.0;
				dy = 0.0;
				x ++;
			} else 
				toMove = false;

			if (toMove) {
				int new_position = y * 4 + x;
				Print_Position ("to", new_position, false);
 				piece.Board [piece.Position] = null;
				piece.Position = new_position;
				piece.Move (dx * PIECE_SIZE, dy * PIECE_SIZE);
				canvas.UpdateNow ();
			} else
				Print_Position ("to", piece.Position, false);

			args.RetVal = true;
			break;

		default:
			args.RetVal = false;
			break;
		}
	}

	static void Print_Position (string text, int position, bool newLine)
	{
		if (!Debug)
			return;
		else {
			int x = position / 4;
			int y = position % 4;
			string output = String.Format (" {0} ({1}, {2})", text, x + 1, y + 1);
			if (newLine)
				Console.Write (output);
			else
				Console.WriteLine (output);
		}
	}

}

