using System;
using Tao.OpenGl;
using Tao.Platform.Windows;
using QCGM;

namespace QCGM
{
	public enum ObjectNames { Wall, Player1, Player2, Board };
	public enum Direction { Up, Down, Left, Right };
	public enum WallOrientation { Vertical, Horizontal };

	public class InvalidBoardLocationException: ApplicationException
	{
		public InvalidBoardLocationException() {}
		public InvalidBoardLocationException(string message): base(message) {}
		public InvalidBoardLocationException(string message, Exception inner) : base(message, inner) {}
	}

	public class InvalidWallOrientationException: ApplicationException
	{
		public InvalidWallOrientationException() {}
		public InvalidWallOrientationException(string message): base(message) {}
		public InvalidWallOrientationException(string message, Exception inner) : base(message, inner) {}
	}

	public class InvalidDirectionException: ApplicationException
	{
		public InvalidDirectionException() {}
		public InvalidDirectionException(string message): base(message) {}
		public InvalidDirectionException(string message, Exception inner) : base(message, inner) {}
	}

	public class InvalidPlayerException: ApplicationException
	{
		public InvalidPlayerException() {}
		public InvalidPlayerException(string message): base(message) {}
		public InvalidPlayerException(string message, Exception inner) : base(message, inner) {}
	}

	public interface IPoint2Di
	{
		int X
		{
			get;
			set;
		}

		int Y
		{
			get;
			set;
		}
	}

	public class Point2Di : IPoint2Di
	{
		public int myX;
		public int myY;

		public Point2Di(int x, int y)
		{
			X = x;
			Y = y;
		}

		public Point2Di()
		{
			X = 0;
			Y = 0;
		}

		public int X
		{
			get
			{
				return myX;
			}
			set
			{
				this.myX = value;
			}
		}

		public int Y
		{
			get
			{
				return myY;
			}
			set
			{
				this.myY = value;
			}
		}
	}

	public class Vertex2f
	{
		public float x, y;

		public Vertex2f(float x, float y)
		{
			this.x = x;
			this.y = y;
		}

		public Vertex2f()
		{
			this.x = 0;
			this.y = 0;
		}
	}

	public class Vertex3f : Vertex2f
	{
		public float z;

		public Vertex3f(float x, float y, float z) : base(x, y)
		{
			this.z = z;
		}

		public Vertex3f() : base()
		{
			this.z = 0;
		}
	}

	public class Color4f
	{
		public float red, green, blue, alpha;

		public Color4f(float r, float g, float b, float a)
		{
			this.red = r;
			this.green = g;
			this.blue = b;
			this.alpha = a;
		}

		public Color4f()
		{
			this.red = 0;
			this.green = 0;
			this.blue = 0;
			this.alpha = 0;
		}
	}

	/// <summary>
	/// Summary description for Board.
	/// </summary>
	public class Board
	{
		private byte[] cell;
		private byte numWallsP1;
		private byte numWallsP2;
		private Camera myCamera;

		public enum Players { Player1=1, Player2=2 };

		public class PlayerBits
		{
			public const byte Player1	= 0x01;
			public const byte Player2	= 0x02;
		}

		public class WallBits
		{
			public const byte Top		= 0x04;
			public const byte Left		= 0x08;
			public const byte Bottom	= 0x10;
			public const byte Right		= 0x20;
		}

		public class InversePlayerBits
		{
			public const byte Player1	= 0xFE;
			public const byte Player2	= 0xFD;
		}

		public class InverseWallBits
		{
			public const byte Top		= 0xFB;
			public const byte Left		= 0xF7;
			public const byte Bottom	= 0x77;
			public const byte Right		= 0xDF;
		}

		public Board()
		{
			this.cell = new byte[81];
			ResetBoard();
			myCamera = new Camera(0, 0, 0, 0, 0, 0, 0, 0, 0);
		}

		public bool CanPlaceWall(Board.Players player)
		{
			switch (player)
			{
				case Board.Players.Player1:
					if (this.numWallsP1 > 0) return true;
					break;
				case Board.Players.Player2:
					if (this.numWallsP2 > 0) return true;
					break;
				default:
					throw new InvalidPlayerException("\"" + player + "\" is not a valid player.");
			}
		
			return false;
		}

		public bool CanPassThrough(int x, int y, Direction direction)
		{
			if (direction == Direction.Right)
			{
				return (CanPassUp(x, y) && CanPassUp(x+1, y)) ? true : false;
			}
			else if (direction == Direction.Down)
			{
				return (CanPassDown(x, y) && CanPassDown(x+1, y)) ? true : false;
			}
			else if (direction == Direction.Left)
			{
				return (CanPassLeft(x, y) && CanPassLeft(x, y-1)) ? true : false;
			}
			else if (direction == Direction.Right)
			{
				return (CanPassRight(x, y) && CanPassRight(x, y-1)) ? true : false;
			}
			else
			{
				throw new InvalidDirectionException("Error: " + direction + " is not a valid direction!");
			}
		}

