using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Collections;
using System.Threading;
using System.Windows.Forms;
using QCGM;
using QCGM.Exceptions;
using QCGM.Forms;
using QCGM.Boards;


namespace QCGM.Net
{
	/// <summary>
	/// Summary description for Server.
	/// </summary>
	public class Server
	{
		#region Private and Protected data members

		private bool listening;
		public bool Listening
		{
			get
			{
				return this.listening;
			}
		}

		private Socket socketListener;

		protected ArrayList ConnectedUsers = new ArrayList();
		private Object Synchronizer = new Object();

		private string serverName = Settings.DefaultServerName;
		private int serverPort = Settings.DefaultServerPort;

		private System.Windows.Forms.Timer timer1 = new System.Windows.Forms.Timer();
		private int ticks=1600000;
		private int ticksReset;

		public int SecondsLeft
		{
			get
			{
				return ticks;
			}
		}

		protected int MyPlayerTurn;

		protected Board2 MyBoard; // The server's copy of the board.
		private bool active;
		private int aliveCount=0;

		#endregion

		#region Public data members
		
		public event ServerEventHandler EventHandler;
		
		#endregion

		#region Constructors / Destructors

		public enum ServerStates { Reset, Online };
		public ServerStates State = ServerStates.Reset;

		public void DisconnectAllUsers()
		{
			lock(ConnectedUsers)
			{
				foreach(User u in ConnectedUsers)
				{
					u.Disconnect();
				}
			}
			ConnectedUsers.Clear();
		}

		/*
		public void Reset()
		{
			if(State != ServerStates.Reset)
			{
				PostEvent(new ServerEventArgs(new Net.Messages.Say("Resetting." )));
				if(this.socketListener != null && this.socket.Connected)
				{
					this.socket.Shutdown(SocketShutdown.Both);
					this.socket.Close();
					this.socket = null;
				}
				
				if(this.WorkerThread != null && this.WorkerThread.ThreadState == ThreadState.Running)
				{
					this.WorkerThread.Abort();
					this.WorkerThread.Join();
					this.WorkerThread = null;
				}

				this.MyPlayerTurn = -1;

				active=false;
				this.ticksReset=Int32.MaxValue;

				PostEvent(new ServerEventArgs(new Net.Messages.RightPanel(Net.Text.Offline)));

				foreach(User u in userList)
				{
					if(u != null)
					{
						if(u.workSocket.Connected)
						{
							u.workSocket.Shutdown(SocketShutdown.Both);
							u.workSocket.Close();
						}
						u.workSocket = null;

						if(u.connectSocket.Connected)
						{
							u.connectSocket.Shutdown(SocketShutdown.Both);
							u.connectSocket.Close();
						}
						u.connectSocket = null;
					}
				}

				userList.Clear();

				this.listening=false;

				this.State = ServerStates.Reset;
			}
			else
			{
				// nothing to do! yay
			}
		}*/
		

		private void CheckAliveCounts()
		{
			lock(this.Synchronizer)
			{
				foreach(User u in this.ConnectedUsers)
				{
					if(u.AliveCount - this.aliveCount < 0)
					{
						PostEvent(new ServerEventArgs(new Net.Messages.Say(u.name + " may be disconnected!")));
						//MessageBox.Show(u.name + " may be disconnected!");
					}
				}
			}
		}


		public Server(Board2 board)
		{
			socketListener = null;

			serverName = Settings.DefaultServerName;
			serverPort = Settings.DefaultServerPort;
			this.MyBoard = board;
			this.MyPlayerTurn = -1;

			// 
			// timer1
			// 
			timer1.Tick += new System.EventHandler(this.timer1_Tick);
			timer1.Interval = 1000;
			timer1.Start();
			
			active=false;
			listening=false;
			this.ticksReset=Int32.MaxValue;

			PostEvent(new ServerEventArgs(new Net.Messages.RightPanel(QCGM.Text.Offline)));
		}

