// ************************************
// * J. Renato Borelli - 31/05/2015
// ************************************

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using ClarionSimulation;
using WorldServerLibrary;
using WorldServerLibrary.Model;
using WorldServerLibrary.Exceptions;



namespace ClarionDEMO {
	class MainClass {	
		#region properties
		private WorldServer worldServer = null;
		private ClarionAgent agent;          // A classe ClarionAgent deveria se chamar Simulador Clarion - não é o "agente" - causa confusão para quem pensa O.O.
		String creatureId = String.Empty;    // Link para o bot no WS.
		String creatureName = String.Empty;  // Link para o bot no WS.

		//Thing closestbrick = null;
		Thing closestDeliverySPOT = null;
		Thing closestFood = null;
		Thing closestJewel = null;
		double prad = 0;
		#endregion

		#region constructor
		public MainClass() {
			Console.WriteLine ("Clarion Demo V0.4");
			agent = new ClarionAgent();  // instância de um ambiente CLARION (não de um agente)

			//******************* Business Delegate - tratamento dos eventos:
			// "OnNewVisualSensorialInformation" é delegado ao método agent_OnNewVisualSensorialInformation
			// "OnNewExternalActionSelected" é delegado ao método agent_OnNewExternalActionSelected.  

			// Cria dois canais de comunicação entre o Simulador CLARION e o WS3D.
			// ENTRADA de Informações através do "Sensorial Information" que traz 
			// informações relativas ao bot no WS3D para o ambiente de simulação CLARION.
			// SAÍDA de comandos através do "ExternalActionSelected" direcionada ao respectivo bot na arena WS3D.

			agent.OnNewVisualSensorialInformation += new InputVisualSensorialInformationEventHandler(agent_OnNewVisualSensorialInformation);
			agent.OnNewExternalActionSelected += new OutputActionChunkEventHandler(agent_OnNewExternalActionSelected);

			//******************** FIM DA CRIAÇÃO DO CANAL DE COMUNICAÇÃO ******************************


			//******************* WS3D CONNECTION AND BUILD ********************************************
			try {          
				worldServer = new WorldServer("localhost", 4011);  // Chama método WorldServer para conexão do ambiente.
				// Muito feio isto!! 
				// O ambiente deveria ser criado e conectado no seu próprio constructor!!!
				String message = worldServer.Connect();            // Fazer o método connect privado da classe World Server

				// Testa a conexão com o WS3D e cria o ambiente (isteo deveria estar encapsulado na classe WorldServer):
				if (worldServer != null && worldServer.IsConnected) {  

					Console.Out.WriteLine ("[SUCCESS] " + message + "\n");
					worldServer.SendWorldReset();
					// não é muito evidente, mas, ao estilo da linguagem C, este método cria uma nova criatura e traz de volta (out) seu ID e Nome: 
					worldServer.NewCreature(400, 200, 0, out creatureId, out creatureName);  
					worldServer.SendCreateLeaflet();
					worldServer.NewBrick(4, 747, 2, 800, 567);
					worldServer.NewBrick(4, 50, -4, 747, 47);
					worldServer.NewBrick(4, 49, 562, 796, 599);
					worldServer.NewBrick(4, -2, 6, 50, 599);

					// A classe Main.cs, que deveria coordenar a comunicação e gerar relatórios, também cria detalhes do ambiente WS3D - encapsulamento Zero!
					// O procedimento cria jóias e alimentos na arena WS3D:

					Random r = new Random(DateTime.Now.Millisecond);  // semente randômica para geração de número aleatório.
					int x_coord = 0;
					int y_coord = 0;

					// Pergunta: Se as coordenadas são aleatórias, por que não gerar na própria classe WS?
					// O método deveria ser no máximo worldServer.NewFood(numberOfFoods);

					// comida:
					for (int i = 0; i < 5; i++)
					{
						x_coord = (int)r.Next(55, 745);
						y_coord = (int)r.Next(55, 545);
						worldServer.NewFood(0, x_coord, y_coord);
					}

					// jóias: 
					for (int i = 0; i < 4; i++)
					{
						for (int j = 0; j < 5; j++)
						{
							x_coord = (int)r.Next(55, 745);
							y_coord = (int)r.Next(55, 545);
							worldServer.NewJewel(j, x_coord, y_coord);
						}
					}

					if (!String.IsNullOrWhiteSpace(creatureId))
					{
						worldServer.SendStartCamera(creatureId);
						worldServer.SendStartCreature(creatureId);
					}

					Console.Out.WriteLine("Creature created with name: " + creatureId + "\n");
					agent.Run();  // run() -> método da classe ClarionAgent.cs que deveria se chamar ClarionSimulator.cs
					Console.Out.WriteLine("Running Simulation ...\n");
				}
			}
			catch (WorldServerInvalidArgument invalidArtgument)
			{
				Console.Out.WriteLine(String.Format("[ERROR] Invalid Argument: {0}\n", invalidArtgument.Message));
			}
			catch (WorldServerConnectionError serverError)
			{
				Console.Out.WriteLine(String.Format("[ERROR] Is is not possible to connect to server: {0}\n", serverError.Message));
			}
			catch (Exception ex)
			{
				Console.Out.WriteLine(String.Format("[ERROR] Unknown Error: {0}\n", ex.Message));
			}
		}
		#endregion

