using System;
using System.Linq;
using System.Threading;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace WorldOfPeacecraft
{
	public class Parser
	{
		public const string ValueAns = "ans";
		public const string ValueHeight = "height";
		public const string ValueWidth = "width";
		public const string ValueSource = "src";
		public const string ValueSourceId = "srcid";
		public const string ValueText = "txt";
		public const string ValueRound = "round";
		public const string ValueRunning = "running";
		public const string ValueDelay = "delay";
		public const string ValueId = "id";
		public const string ValuePoints = "points";
		public const string ValueTotal = "total";
		public const string ValueDescription = "desc";
		public const string ValueBusy = "busy";
		public const string ValueX = "x";
		public const string ValueY = "y";
		public const string ValueCol = "col";
		public const string ValueRow = "row";
		public const string EValueFight = "FIGHT";
		public const string EValueRest = "REST";
		public const string EValueStag = "STAG";
		public const string EValueBunny = "BUNNY";
		public const string EValueSword = "SWORD";
		public const string EValueMagic = "MAGIC";
		public const string EValueAlchemy = "ALCHEMY";
		public const string EValueWalkable = "WALKABLE";
		public const string EValueSkirmish = "SKIRMISH";
		public const string EValueStaghunt = "STAGHUNT";
		public const string EValueDragon = "DRAGON";
		public const string EValueWall = "WALL";
		public const string EValueForest = "FOREST";
		public const string EValueWater = "WATER";
		public const string EValueHuntable = "HUNTABLE";
		public const string MessUpdate = "upd";
		public const string MessDelete = "del";
		public const string MessMap = "map";
		public const string MessMessage = "mes";
		public const string MessResult = "result";
		public const string MessChallenge = "challenge";
		public const string MessPlayer = "player";
		public const string MessYourid = "yourid";
		public const string MessTime = "time";
		public const string MessOnline = "online";
		public const string MessEntities = "ents";
		public const string MessPlayers = "players";
		public const string MessDragon = "dragon";
		public const string MessMapcell = "cell";
		private Buffer InputBuffer;
		private Thread ParserThread;
		private LinkedList<string> Message;
		private Regex LastLineRegex;
		private Backend Backend;

		public Parser (Backend backend, Buffer buffer)
		{
            InputBuffer = buffer;
			Backend = backend;
			Message = new LinkedList<string> ();
			LastLineRegex = new Regex ("^end:[0-9]+$");
			ParserThread = new Thread (new ThreadStart (this.RunParser));
			ParserThread.IsBackground = true;
		}

		private void RunParser ()
		{
			while (true) {
				Message.AddLast (InputBuffer.NextLine ());
				Console.WriteLine(Message.Last.Value);
				if (IsCompletePackage ()) {
					Parse ();
				}
			}
		}

		private void Parse ()
		{
			string[] aMessage = Enumerable.ToArray (Message);
			Message.Clear();
			try {
				Block mainBlock = new Block (aMessage, 0, aMessage.Length - 1);
				ProcessData (mainBlock);
			} catch (ParsingException e) {
				string errormsg = "Error while parsing message: " + e.Message + "\n";
				errormsg += "Message:";
				foreach (string line in aMessage) {
					errormsg += "\n\t" + line;
				}
				Console.WriteLine(errormsg);
			}
		}

		private void ProcessData (Block parentBlock)
		{
			if (parentBlock.ValueExists (ValueAns)) {
				ProcessAnswer (parentBlock);
				return;
			}
			CheckBlocksSize (parentBlock, 1, 1);
			Block block = parentBlock.GetBlocks ().First.Value;
			switch (block.GetName ()) {
			case MessUpdate:
				ProcessUpdate (block);
				break;
			case MessDelete:
				ProcessDelete (block);
				break;
			case MessMap:
				ProcessMap (block);
				break;
			case MessMessage:
				ProcessMessage (block);
				break;
			case MessResult:
				ProcessResult (block);
				break;
			case MessChallenge:
				ProcessChallenge (block);
				break;
			case MessPlayer:
				ProcessPlayerSelf (block);
				break;
			case MessYourid:
				ProcessYourid (block);
				break;
			case MessTime:
				ProcessTime (block);
				break;
			case MessOnline:
				ProcessOnline (block);
				break;
			case MessEntities:
				ProcessEntities (block);
				break;
			case MessPlayers:
				ProcessEntities (block);
				break;
			default:
				ThrowUnknownBlockException (parentBlock, block); 
				break;
			}
		}

		private void ProcessUpdate (Block updateBlock)
		{
			CheckBlocksSize (updateBlock, 1, 1);
			LinkedList<Block> blocks = updateBlock.GetBlocks ();
			Block block = blocks.First.Value;
			switch (block.GetName ()) {
			case MessDragon:
				ProcessDragon (block);
				break;
			case MessPlayer:
				ProcessPlayer (block);
				break;
			case MessMapcell:
				ProcessMapcell (block);
				break;
			default:
				ThrowUnknownBlockException (updateBlock, block);
				break;
			}
		}

		private void ProcessDelete (Block deleteBlock)
		{
			CheckBlocksSize (deleteBlock, 1, 1);
			LinkedList<Block> blocks = deleteBlock.GetBlocks ();
			Block block = blocks.First.Value;
			switch (block.GetName ()) {
			case MessPlayer:
				Player player = MapPlayer (block);
				lock (Backend) {
					Backend.removePlayer (player);
				}
				Backend.RefreshGui ();
				break;
			case MessDragon:
				Dragon dragon = MapDragon (block);
				lock (Backend) {
					Backend.removeDragon (dragon);
				}
				Backend.RefreshGui ();
				break;
			default:
				ThrowUnknownBlockException(deleteBlock, block);
				break;
			}
		}

		private void ProcessMap (Block mapBlock)
		{
			CheckBlocksSize (mapBlock, 1, 1);
			Block cellsBlock = mapBlock.GetBlocks ().First.Value;
			int height = mapBlock.GetIntValue (ValueHeight);
			int width = mapBlock.GetIntValue (ValueWidth);
			try {
				CheckBlocksSize (cellsBlock, width * height, width * height);
			} catch (ParsingException e) {
				throw new ParsingException ("The received map is " + width + "x" + height + "=" + (width * height) + " cells large, but the received cellblock contained " + cellsBlock.GetBlocks ().Count, e);
			}
			Map map = new Map(height, width);
			foreach (Block cell in cellsBlock.GetBlocks()) {
				map.SetTile(MapMapcell(cell));
			}
			lock (Backend) {
				Backend.SetMap (map);
			}
			Backend.RefreshGui();
		}

		private void ProcessMessage (Block mesBlock)
		{
			// TODO Ausnahmebehandlung von mehrzeiligen Nachrichten?
			int srcid = mesBlock.GetIntValue (ValueSourceId);
			string src = mesBlock.GetStringValue (ValueSource);
			string txt = mesBlock.GetStringValue (ValueText);
			lock (Backend) {
				Backend.AddChatMessage (new Message (srcid, src, txt));
			}
			Backend.RefreshGui ();
		}

		private void ProcessAnswer (Block block)
		{
			// TODO or not TODO
		}

		private void ProcessResult (Block procBlock)
		{
			CheckBlocksSize (procBlock, 1, 1);
			int round = procBlock.GetIntValue (ValueRound);
			bool running = procBlock.GetBoolValue (ValueRunning);
			int delay = procBlock.GetIntValue (ValueDelay);
			LinkedList <Block> block = procBlock.GetBlocks ();
			ProcessOpponent (block.First.Value);
			Result r = new Result(round, running, delay);
		}

		private void ProcessOpponent (Block oppBlock)
		{
			int id = oppBlock.GetIntValue (ValueId);
			int points = oppBlock.GetIntValue (ValuePoints);
			int total = oppBlock.GetIntValue (ValueTotal);
			LinkedList <string> unnamedValue = oppBlock.GetUnnamedValues ();
			string stringValue = unnamedValue.First.Value;
			Decision d;
			switch (stringValue) {
			case EValueAlchemy:
				d = Decision.ALCHEMY;
				break;
			case EValueBunny:
				d = Decision.BUNNY;
				break;
			case EValueFight:
				d = Decision.FIGHT;
				break;
			case EValueMagic:
				d = Decision.MAGIC;
				break;
			case EValueRest:
				d = Decision.REST;
				break;
			case EValueStag:
				d = Decision.STAG;
				break;
			case EValueSword:
				d = Decision.SWORD;
				break;
			default:
				throw new ParsingException("Wrong param"); // TODO Better message
			} 
			Opponent o = new Opponent (id, points, total, d);
		}

		private void ProcessDecision(Block block)
		{
			//TODO when "ProcessAnswer" is done
		}

        
		private void ProcessChallenge (Block challengeBlock)
		{
            int id = challengeBlock.GetIntValue(ValueId);
            GameType type;
			string sType = challengeBlock.GetStringValue("type");
			// TODO check value size, better name
            switch (sType)
            {
            case EValueDragon:
                type = GameType.DRAGON;
                break;
            case EValueStaghunt:
                type = GameType.STAGHUNT;
                break;
            case EValueSkirmish:
                type = GameType.SKIRMISH;
                break;
			default:
				throw new ParsingException("Invalid type"); // TODO Better message
            }
            bool accepted = challengeBlock.GetBoolValue("accepted");
            Challenge c = new Challenge(id, type, accepted);
            Backend.ChallengeIncome(c);
		}
 
		private void ProcessPlayerSelf (Block playerBlock)
		{
			Player self = MapPlayer (playerBlock);
			lock (Backend) {
				Backend.SetPlayer(self);
				Backend.SetSelfId(self.GetId());
			}
			Backend.RefreshGui ();
		}

		private void ProcessPlayer (Block playerBlock)
		{
			Player newPlayer = MapPlayer(playerBlock);
            int id = newPlayer.GetId();
            if (Backend.getPlayer(id) == null)
            {
                lock (Backend)
                {
                    Backend.SetPlayer(newPlayer);
                }
            }
            else
            {
                MapPlayerUPD(newPlayer);
            }
			Backend.RefreshGui();
		}

       
		
		private void ProcessMapcell (Block mapcellBlock)
		{
			Tile tile = MapMapcell (mapcellBlock);
			lock (Backend) {
				 Backend.getMapObject ().SetTile (tile);
			}
			Backend.RefreshGui ();
		}

		private void ProcessYourid (Block yourIdBlock)
		{
			LinkedList <string> unnamedValues = yourIdBlock.GetUnnamedValues ();
			string stringValue = unnamedValues.First.Value;
			int intValue = int.Parse (stringValue);
			YourID id = new YourID (intValue);
		}

		private void ProcessTime (Block timeBlock)
		{
			LinkedList <string> unnamedValues = timeBlock.GetUnnamedValues ();
			string stringValue = unnamedValues.First.Value;
			long longValue = long.Parse (stringValue);
			Time t = new Time(longValue);
		}

		private void ProcessOnline (Block onlineBlock)
		{
			LinkedList <string> unnamedValues = onlineBlock.GetUnnamedValues ();
			string stringValue = unnamedValues.First.Value;
			int intValue = int.Parse (stringValue);
			Online o = new Online(intValue);
		}

		private void ProcessEntities (Block entitiesBlock)
		{
			// This lock can be placed more efficiently. Does it make sense?
			lock (Backend) {
				Backend.clearDragons ();
				Backend.clearPlayers ();
				foreach (Block entityBlock in entitiesBlock.GetBlocks ()) {
					switch (entityBlock.GetName ()) {
					case MessPlayer:
						ProcessPlayer(entityBlock);
						break;
					case MessDragon:
						Backend.SetDragon (MapDragon (entityBlock));
						break;
					default:
						ThrowUnknownBlockException (entitiesBlock, entityBlock);
						break;
					}
				}
			}
			Backend.RefreshGui();
		}

		private void ProcessPlayers (Block playersBlock)
		{
			// This lock can be placed more efficiently. Does it make sense?
			lock (Backend) {
				Backend.clearPlayers ();
				foreach (Block playerBlock in playersBlock.GetBlocks ()) {
					ProcessPlayer(playerBlock);
				}
			}
			Backend.RefreshGui ();
		}

		private void ProcessDragon (Block dragonBlock)
		{
			Dragon dragon = MapDragon (dragonBlock);
			lock (Backend) {
				Backend.SetDragon (dragon);
			}
			Backend.RefreshGui ();
		}

		private Dragon MapDragon (Block dragonBlock)
		{
			int id = dragonBlock.GetIntValue (ValueId);
			bool busy = dragonBlock.GetBoolValue (ValueBusy);
			string desc = dragonBlock.GetStringValue (ValueDescription);
			int x = dragonBlock.GetIntValue (ValueX);
			int y = dragonBlock.GetIntValue (ValueY);
			return new Dragon (id, x, y, desc, busy);
		}

		private Player MapPlayer (Block playerBlock)
		{
			int id = playerBlock.GetIntValue (ValueId);
			int score = playerBlock.GetIntValue (ValuePoints);
			bool busy = playerBlock.GetBoolValue (ValueBusy);
			string desc = playerBlock.GetStringValue (ValueDescription);
			int x = playerBlock.GetIntValue (ValueX);
			int y = playerBlock.GetIntValue (ValueY);
			return new Player (id, x, y, desc, busy, score);
		}

        private void MapPlayerUPD(Player p)
        {
			Player backendPlayer = Backend.getPlayer(p.GetId());

            backendPlayer.SetScore(p.GetScore());
            backendPlayer.SetDesc(p.GetName());
			backendPlayer.SetBusy(p.GetBusy());
			backendPlayer.SetScore(p.GetScore());
			backendPlayer.SetPos(p.GetX(), p.GetY());
        }
		
		private Tile MapMapcell (Block cellBlock)
		{
			CheckBlocksSize (cellBlock, 1, 1);
			int x = cellBlock.GetIntValue (ValueRow);
			int y = cellBlock.GetIntValue (ValueCol);
			Block propsBlock = cellBlock.GetBlocks ().First.Value;
			bool walkable = false;
			bool wall = false;
			bool forest = false;
			bool water = false;
			bool huntable = false;
			foreach (string prop in propsBlock.GetUnnamedValues()) {
				switch (prop) {
				case EValueWalkable:
					walkable = true;
					break;
				case EValueWall:
					wall = true;
					break;
				case EValueForest:
					forest = true;
					break;
				case EValueWater:
					water = true;
					break;
				case EValueHuntable:
					huntable = true;
					break;
				default:
					throw new ParsingException("Unknown mapcell property '" + prop + "'");
				}
			}
			return new Tile(x,y,walkable, wall, forest, huntable, water);
		}

		private void ThrowUnknownBlockException (Block parentBlock, Block childBlock)
		{
			throw new ParsingException ("Unknown Block: '" + childBlock.GetName () + "' (as subblock of '" + parentBlock.GetName () + "')");
		}

		private void CheckBlocksSize (Block parentBlock, int min, int max)
		{
			LinkedList<Block> blocks = parentBlock.GetBlocks ();
			if (blocks.Count < min || (max >= 0 && blocks.Count > max)) {
				if (min == max) {
					throw new ParsingException ("The block '" + parentBlock.GetName () + "' has to contain exactly '" + min + "' Block, but contained '" + blocks.Count + "'");
				} else if (max < 0) {
					throw new ParsingException ("The block '" + parentBlock.GetName () + "' has to contain at least '" + min + "' Blocks, but contained '" + blocks.Count + "'");
				} else {
					throw new ParsingException ("The block '" + parentBlock.GetName () + "' has to contain between '" + min + "' and '" + max + "' Blocks, but contained '" + blocks.Count + "'");
				}
			}
		}

		private bool IsCompletePackage ()
		{
			string lastLine = Message.Last.Value;
			return LastLineRegex.IsMatch (lastLine);
		}

		public void Start ()
		{
			ParserThread.Start();
		}

		public void Stop ()
		{
			ParserThread.Abort ();
			ParserThread.Join ();
		}
		
		private class Block
		{
			private string Name;
			private LinkedList<Block> Blocks = new LinkedList<Block> ();
			private Dictionary<String, String> Values = new Dictionary<String, String> ();
			private LinkedList<String> UnnamedValues = new LinkedList<String>();
			
			public Block (String[] message, int start, int end)
			{
				int pos = start;
				Name = StringUtils.SubstringAfter (message [pos], ":");
				pos++;
				while (pos < end) {
					// Is the next element a block or a value?
					if (message [pos].StartsWith ("begin:")) {
						// It's a block
						int blockstart = pos;
						int begins = 1;
						while (begins > 0) {
							pos++;
							if (pos >= end)
								throw new ParsingException ("The message is missing end:-lines");
							if (message [pos].StartsWith ("end:"))
								begins--;
							else if (message [pos].StartsWith ("begin:"))
								begins++;
						}
						Blocks.AddLast (new Block (message, blockstart, pos));
					} else if (message[pos].Contains(":")) {
						// It's a value
						string name = StringUtils.SubstringBefore (message [pos], ":");
						string val = StringUtils.SubstringAfter (message [pos], ":");
						Values [name] = val;
						pos++;
					}
					else {
						UnnamedValues.AddLast(message[pos]);
						pos++;
					}
				}
			}
 			
			public string GetName ()
			{
				return Name;
			}

			public LinkedList<string> GetUnnamedValues()
			{
				return UnnamedValues;
			}

			public LinkedList<Block> GetBlocks ()
			{
				return Blocks;
			}

			public bool ValueExists (string name)
			{
				return Values.ContainsKey (name);
			}

			public string GetStringValue (string name)
			{
				try {
					return Values [name];
				} catch (KeyNotFoundException e) {
					throw new ParsingException("The parameter '" + name + "' does not exist in block '" + Name + "'", e);
				}
			}

			public int GetIntValue (string name)
			{
				try {
					return int.Parse (Values [name]);
				} catch (KeyNotFoundException e) {
					throw new ParsingException("The parameter '" + name + "' does not exist in block '" + Name + "'", e);
				} catch (FormatException e) {
					throw new ParsingException("The parameter '" + name + "' in block '" + Name + "' is not an integer (it was '" + Values [name] + "')", e);
				} catch (OverflowException e) {
					throw new ParsingException("The parameter '" + name + "' in blo '" + Name + "' does not fit into an integer (it was '" + Values [name] + "')", e);
				}
			}

			public long GetLongValue (string name)
			{
				try {
					return long.Parse (Values [name]);
				} catch (KeyNotFoundException e) {
					throw new ParsingException("The parameter '" + name + "' does not exist in block '" + Name + "'", e);
				} catch (FormatException e) {
					throw new ParsingException("The parameter '" + name + "' in block '" + Name + "' is not a long (it was '" + Values [name] + "')", e);
				} catch (OverflowException e) {
					throw new ParsingException("The parameter '" + name + "' in blo '" + Name + "' does not fit into a long (it was '" + Values [name] + "')", e);
				}
			}

			public bool GetBoolValue (string name)
			{
				try {
					return bool.Parse (Values [name]);
				} catch (KeyNotFoundException e) {
					throw new ParsingException ("The parameter '" + name + "' does not exist in block '" + Name + "'", e);
				} catch (FormatException e) {
					throw new ParsingException ("The parameter '" + name + "' in block '" + Name + "' is not a bool (it was '" + Values [name] + "')", e);
				}
			}
		}
	}
}