		~Server()
		{
			DisconnectAllUsers();
			StopListening();
		}

		#endregion

		#region Public Accessor Methods and Delegates

		public delegate void ServerEventHandler(object sender, ServerEventArgs e);

		public Board2 Board
		{
			get
			{
				return this.MyBoard;
			}
		}
		
		public int PlayerTurn
		{
			get
			{
				return MyPlayerTurn;
			}
			set
			{
				if(value == 1 || value == 2)
				{
					MyPlayerTurn = value;
				}
			}
		}

		public int Port
		{
			set
			{
				this.serverPort = value;
			}
			get
			{
				return this.serverPort;
			}
		}

		public string Name
		{
			set
			{
				this.serverName = value;
			}
			get
			{
				return this.serverName;
			}
		}

		#endregion

		#region Events

		/// <summary>
		/// Delegate functions control events. This function acts as a bridge.
		/// </summary>
		/// <param name="e"></param>
		protected virtual void PostEvent(ServerEventArgs e)
		{
			if (EventHandler != null)
				EventHandler(this, e);
		}

		private void timer1_Tick(object sender, System.EventArgs e)
		{
			if(this.active)
			{
				this.ticks--;
				PostEvent(new ServerEventArgs(new Net.Messages.Timer(ticks)));
				
				if(ticks % 10 == 0)
				{
					CheckAliveCounts();
					this.aliveCount++;
					MessageAll("keepalive " + this.aliveCount);
				}
				
				MessageAll("timeleft " + this.ticks);
				
				if(ticks <= 0)
				{
					NextTurn();
				}
			}
		}

		#endregion

		private void NextTurn()
		{
			if (PlayerTurn == 1)
			{
				PlayerTurn = 2;
			}
			else
			{
				PlayerTurn = 1;
			}
		
			int Winner = TestVictory();
			if(Winner != -1 && !Board.someoneWon)
			{
				MessageAll("Winner " + Winner);
				Board.someoneWon = true;
				PostEvent(new ServerEventArgs(new Net.Messages.Winner(Winner, (Winner==1?Board.Player1.Pawn.Name:Board.Player2.Pawn.Name))));
				this.active = false;
			}
			
			// Message everyone for next turn.
			
			MessageAll("PlayerTurn " + PlayerTurn);

			this.ticks = ticksReset;
			PostEvent(new ServerEventArgs(new Net.Messages.Timer(ticks)));

			//string name = (val==1?Board.Player1.Pawn.Name:Board.Player2.Pawn.Name);

			PostEvent(new ServerEventArgs(new Net.Messages.WhoseTurn(PlayerTurn, (PlayerTurn==1?Board.Player1.Pawn.Name:Board.Player2.Pawn.Name))));
			
			//PostEvent(new ServerEventArgs(new Net.Messages.WhoseTurn(PlayerTurn, name)));

			this.Board.UpdateBFS(this.PlayerTurn == 1 ? this.Board.Player1 : this.Board.Player2);
		}

		#region Graphics

		public void DrawBoard()
		{
			this.Board.Draw();
		}

		#endregion

		#region Starting / Stopping the server

		public void Listen(int timeout)
		{
			if(listening == false)
			{
				this.ticksReset = timeout;
				
				active = false;

				this.aliveCount = 1;
				
				if(socketListener != null)
				{
					try
					{
						socketListener.Shutdown(SocketShutdown.Both);
					}
					catch(ObjectDisposedException)
					{
						// Don't care
					}
					catch(SocketException)
					{
						// Don't care
					}
					finally
					{
						socketListener.Close();
					}
					socketListener = null;
				}
				socketListener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

				IPEndPoint ipLocal = new IPEndPoint (IPAddress.Any, this.serverPort);
				socketListener.Bind(ipLocal);

				socketListener.Listen(4);

				PostEvent(new ServerEventArgs(new Net.Messages.Say("Listening at endpoint: " + ipLocal.ToString())));

				try
				{
					socketListener.BeginAccept(new AsyncCallback(OnClientConnect), null);
				}
				catch(ObjectDisposedException)
				{
					MessageBox.Show("Could not listen.");
					return;
				}

				PostEvent(new ServerEventArgs(new Net.Messages.RightPanel(QCGM.Text.Online)));

				this.State = ServerStates.Online;
				
				this.listening = true;
			}
			else
			{
				throw new Exception("Already listening.");
			}
		}