		//******************* FIM DO WS3D CONNECTION AND BUILD ********************************************



		// Método Main que instancia uma classe "MainClass"... 
		#region Methods
		public static void Main (string[] args)	{
			new MainClass();
		}


		//******************* Métodos da "interface" com o WS3D ********************************************
		// CANAL DE ENTRADA - Visão da câmera. Deve-se identificar "Things" no campo de visão e ordenar por distância com relação à criatura.
		//Event Handlers by DELEGATION from CLARION (OnNewVisualSensorialInformation):

		IList<Thing> agent_OnNewVisualSensorialInformation()
		{
			IList<Thing> response = null;

			if (worldServer != null && worldServer.IsConnected)
			{

				response = worldServer.SendGetCreatureState(creatureName);

				Creature creature = (Creature) response.Where(item => (item.CategoryId == Thing.CATEGORY_CREATURE)).First();


				IEnumerable<Thing> jewelSorted = response.Where(item => (item.CategoryId == Thing.CATEGORY_JEWEL && creature.inLeaflet(item.Material.Color)));
				if (jewelSorted.Any ()) {
					closestJewel = jewelSorted.OrderBy (item => item.DistanceToCreature).First ();
				}

				IEnumerable<Thing> foodSorted = response.Where(item => ((item.CategoryId == Thing.CATEGORY_FOOD)||(item.CategoryId == Thing.CATEGORY_FOOD)||(item.CategoryId ==Thing.CATEGORY_PFOOD)));
				if (foodSorted.Any ()) {
					closestFood = foodSorted.OrderBy (item => item.DistanceToCreature).First ();
				}

				IEnumerable<Thing> spotSorted = response.Where(item => (item.CategoryId == Thing.CATEGORY_DeliverySPOT));
				if (spotSorted.Any ()) {
					closestDeliverySPOT = foodSorted.OrderBy (item => item.DistanceToCreature).First ();
				}

				prad = (Math.PI / 180) * response.First().Pitch;
				while (prad > Math.PI) prad -= 2 * Math.PI;
				while (prad < - Math.PI) prad += 2 * Math.PI;

			}

			return response;
		}



		//CANAL DE SAÍDA - Event Handlers by DELEGATION from CLARION (OnNewExternalActionSelected):

		void agent_OnNewExternalActionSelected(ClarionAgentActionType externalAction) {          
			Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");			
			if (worldServer != null && worldServer.IsConnected) {      

				Console.Out.WriteLine("agent_OnNewExternalActionSelected(ClarionAgentActionType externalAction)- externalAction: " + externalAction + "\n"); //debug
				switch (externalAction) {                

					case ClarionAgentActionType.DO_NOTHING:
					Console.Out.WriteLine ("NOTHING");
					// Não implementado -> Rotate.
					break; 
					case ClarionAgentActionType.ROTATE:
					Console.Out.WriteLine ("Just rotating");
					double newprad = prad + Math.PI/1000;
					while (newprad > Math.PI) newprad -= 2 * Math.PI;
					while (newprad < - Math.PI) newprad += 2 * Math.PI;
					worldServer.SendSetAngle(creatureId, 0, 0, newprad);
					break;
					case ClarionAgentActionType.GO_AHEAD:
					Console.Out.WriteLine ("Going aheadddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd");
					worldServer.SendSetAngle(creatureId, 1, 1, prad);
					break;

					case ClarionAgentActionType.MOVE_JEWEL:
					Console.Out.WriteLine ("Moving at jewel's direction: "+closestJewel.Name + "X = "+closestJewel.X1+"Y = "+closestJewel.Y1);
					worldServer.SendSetAngle (creatureId, 0, 0, prad);
					worldServer.SendSetGoTo (creatureId, 1, 1, closestJewel.X1, closestJewel.Y1);
					break;

					case ClarionAgentActionType.MOVE_FOOD:
					Console.Out.WriteLine ("Moving at food's direction: " +closestFood.Name + "X = "+closestFood.X1+"Y = "+closestFood.Y1);
					worldServer.SendSetAngle (creatureId, 0, 0, prad);
					worldServer.SendSetGoTo (creatureId, 1, 1, closestFood.X1, closestFood.Y1);
					break;

					case ClarionAgentActionType.GET_JEWEL:
					Console.Out.WriteLine ("Getting Jewel:" + closestJewel.Name);
					worldServer.SendSetAngle (creatureId, 0, 0, prad);
					worldServer.SendSackIt (creatureId, closestJewel.Name);
					break;

					case ClarionAgentActionType.EAT_FOOD:
					Console.Out.WriteLine ("Eating some food:" + closestFood.Name);
					worldServer.SendSetAngle (creatureId, 0, 0, prad);
					worldServer.SendEatIt (creatureId, closestFood.Name);
					break; 
				}
			}
		}
		#endregion
	}		
}