		private Point2Di GetPlayerLocation(byte bits)
		{
			for(int i=0; i < 9; i++)
			{
				for(int j=0; j < 9; j++)
				{
					if ((int)(this.cell[i+j*8] & bits) != 0)
					{
						return new Point2Di(i, j);
					}
				}
			}
			return null;
		}
		
		public bool Move(int player, Direction dir)
		{
			byte bits = 0;
			byte ibits = 0;

			if (player == 1)
			{
				bits = PlayerBits.Player1;
				ibits = InversePlayerBits.Player1;
			}
			else if (player == 2)
			{
				bits = PlayerBits.Player2;
				ibits = InversePlayerBits.Player2;
			}
			else
			{
				return false;
			}

			Point2Di p = GetPlayerLocation(bits);
			if (p == null)
			{
				return false;
			}

			int x = p.X;
			int y = p.Y;

			if (dir == Direction.Down && CanPassDown(p.X, p.Y))
			{
				y++;
				this.cell[p.X+p.Y*8] &= ibits;
				this.cell[x+y*8] |= bits;
				return true;
			}
			else if (dir == Direction.Left && CanPassLeft(p.X, p.Y))
			{
				x--;
				this.cell[p.X+p.Y*8] &= ibits;
				this.cell[x+y*8] |= bits;
				return true;
			}
			else if (dir == Direction.Right && CanPassRight(p.X, p.Y))
			{
				x++;
				this.cell[p.X+p.Y*8] &= ibits;
				this.cell[x+y*8] |= bits;
				return true;
			}
			else if (dir == Direction.Up && CanPassUp(p.X, p.Y))
			{
				y--;
				this.cell[p.X+p.Y*8] &= ibits;
				this.cell[x+y*8] |= bits;
				return true;
			}
			else
			{
				return false;
			}
		}

		private bool CanPassUp(int x, int y)
		{
			if (x <= 8 && x >= 0 && y <= 8 && y >= 1)
				if ((int)(this.cell[x+y*8] & Board.WallBits.Top) == 0 &&
						(int)(this.cell[x+(y-1)*8] & Board.WallBits.Bottom) == 0)
					return true;
			return false;
		}

		private bool CanPassDown(int x, int y)
		{
			if (x <= 8 && x >= 0 && y <= 7 && y >= 0)
				if ((int)(this.cell[x+y*8] & Board.WallBits.Bottom) == 0 &&
						(int)(cell[x+(y+1)*8] & Board.WallBits.Top) == 0)
					return true;
			return false;
		}

		private bool CanPassLeft(int x, int y)
		{
			if (x <= 8 && x >= 1 && y <= 8 && y >= 0)
				if ((int)(cell[x + y*8] & Board.WallBits.Left) == 0 &&
						(int)(cell[(x-1) + (y)*8] & Board.WallBits.Right) == 0)
					return true;
			return false;
		}

		private bool CanPassRight(int x, int y)
		{
			if (x <= 7 && x >= 0 && y <= 8 && y >= 0)
				if ((int)(cell[x + y*8] & Board.WallBits.Right) == 0 &&
						(int)(cell[(x+1) + (y)*8] & Board.WallBits.Left) == 0)
					return true;
			return false;
		}

		public void RaiseWall(int x, int y, WallOrientation orientation)
		{
			switch (orientation)
			{
				case WallOrientation.Vertical:
					if (x >= 0 && x <= 8 && y >= 0 && y <= 7)
					{
						cell[x + y*8] |= Board.WallBits.Right;
						cell[x+1 + y*8] |= Board.WallBits.Left;
					}
					else throw new InvalidBoardLocationException("Error: Error placing vertical wall at position " + x + "," + y + ".");
					break;
				case WallOrientation.Horizontal:
					if (x >= 0 && x  <= 7 && y >= 0 && y <= 8)
					{
						cell[x+y*8] |= Board.WallBits.Top;
						cell[x+(y+1)*8] |= Board.WallBits.Bottom;
					}
					else throw new InvalidBoardLocationException("Error: Error placing vertical wall at position " + x + "," + y + ".");
					break;
			}
		}

		public void ResetBoard()
		{
			// Reset the board to zeroes.
			for(int i = 0; i < 81; i++)
				cell[i] = 0;

			// Put Players 1 and 2 on the board.
			cell[4 + (0*8)] |= Board.PlayerBits.Player1;
			cell[4 + (8*8)] |= Board.PlayerBits.Player2;

			// each player starts with 10 walls.
			this.numWallsP1 = 10;
			this.numWallsP2 = 10;
		}

		private void DrawSquare(Vertex3f pos, Vertex2f size)
		{
			Gl.glVertex3f(pos.x, pos.y, pos.z);
			Gl.glVertex3f(pos.x+size.x, pos.y, pos.z);
			Gl.glVertex3f(pos.x+size.x, pos.y + size.y, pos.z);
			Gl.glVertex3f(pos.x, pos.y+size.y, pos.z);
		}