		/// <summary>
		/// Stops the server from running.
		/// </summary>
		public void StopListening()
		{
			if(listening)
			{
				try
				{
					this.socketListener.Shutdown(SocketShutdown.Both);
				}
				catch (ObjectDisposedException)
				{
					// shutting down, so don't care
				}
				catch (SocketException)
				{
					// shutting down, so don't care
				}
				finally
				{
					this.socketListener.Close();
				}

				active = false;
				listening=false;
				PostEvent(new ServerEventArgs(new Net.Messages.RightPanel(QCGM.Text.Offline)));
			}
		}

		#endregion

		#region Network Callbacks

		private void OnClientConnect(IAsyncResult asyncResult)
		{
			User user = new User("Waiting for server...");

			try
			{
				user.workSocket = socketListener.EndAccept(asyncResult);
				user.WaitForData(new AsyncCallback(this.OnReceiveData));

				if(ConnectedUsers.Count < 2)
				{
					ConnectUser(user);

					PostEvent(new ServerEventArgs(new Net.Messages.Say("Connection accepted.")));

					CheckForStartOfGame();
				}
				else
				{
					user.SendMessage("server full");
					user.Disconnect();
				}

				socketListener.BeginAccept(new AsyncCallback(OnClientConnect), null);
			}
			catch(ObjectDisposedException)
			{
				//MessageBox.Show("Socket has been closed.");
				//System.Diagnostics.Debugger.Log(0,"1","\n OnClientConnection: Socket has been closed\n");
			}
			catch(SocketException se)
			{
				HandleException(se);
			}
		}

		private bool IsDuplicateName(User user)
		{
			lock(this.Synchronizer)
			{
				foreach(User u in ConnectedUsers)
				{
					if (u != user && u.name == user.name)
					{
						return true;
					}
				}
			}
			return false;
		}

		private void ProcessForDuplicateName(ref User user)
		{
			if (IsDuplicateName(user))
			{
				int i = 2;
						
				while(IsDuplicateName(user))
				{
					if (i > 2)
					{
						user.name = user.name.Substring(0, user.name.Length - 4);
					}
					user.name += " (" + i + ")";
					i++;
				}
			}
		}

		private void ConnectUser(User user)
		{
			ProcessForDuplicateName(ref user);
			
			ConnectedUsers.Add(user);
			user.PlayerNumber = ConnectedUsers.Count;
			user.SendMessage("Player " + user.PlayerNumber);
			if(user.playerNumber == 1)
			{
				Board.Player1.MoveTo(4, 0);
			}
			else if (user.playerNumber == 2)
			{
				Board.Player2.MoveTo(4, 8);
			}
			
			PostEvent(new ServerEventArgs(new Net.Messages.UserLoggedOn(user)));
		}

		private void SendNamesTo(User user)
		{
			lock(Synchronizer)
			{
				foreach(User v in this.ConnectedUsers)
				{
					user.SendMessage("name " + v.playerNumber + " " + v.name);
				}
			}
		}

		private void CheckForStartOfGame()
		{
			if(ConnectedUsers.Count == 2)
			{
				PlayerTurn = Client.PLAYER_BLACK;
				this.active = true;

				foreach(User u in this.ConnectedUsers)
				{
					SendNamesTo(u);
				}
				NextTurn();
			}
		}
/*				
					user.workSocket.BeginReceive(user.buffer, 0, User.BUFFER_SIZE, 0, new AsyncCallback(Read_Callback), user);

					if (ConnectedUsers.Count == 2)
					{
						// start the game, two players have connected.
					}
				}
				else
				{
					MessageBox.Show("Server full.");
					MessageUser(user, "server full");
					DisconnectUser(user);
				}
			}
			catch(Exception e)
			{
				MessageBox.Show("listen point");
				HandleException(e);
				Reset();

				throw new Exception("Stop listening.");
			}

			}
			catch(Exception e)
			{
			}*/
		

		private void OnReceiveData(System.IAsyncResult asyncResult)
		{
			User user = (User)asyncResult.AsyncState;

			try
			{
				int msgLength = user.workSocket.EndReceive(asyncResult);

				if (msgLength > 0)
				{
					string message = Encoding.ASCII.GetString(user.buffer, 0, msgLength);
					message = message.Trim();
					ProcessMessage(user, message);
				}

				user.workSocket.BeginReceive(user.buffer, 0, User.BUFFER_SIZE,0, new AsyncCallback(OnReceiveData), user);
			}
			catch (ObjectDisposedException)
			{
				// Object is disposed, so finish him off
				user.Disconnect();
				ConnectedUsers.Remove(user);
			}
			catch (SocketException)
			{
				user.Disconnect();
				ConnectedUsers.Remove(user);
			}
		}


		/*private void OnRecieveData(IAsyncResult asyncResult)
		{
			User user = (User)asyncResult.AsyncState;
			
			int length = user.workSocket.EndReceive(asyncResult);

			if (length > 0)
			{
				string message = Encoding.ASCII.GetString(user.buffer, 0, length);
				message = message.Trim();
				ProcessMessage(message);
			}

			user.workSocket.BeginReceive(user.buffer, 0, User.BUFFER_SIZE,0, new AsyncCallback(OnReceiveData), user);

		}*/

		private DialogResult HandleException(Exception e)
		{
			if(e is SocketException)
			{
				SocketException se = (SocketException)e;
				return MessageBox.Show(
					"SocketException caught by Server.\r\n\r\n" +
					"Source: " + se.Source + "\r\n\r\n" +
					"Socket error code: " + se.ErrorCode + "\r\n\r\n" +
					"Error code description: " + Client.SocketErrorCodeDescription(se.ErrorCode) + "\r\n\r\n" +
					"Message: " + se.Message,
					"Exception Caught",
					MessageBoxButtons.OK,
					MessageBoxIcon.Error);
			}
			else
			{
				return MessageBox.Show(
					"Exception caught by Server: " + e.GetType() + "\r\n\r\n" +
					"Source: " + e.Source + "\r\n\r\n" +
					"Message: " + e.Message,
					"Exception Caught",
					MessageBoxButtons.OK,
					MessageBoxIcon.Error);
			}
		}


		#endregion

		private int TestVictory()
		{
			if(this.Board.Player1.Position.Y == 8)
			{
				return 1;
			}
			if(this.Board.Player2.Position.Y == 0)
			{
				return 2;
			}
            
			return -1;
		}

		#region Message-Handling
		
		/// <summary>
		/// Splits a string recieved via sockets into separate messages. The delimiter is '.'
		/// </summary>
		/// <param name="user">User who sent the message</param>
		/// <param name="message">A string of commands, delimited by '.'</param>
		private void ProcessMessage(User user, string message)
		{
			string [] messages = message.Split('.');

			foreach(string s in messages)
			{
				try
				{
					if(s.Length > 0)
					{
						HandleMessage(user, s);
					}
				}
				catch(Exceptions.CommandException e)
				{
					user.SendMessage("Say Invalid command: \"" + s + "\"");
					System.Windows.Forms.MessageBox.Show("The server encountered an invalid command:\r\n\r\n" + s + "\r\n\r\n" + e.Message, "Invalid Command", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Information);
				}
			}
		}