		private void DrawBox(Vertex3f pos, Vertex3f size)
		{
			// bottom
			Gl.glVertex3f(pos.x, pos.y, pos.z);
			Gl.glVertex3f(pos.x+size.x, pos.y, pos.z);
			Gl.glVertex3f(pos.x+size.x, pos.y+size.y, pos.z);
			Gl.glVertex3f(pos.x, pos.y+size.y, pos.z);

			// top
			Gl.glVertex3f(pos.x, pos.y, pos.z+size.z);
			Gl.glVertex3f(pos.x+size.x, pos.y, pos.z+size.z);
			Gl.glVertex3f(pos.x+size.x, pos.y+size.y, pos.z+size.z);
			Gl.glVertex3f(pos.x, pos.y+size.y, pos.z);

			// left side
			Gl.glVertex3f(pos.x, pos.y+size.y, pos.z+pos.z);
			Gl.glVertex3f(pos.x, pos.y+size.y, size.z);
			Gl.glVertex3f(pos.x, pos.y, size.z);
			Gl.glVertex3f(pos.x, pos.y, pos.z+size.z);

			// right side
			Gl.glVertex3f(pos.x+size.x, pos.y+size.y, pos.z+size.z);
			Gl.glVertex3f(pos.x+size.x, pos.y+size.y, size.z);
			Gl.glVertex3f(pos.x+size.x, pos.y, size.z);
			Gl.glVertex3f(pos.x+size.x, pos.y, pos.z+size.z);

			// back
			Gl.glVertex3f(pos.x+size.x, pos.y+size.y, pos.z+size.z);
			Gl.glVertex3f(pos.x, pos.y+size.y, pos.z+size.z);
			Gl.glVertex3f(pos.x, pos.y+size.y, size.z);
			Gl.glVertex3f(pos.x+size.x, pos.y+size.y, size.z);

			// front
			Gl.glVertex3f(pos.x+size.x, pos.y, pos.z+size.z);
			Gl.glVertex3f(pos.x, pos.y, pos.z+size.z);
			Gl.glVertex3f(pos.x, pos.y, size.z);
			Gl.glVertex3f(pos.x+size.x, pos.y, size.z);
		}

		public void Draw()
		{
			Vertex3f pos;
			Vertex3f size = new Vertex3f(0.8f, 0.8f, 0.0f);
			Color4f c1 = new Color4f(0.96f, 0.96f, 1.0f, 1.0f);
			Color4f c2 = new Color4f(0.0f, 0.0f, 0.0f, 1.0f);

			//Gl.glInitNames();

			for(int x=0; x < 9; x++)
			{
				for(int y=0; y < 9; y++)
				{
					//drawBox(x, y, 0.0f, 0.8f, 0.8f, 0.15f, 0.5f, 0.7f, 1.0f, 0.8f);
					pos = new Vertex3f((float)x, (float)y, 0.0f);

					Gl.glColor4f(c1.red, c1.green, c1.blue, c1.alpha);
					Gl.glBegin(Gl.GL_QUADS);
					DrawSquare(pos, size);
					Gl.glEnd();

					Gl.glColor4f(c2.red, c2.green, c2.blue, c2.alpha);
					Gl.glBegin(Gl.GL_LINE_LOOP);
					DrawSquare(pos, size);
					Gl.glEnd();

					// Draw Player 1
					if((cell[x+y*8] ^ Board.PlayerBits.Player1) == 0)
					{
						Vertex3f pos2 = new Vertex3f((float)x+0.2f, (float)y+0.2f, 0.1f);
						Vertex3f size2 = new Vertex3f(0.6f, 0.6f, 0.15f);

						Gl.glColor4f(1.0f, 0.6f, 0.6f, 0.9f);
						Gl.glBegin(Gl.GL_QUADS);
						DrawBox(pos2, size2);
						Gl.glEnd();

						/*Gl.glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
						Gl.glBegin(Gl.GL_LINE_STRIP);
						drawBox(pos2, size2);
						Gl.glEnd();*/
					}

					// Draw Player 2
					if((cell[x+y*8] ^ Board.PlayerBits.Player2) == 0)
					{
						//drawBox(x+0.2f, y+0.2f, 0.26f, 0.4f, 0.4f, 1.0f, 0.0f, 1.0f, 0.6f, 1.0f);
						Vertex3f pos2 = new Vertex3f((float)x+0.2f, (float)y+0.2f, 0.1f);
						Vertex3f size2 = new Vertex3f(0.6f, 0.6f, 0.15f);

						Gl.glColor4f(0.6f, 1.0f, 0.6f, 0.9f);
						Gl.glBegin(Gl.GL_QUADS);
						DrawBox(pos2, size2);
						Gl.glEnd();

						/*Gl.glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
						Gl.glBegin(Gl.GL_LINE_STRIP);
						drawBox(pos2, size2);
						Gl.glEnd();*/
					}

					if((int)(cell[x+y*8] & Board.WallBits.Bottom) != 0)
					{
						//drawBox(x, y+0.2f, 0.26f, 0.6f, 0.6f, 0.25f, 1.0f, 0.0f, 0.2f, 1.0f);
					}
				}
			}
		}
	}
}