		/// <summary>
		/// Handles a message
		/// </summary>
		/// <param name="user">The user who sent the message</param>
		/// <param name="message">The message</param>
		/// <returns>The result MsgHandlerResult.Failure on failure, .Success on success. Easy.</returns>
		protected void HandleMessage(User user, string message)
		{
			string [] words = message.Split(' ');

			string firstword = words[0].ToLower();
			
			if (firstword == "name" && words.Length >= 1)
			{
				string newName = "";

				for(int i=1; i < words.Length; i++)
				{
					newName += words[i] + " ";
				}

				newName = newName.Trim();
				user.name = newName;

				ProcessForDuplicateName(ref user);

				lock(this.Synchronizer)
				{
					foreach(User u in this.ConnectedUsers)
					{
						SendNamesTo(u);
					}
				}
			}
			else if (firstword == "keepalive" && words.Length == 2)
			{
				try
				{
					int val = System.Convert.ToInt32(words[1], 10);
                    
					user.AliveCount = aliveCount;
				}
				catch(Exception)
				{
					throw new CommandException("Keepalive was found to be invalid.");
				}
			}
			else if (firstword == "jump" && words.Length == 2)
			{
				if(user.playerNumber != this.PlayerTurn)
				{
					throw new CommandException("Tried to play move for player " + user.playerNumber + " when it was player " +this.PlayerTurn + "'s move.");
				}

				int i = user.playerNumber;
				string b = words[1].ToLower();

				if(b == "right" && Board.CanJumpRight(i))
				{
					Board.JumpRight(i);
					MessageAll("jump " + i + " right");
				}
				else if (b == "left" && Board.CanJumpLeft(i))
				{
					Board.JumpLeft(i);
					MessageAll("jump " + i + " left");
				}
				else if (b == "up" && Board.CanJumpUp(i))
				{
					Board.JumpUp(i);
					MessageAll("jump " + i + " up");
				}
				else if (b == "down" && Board.CanJumpDown(i))
				{
					Board.JumpDown(i);
					MessageAll("jump " + i + " down");
				}
				else
				{
					throw new CommandException("Move was found to be invalid.");
				}
				
				NextTurn();
			}
			else if (firstword == "move" && words.Length == 2)
			{
				if(user.playerNumber != this.PlayerTurn)
				{
					throw new CommandException("Tried to play move for player " + user.playerNumber + " when it was player " +this.PlayerTurn + "'s move.");
				}

				int i = user.playerNumber;
				string s = words[1].ToLower();

				if (s == "left" && this.Board.CanMoveLeft(i))
				{
					MessageAll("move " + i + " left");
					this.Board.MoveLeft(i);
				}
				else if (s == "right" && this.Board.CanMoveRight(i))
				{
					MessageAll("move " + i + " right");
					this.Board.MoveRight(i);
				}
				else if (s == "up" && this.Board.CanMoveUp(i))
				{
					MessageAll("move " + i + " up");
					this.Board.MoveUp(i);
				}
				else if (s == "down" && this.Board.CanMoveDown(i))
				{
					MessageAll("move " + i + " down");
					this.Board.MoveDown(i);
				}
				else
				{
					throw new CommandException("Move was found to be invalid.");
				}

				NextTurn();
			}
			else if (firstword == "placehwall" && words.Length == 4)
			{
				if(user.playerNumber != this.PlayerTurn)
				{
					throw new CommandException("Tried to play move for player " + user.playerNumber + " when it was player " +this.PlayerTurn + "'s move.");
				}

				int i = user.playerNumber; //this.ConnectedUsers.IndexOf(user) + 1;
				int x = System.Convert.ToInt32(words[1], 10);
				int y = System.Convert.ToInt32(words[2], 10);
				int wi = System.Convert.ToInt32(words[3], 10);

				if(this.Board.CanPlaceWall(i, wi, x, y, Wall.Orientations.Horizontal))
				{
					MessageAll("placehwall " + i + " " + x + " " + y + " " + wi);
					Board.PlaceWall(i, wi, x, y, Wall.Orientations.Horizontal);
					NextTurn();
				}
				else
				{
					throw new CommandException("Move was found to be invalid.");
				}
			}
			else if (firstword == "placevwall" && words.Length == 4)
			{
				if(user.playerNumber != this.PlayerTurn)
				{
					throw new CommandException("Tried to play move for player " + user.playerNumber + " when it was player " +this.PlayerTurn + "'s move.");
				}

				int x = System.Convert.ToInt32(words[1], 10);
				int y = System.Convert.ToInt32(words[2], 10);
				int wi = System.Convert.ToInt32(words[3], 10);
				int i = user.playerNumber;

				if(this.Board.CanPlaceWall(i, wi, x, y, Wall.Orientations.Vertical))
				{
					this.Board.PlaceWall(i, wi, x, y, Wall.Orientations.Vertical);
					MessageAll("placevwall " + i + " " + x + " " + y + " " + wi);
					NextTurn();
				}
				else
				{
					throw new CommandException("Move was found to be invalid.");
				}
			}
			else if (words[0].ToLower() == "say" && message.Length > 4)
			{
				MessageAll("say " + user.name + " says \"" + message.Substring(4, message.Length-4) + "\"");
			}
			else if (firstword == "bye")
			{
				try
				{
					user.Disconnect();
					ConnectedUsers.Remove(user);
				}
				catch(SocketException)
				{
				}
				// TODO: Post message to mainform to kill game entirely!
				// Otherwise game will "continue" causing errors.
				//PostEvent(new ServerEventArgs(new Net.Messages.Say("" + e.ToString() + "Error Sending \"" + msg + "\" to " + user.name + ". Disconnecting user." )));
			}
			else
			{
				throw new CommandException("Command was not recognized.");
			}
		}

		/// <summary>
		/// Sends a message to all users.
		/// </summary>
		/// <param name="msg">The message to be sent to all connected clients.</param>
		public void MessageAll(String msg)
		{
			byte[] buffer = Encoding.ASCII.GetBytes(msg);
			
			lock(Synchronizer)
			{
				PostEvent(new ServerEventArgs(new Net.Messages.Say("Broadcasting \"" + msg + "\"")));

				foreach(User u in ConnectedUsers)
				{
					u.SendMessage(msg);
				}
			}
		}

		#endregion
	}

	public class User
	{ 
		public const int BUFFER_SIZE = 1024;
		public Socket connectSocket;
		public Socket workSocket;
		
		public AsyncCallback workerCallback;

		public byte[] buffer = new byte[BUFFER_SIZE];

		private int aliveCount=1;

		public int AliveCount
		{
			get
			{
				return aliveCount;
			}
			set
			{
				aliveCount = value;
			}
		}

		public String name;		// Name of the user.
		public int playerNumber; // Either player 1 or player 2

		private object Synchronizer = new object(); // Used for synchronization

		public User(String name)
		{ 
			this.name = name;
			this.playerNumber = 0;
		}

		public int PlayerNumber
		{
			get
			{
				return this.playerNumber;
			}
			set
			{
				this.playerNumber = value;
			}
		}

		public void WaitForData(AsyncCallback callback)
		{
			try
			{
				if  ( workerCallback == null ) 
				{
					workerCallback = new AsyncCallback(callback);
				}

				workSocket.BeginReceive(buffer,0,buffer.Length,SocketFlags.None,workerCallback,this);
			}
			catch(SocketException se)
			{
				MessageBox.Show (se.Message );
			}
		}

		public void Disconnect()
		{
			workerCallback = null;
			try
			{
				workSocket.Shutdown(SocketShutdown.Both);
			}
			catch(SocketException)
			{
				// Disconnecting, so don't care
			}
			catch(ObjectDisposedException)
			{
				// Disconnecting, so don't care
			}
			finally
			{
				workSocket.Close();
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="text"></param>
		/// <returns>The number of bytes sent</returns>
		public int SendMessage(string text)
		{
			text = text.Trim(); // always trim extra whitespace off end of messages.
			
			// make sure message ends with period.
			if(text.EndsWith(".") == false)
			{
				text += ".";
			}

			byte[] buffer = Encoding.ASCII.GetBytes(text);

			lock(Synchronizer)
			{
				return workSocket.Send(buffer, 0, buffer.Length, SocketFlags.None);
			}
		}
	}

	public enum MsgHandlerResult { Failure, Success };

	public class ServerEventArgs : EventArgs
	{
		public object stateObject;

		public ServerEventArgs(object stateObject)
		{
			this.stateObject = stateObject;
		}
	}
}

namespace QCGM.Net.Messages
{
	/// <summary>
	/// Generic server message. This class only contains a timestamp.
	/// </summary>
	public abstract class Message
	{
		public DateTime timeStamp; // All server commands have an accessor to retrieve the exact time the message was created.

		public DateTime TimeStamp
		{
			get
			{
				return this.timeStamp;
			}
		}

		public Message()
		{
			this.timeStamp = DateTime.UtcNow;
			// empty constructor
		}
	}

	public abstract class TextMessage : Message
	{
		string text;

		public string Text
		{
			get
			{
				return this.text;
			}
		}

		public TextMessage(string text)
		{
			this.text = text;
		}
	}

	public class UserLoggedOn : Message
	{
		User user;

		public User User
		{
			get
			{
				return this.user;
			}
		}

		public UserLoggedOn(User user)
		{
			this.user = user;
		}
	}

	public class Say : TextMessage
	{
		public Say(string message) : base(message)
		{
			// nothing
		}
	}

	public class Stop : Message
	{
		public Stop()
		{
			// nothing
		}
	}

	public class Start : Message
	{
		public Start()
		{
			// nothing
		}
	}

	public class RightPanel : TextMessage
	{
		public RightPanel(string text) : base(text)
		{
			// nothing
		}
	}

	public class LeftPanel : TextMessage
	{
		public LeftPanel(string text) : base(text)
		{
			// nothing
		}
	}

	public class MiddlePanel : TextMessage
	{
		public MiddlePanel(string text) : base(text)
		{
			// nothing
		}
	}

	public class Move : Message
	{
		private int player;
		private Direction dir;
		
		public Move(int player, Direction dir)
		{
			this.player = player;
			this.dir = dir;
		}

		public Direction Direction
		{
			get
			{
				return this.dir;
			}
		}

		public int Player
		{
			get
			{
				return this.player;
			}
		}
	}

	/// <summary>
	/// Indicates that it is this client's turn to play.
	/// </summary>
	public class WhoseTurn : Message
	{
		private int turn;
		private string name;

		public int Turn
		{
			get
			{
				return turn;
			}
		}

		public string Name
		{
			get
			{
				return name;
			}
		}

		public WhoseTurn(int turn, string name)
		{
			this.turn = turn;
			this.name = name;
		}
	}

	public class Winner : Message
	{
		private int playerNum;
		private string name;

		public int Number
		{
			get
			{
				return playerNum;
			}
		}
		
		public string Name
		{
			get
			{
				return name;
			}
		}

		public Winner(int playerNum, string name)
		{
			this.playerNum = playerNum;
			this.name = name;
		}
	}

	public class Timer : Message
	{
		private int timeRemaining;

		public int TimeRemaining
		{
			get
			{
				return timeRemaining;
			}
		}

		public Timer(int time)
		{
			timeRemaining = time;
		}
	}
}

namespace QCGM.Exceptions
{
	public class CommandException : Exception
	{
		public CommandException(string message) : base(message) {}
	}
	
	public class ServerException: ApplicationException
	{
		public ServerException() {}
		public ServerException(string message): base(message) {}
		public ServerException(string message, Exception inner) : base(message, inner) {}
	}
}

