using System;
using Gtk;
using System.Timers;
using System.Threading;
using System.Collections.Generic;

public partial class MainWindow: Gtk.Window
{	
	#region Private Members
	private const uint worldWidth = 15;
	private const uint worldHeight = 15;	
	private const uint numElements = 5;
	
	private const int timerInterval = 500;
	private int numTrials = 100;
	private int velocidade = 0;
	private System.Timers.Timer timer;
	
	private enum Elements {Empty, Dirt, Fire, Wall, Robot};
	
	private	Gtk.TextBuffer buffer;
	private	Gtk.TextBuffer buffer2;
	//private List<string> loggin1;
	
	private Gdk.Pixbuf[] iconList;
	private Gtk.Image[,] worldImagesMatrix;	
	private int[,] worldMatrix;
	
	private Gtk.Image[,] worldImagesMatrixTrain;	
	private int[,] worldMatrixTrain;
	
	private int[,] pastWorldMatrix;
	private int[] sensorInfo;
	
	private System.Drawing.Point agentPosi;
	
	private int numTicksDirt;
	private int numTicksFire;
	private int numTotalTicks;
	private const int numTimes = 20;
	private int val1 = 0;
	private int val2 = 0;
	private int val3 = 0;
	private int progress = 0;	
	private bool isTrainning;
	
	private Random r;
	
	private RER.Agente Andy;
	private Thread trainThread;
	
	#endregion
	
	#region Private Methods
	private void StartWorld()
	{
		buffer = txtOut.Buffer;
		buffer2 = txtSimResult.Buffer;
		
		//SetFlagggin1 = new List<string>();
		
		Gdk.Color c = new Gdk.Color(73,73,73);
		
		eventbox1.ModifyBg(StateType.Normal, c);
		eventbox2.ModifyBg(StateType.Normal, c);
		
		iconList = new Gdk.Pixbuf[numElements];
		
		// Initialize all the private members
		iconList[(int)Elements.Empty] = new Gdk.Pixbuf("Images/empty.jpg", 30, 30);
		iconList[(int)Elements.Dirt] = new Gdk.Pixbuf("Images/dirt.jpg", 30, 30);
		iconList[(int)Elements.Fire] = new Gdk.Pixbuf("Images/fire.jpg", 30, 30);
		iconList[(int)Elements.Wall] = new Gdk.Pixbuf("Images/wall.jpg", 30, 30);		
		iconList[(int)Elements.Robot] = new Gdk.Pixbuf("Images/robot.jpg", 30, 30);
		
		worldImagesMatrix = new Gtk.Image[worldWidth,worldHeight];
		worldMatrix = new int[worldWidth,worldHeight];
		
		worldImagesMatrixTrain = new Gtk.Image[3,3];
		worldMatrixTrain = new int[3,3];
		
		pastWorldMatrix	 = new int[worldWidth,worldHeight];
		sensorInfo = new int[8]{0,0,0,0,0,0,0,0};
		
		agentPosi = new System.Drawing.Point(-1,-1);
		numTicksDirt = 0;
		numTicksFire = 0;
		numTotalTicks = 0;		
		isTrainning = true;
		
		for(uint i = 0; i < 3; i++)
		{
			for(uint j = 0; j < 3; j++)
			{
				worldMatrixTrain[i,j] = 0;				
				worldImagesMatrixTrain[i,j] = new Gtk.Image();
				worldImagesMatrixTrain[i,j].Pixbuf = iconList[(int)Elements.Empty];
				table1.Attach(worldImagesMatrixTrain[i,j],i,(i+1),j,(j+1));	
				worldImagesMatrixTrain[i,j].Show();
			}			
		}
		
		for(uint i = 0; i < worldWidth; i++)
		{
			for(uint j = 0; j < worldHeight; j++)
			{
				worldMatrix[i,j] = 0;
				pastWorldMatrix[i,j] = 0;
				worldImagesMatrix[i,j] = new Gtk.Image();
				worldImagesMatrix[i,j].Pixbuf = iconList[(int)Elements.Empty];
				table2.Attach(worldImagesMatrix[i,j],i,(i+1),j,(j+1));	
				worldImagesMatrix[i,j].Show();
			}			
		}
			
		
		timer = new System.Timers.Timer(timerInterval);
		timer.Elapsed += new ElapsedEventHandler(OnTimerTick);
		timer.Stop();
		
		r = new Random(DateTime.Now.Millisecond);	
		
		ResetWorld();
		
		//Create thread to simulation
        trainThread = new Thread(Train);
		
		// Agente
		Andy = new RER.Agente();
		
		txtOut.Buffer.Clear();
		buffer.Text = "Andy está pronto para ser treinado";
		
	}
	
	private void ResetWorld()
	{		
		// All the worldMatrix values to 0
		numTicksDirt = 0;
		numTicksFire = 0;
		numTotalTicks = 0;
				
		for(int i = 0; i < worldWidth; i++)
		{
			for(int j = 0; j < worldHeight; j++)
			{
				worldMatrix[i,j] = 0;
			}			
		}
		
		if(!isTrainning)
		{		
			// Draw the exterior Walls
			for(int j = 0; j < 15; j++)
			{
					worldMatrix[0,j] = 3;
					worldMatrix[14,j] = 3;
					worldMatrix[j,0] = 3;
					worldMatrix[j,14] = 3;
			}
			
			// Draw the interiors walls
			worldMatrix[9,4] = (int)Elements.Wall;
			worldMatrix[9,5] = (int)Elements.Wall;
			worldMatrix[9,6] = (int)Elements.Wall;
			worldMatrix[9,7] = (int)Elements.Wall;
			worldMatrix[9,8] = (int)Elements.Wall;
			worldMatrix[9,9] = (int)Elements.Wall;
			
			worldMatrix[4,5] = (int)Elements.Wall;
			worldMatrix[5,5] = (int)Elements.Wall;
			
			// Draw 3 Fires and 3 dirt aleatori
			int val1 = 0;
			int val2 = 0;
			
			for (int i = 0; i < 3; i++)
			{
				do {
					val1 = r.Next(1,14);
					val2 = r.Next(1,14);
				} while (worldMatrix[val1, val2] != (int)Elements.Empty);
				
				worldMatrix[val1, val2] = (int)Elements.Fire;
				
				do {
					val1 = r.Next(1,14);
					val2 = r.Next(1,14);
				} while (worldMatrix[val1, val2] != (int)Elements.Empty);
				
				worldMatrix[val1, val2] = (int)Elements.Dirt;			
			}		
			
			// Draw Agent
			val1 = 0;
			val2 = 0;
			
			do {
				val1 = r.Next(1,14);
				val2 = r.Next(1,14);
			} while (worldMatrix[val1, val2] != (int)Elements.Empty);
			
			agentPosi.X = val1;
			agentPosi.Y = val2;
			
			worldMatrix[agentPosi.X, agentPosi.Y] = (int)Elements.Robot;					
		}
		else
		{
			agentPosi.X = 1;
			agentPosi.Y = 1;
			
			worldMatrixTrain[agentPosi.X, agentPosi.Y] = (int)Elements.Robot;
		}		
		
		DrawWorld();
	}
		
	/// <summary>
	/// Draws the world.
	/// </summary>
	private void DrawWorld()
	{
		Gtk.Application.Invoke(delegate {
			
			if(!isTrainning)
			{				
				for(int i = 0; i < worldWidth; i++)
				{
					for(int j = 0; j < worldHeight; j++)
					{
						
						if(pastWorldMatrix[i,j] != worldMatrix[i,j])
						{
							switch (worldMatrix[i,j]) 
							{
							
							case 0:
								worldImagesMatrix[i,j].Pixbuf = iconList[(int)Elements.Empty];
								break;
								
							case 1:	
								worldImagesMatrix[i,j].Pixbuf = iconList[(int)Elements.Dirt];
													
								break;
								
							case 2:					
								worldImagesMatrix[i,j].Pixbuf = iconList[(int)Elements.Fire];
								break;
								
							case 3:
								worldImagesMatrix[i,j].Pixbuf = iconList[(int)Elements.Wall];
								break;
								
							case 4:
								worldImagesMatrix[i,j].Pixbuf = iconList[(int)Elements.Robot];
								break;
								
							default:
								break;
							}
						}
					}
				}
				
				for(int i = 0; i < worldWidth; i++)
				{
					for(int j = 0; j < worldHeight; j++)
					{
						pastWorldMatrix[i,j] = worldMatrix[i,j];
					}
				}
			}
			else
			{
				for(int i = 0; i < 3; i++)
				{
					for(int j = 0; j < 3; j++)
					{												
						switch (worldMatrixTrain[i,j]) 
						{
						
						case 0:
							worldImagesMatrixTrain[i,j].Pixbuf = iconList[(int)Elements.Empty];
							break;
							
						case 1:	
							worldImagesMatrixTrain[i,j].Pixbuf = iconList[(int)Elements.Dirt];
												
							break;
							
						case 2:					
							worldImagesMatrixTrain[i,j].Pixbuf = iconList[(int)Elements.Fire];
							break;
							
						case 3:
							worldImagesMatrixTrain[i,j].Pixbuf = iconList[(int)Elements.Wall];
							break;
							
						case 4:
							worldImagesMatrixTrain[i,j].Pixbuf = iconList[(int)Elements.Robot];
							break;
							
						default:
							break;
						}
					}					
				}				
			}
		});
	}
	
	/// <summary>
	/// Gets the absolute position.
	/// </summary>
	/// <returns>
	/// The absolute position.
	/// </returns>
	/// <param name='relative_position'>
	/// The absolute position. [0-9]
	/// </param>
	/// <param name='current_point'>
	/// Current_point.
	/// </param>
	private System.Drawing.Point GetAbsolutePosition(int relative_position, System.Drawing.Point current_point)
	{
		System.Drawing.Point absPoint = new System.Drawing.Point(0,0);
		
		switch (relative_position) {
		case 0:
			absPoint.X = current_point.X -1;
			absPoint.Y = current_point.Y -1;
			break;
		case 1:
			absPoint.X = current_point.X;
			absPoint.Y = current_point.Y -1;
			break;
		case 2:
			absPoint.X = current_point.X +1;
			absPoint.Y = current_point.Y -1;
			break;
		case 3:
			absPoint.X = current_point.X +1;
			absPoint.Y = current_point.Y;
			break;
		case 4:
			absPoint.X = current_point.X +1;
			absPoint.Y = current_point.Y +1;
			break;
		case 5:
			absPoint.X = current_point.X;
			absPoint.Y = current_point.Y+1;
			break;
		case 6:
			absPoint.X = current_point.X -1;
			absPoint.Y = current_point.Y +1;
			break;
		case 7:
			absPoint.X = current_point.X -1;
			absPoint.Y = current_point.Y;
			break;					
		default:
			break;
		}
		
		return absPoint;
	}
	
	/// <summary>
	/// Moves the element.
	/// </summary>
	/// <param name='origin'>
	/// Point with the origin of the element.
	/// </param>
	/// <param name='dest'>
	/// Destination Point
	/// </param>	
	private void MoveElement(System.Drawing.Point origin, System.Drawing.Point dest)
	{
		if(dest.X > 0 && dest.X < (worldWidth-1) && dest.Y > 0 && dest.Y < (worldHeight-1))
		{
			if(worldMatrix[dest.X, dest.Y] == (int)Elements.Empty)
			{
				int elem = worldMatrix[origin.X, origin.Y];
				worldMatrix[origin.X, origin.Y] = (int)Elements.Empty;
				worldMatrix[dest.X, dest.Y] = elem;
			}
		}
	}
	
	/// <summary>
	/// Gets the sensor information.
	/// </summary>
	private void GetSensorInformation()
	{
		if(isTrainning)
		{
			agentPosi.X = 1;
			agentPosi.Y = 1;
			
			sensorInfo[0] = worldMatrixTrain[agentPosi.X-1, agentPosi.Y-1];
			sensorInfo[1] = worldMatrixTrain[agentPosi.X, agentPosi.Y-1];
			sensorInfo[2] = worldMatrixTrain[agentPosi.X+1, agentPosi.Y-1];
			sensorInfo[3] = worldMatrixTrain[agentPosi.X+1, agentPosi.Y];
			sensorInfo[4] = worldMatrixTrain[agentPosi.X+1, agentPosi.Y+1];
			sensorInfo[5] = worldMatrixTrain[agentPosi.X, agentPosi.Y+1];
			sensorInfo[6] = worldMatrixTrain[agentPosi.X-1, agentPosi.Y+1];
			sensorInfo[7] = worldMatrixTrain[agentPosi.X-1, agentPosi.Y];		
			
		}
		else
		{
			bool aux = false;
			
			sensorInfo[0] = worldMatrix[agentPosi.X-1, agentPosi.Y-1];
			sensorInfo[1] = worldMatrix[agentPosi.X, agentPosi.Y-1];
			sensorInfo[2] = worldMatrix[agentPosi.X+1, agentPosi.Y-1];
			sensorInfo[3] = worldMatrix[agentPosi.X+1, agentPosi.Y];
			sensorInfo[4] = worldMatrix[agentPosi.X+1, agentPosi.Y+1];
			sensorInfo[5] = worldMatrix[agentPosi.X, agentPosi.Y+1];
			sensorInfo[6] = worldMatrix[agentPosi.X-1, agentPosi.Y+1];
			sensorInfo[7] = worldMatrix[agentPosi.X-1, agentPosi.Y];
			
			for(int i = 0 ; i < 8; i++)
			{
				if(sensorInfo[i] == (int)Elements.Fire)
				{
					for(int j = 0 ; j < 8; j++)						
						sensorInfo[j] = (int)Elements.Empty;
					
					sensorInfo[i] = (int)Elements.Fire;
					aux = true;
					return;
				}				
			}
			
			if(!aux)
			{
				for(int i = 0 ; i < 8; i++)
				{
					if(sensorInfo[i] == (int)Elements.Dirt)
					{
						for(int j = 0 ; j < 8; j++)						
							sensorInfo[j] = (int)Elements.Empty;
						
						sensorInfo[i] = (int)Elements.Dirt;
						aux = true;
						return;
					}				
				}				
			}
			
			if(!aux)
			{
				for(int i = 0 ; i < 8; i++)
				{
					if(sensorInfo[i] == (int)Elements.Wall)
					{
						for(int j = 0 ; j < 8; j++)						
							sensorInfo[j] = (int)Elements.Empty;
						
						sensorInfo[i] = (int)Elements.Wall;
						aux = true;
						return;
					}				
				}				
			}
		}
	}
	
	private void OnTimerTick(object source, ElapsedEventArgs e) 
	{
		Simutale();
	}
	
	private void Train()
	{	
		for(int o = 0; o< numTimes; o++ )
		{
			for(int k = 0; k <= numTrials; k++)
			{				
				progress = (int)(((double)(k) / (double)numTrials) * 100);
			
				Gtk.Application.Invoke(delegate {
					lblNumTicksTrain.Text = "Tick # "+ k.ToString() + " ( " + progress.ToString()+" % )";	
					});
				
				for(uint i = 0; i < 3; i++)
				{
					for(uint j = 0; j < 3; j++)
					{
						worldMatrixTrain[val1, val2] = (int)Elements.Empty;
					}
				}
				
				worldMatrixTrain[1, 1] = (int)Elements.Robot;
				
				do {
					val1 = r.Next(0,3);
					val2 = r.Next(0,3);
				} while (worldMatrixTrain[val1, val2] != (int)Elements.Empty);
				
				val3 = r.Next(0,4);
				worldMatrixTrain[val1, val2] = val3;
				
				
				// Start cognetive cycle
				GetSensorInformation();								
				Andy.Train(sensorInfo);
				
				// Update World
				DrawWorld();
				
				//Wait 
	            if (velocidade > 0)
	            {
	                Thread.Sleep(velocidade);
	            }	
			}
			
			string output =	"Epoca " + (o+1) + "/" + numTimes + ", Andy obtive " + Andy.CorrectCounter + " corretas de um total de " + numTrials + " amostras (" + (int)Math.Round(((double)Andy.CorrectCounter / (double)numTrials) * 100) + "%)";
			Andy.CorrectCounter = 0;
			
			Gtk.Application.Invoke(delegate {
			buffer.Text = output +"\n" + buffer.Text;
			//buffer.Text = "Train # "+(o+2)+" out of "+numTimes +"\n" + buffer.Text;
				
			});
		}
	}
	
	private void Simutale()
	{
		numTotalTicks++;
		numTicksDirt++;
		numTicksFire++;
		
		Gtk.Application.Invoke(delegate {
		lblNumTicksSim.Text = "Tick # "+ numTotalTicks.ToString(); });
		
		// Add Dirt
		if(numTicksDirt == 4)
		{			
			int val1 = 0;
			int val2 = 0;
			
			do {
				val1 = r.Next(1,14);
				val2 = r.Next(1,14);
			} while (worldMatrix[val1, val2] != (int)Elements.Empty);
			
			worldMatrix[val1, val2] = (int)Elements.Dirt;
			numTicksDirt = 0;
		}
		
		// Add Fire
		if(numTicksFire == 8)
		{			
			int val1 = 0;
			int val2 = 0;
			
			do {
				val1 = r.Next(1,14);
				val2 = r.Next(1,14);
			} while (worldMatrix[val1, val2] != (int)Elements.Empty);
			
			worldMatrix[val1, val2] = (int)Elements.Fire;
			
			numTicksFire = 0;
		}
		
		// Start Cognitive Cicle
		// Gets the vector with the sensor information
		GetSensorInformation();
		
		string action = Andy.NewCognitiveCicle(sensorInfo);		
		ExecuteAction(action);		
		
		// Update World
		DrawWorld();
	}
	
	private void ExecuteAction(string action)
	{
			
		int var1 = 0;
		int var2 = 1;
		
		if(action.Contains("moveTo_"))
			var1 = 0;		
		else if(action.Contains("clean_")) 
			var1 = 1;
		else if(action.Contains("pourWater_"))
			var1 = 2;
		
		string input = "";
				
		for(int i = 0 ; i < 8; i++)
		{
			input = input + sensorInfo[i] + ",";
		}
		
		input = "Entrada: "+input + "\n";
		
		var2 = Int32.Parse(action[action.Length-1].ToString());
		
		System.Drawing.Point dest = new System.Drawing.Point();
		
		switch (var1) 
		{
			
		case 0:
			// Move To
			dest = GetAbsolutePosition(var2,agentPosi);
			
			if(worldMatrix[dest.X, dest.Y] == (int)Elements.Empty) // Andy is correct
			{
				input += "Andy was correct (Move To "+ var2.ToString() +" )\n \n";
				
				//Gtk.Application.Invoke(delegate {
				//	buffer2.Text = "Andy was correct (Move To "+ var2.ToString() +" )\n" + buffer2.Text;});
				
				// Move Andy
				MoveElement(agentPosi,dest);
				agentPosi = dest;
			}
			else
				input += "Andy was wrong (Move To "+ var2.ToString() +" )\n \n";
			
			break;
			
		case 1:
			
			// Clean
			
			dest = GetAbsolutePosition(var2,agentPosi);
			
			if(worldMatrix[dest.X, dest.Y] == (int)Elements.Dirt) // Andy is correct
			{
				input += "Andy was correct (Clean on "+ var2.ToString() +" )\n \n";
				
				// Clean
				worldMatrix[dest.X, dest.Y] = (int)Elements.Empty;
				
				// Move Andy
				MoveElement(agentPosi,dest);
				agentPosi = dest;
			}
			else				
				input += "Andy was wrong (Clean on "+ var2.ToString() +" )\n \n";		
			
			break;			
			
		case 2:
			// Pour Water
			
			dest = GetAbsolutePosition(var2,agentPosi);
			
			if(worldMatrix[dest.X, dest.Y] == (int)Elements.Fire) // Andy is correct
			{				
				input += "Andy was correct (Pour Water on "+ var2.ToString() +" )\n \n";
				
				// Pour Water 
				worldMatrix[dest.X, dest.Y] = (int)Elements.Empty;
				
				// Move Andy
				MoveElement(agentPosi,dest);
				agentPosi = dest;
			}
			else				
				input +=  "Andy was wrong (Pour Water on "+ var2.ToString() +" )\n \n";
			
			break;			
			
		default:
			break;
		}
		
		
		Gtk.Application.Invoke(delegate {
		buffer2.Text = input + buffer2.Text;});			
		
		
	}
	
	#endregion
	
	#region Fixed Methods
	
	public MainWindow (): base (Gtk.WindowType.Toplevel)
	{
		Build ();		
		StartWorld();
	}
	
	protected void OnDeleteEvent (object sender, DeleteEventArgs a)
	{
		Andy.EndAgent();
		
		if (trainThread != null && trainThread.IsAlive)
        {
			trainThread.Abort();
        }
		
		Application.Quit ();
		a.RetVal = true;
	}
	
	protected void OnNotebook1SwitchPage (object o, Gtk.SwitchPageArgs args)
	{
		timer.Stop();
						
		if(notebook1.CurrentPage == 0)
		{			
			table2.Hide();
			table1.Show();
			isTrainning = true;
		}
		else
		{
			table1.Hide();
			table2.Show();			
			isTrainning = false;			
		}
		
		ResetWorld();	
	}
		
	protected void OnBtnStartTrainClicked (object sender, System.EventArgs e)
	{
		txtNumTrials.IsEditable = false;
		numTrials = Int32.Parse(txtNumTrials.Text);
		Andy.NumberTrials = numTrials;
		isTrainning = true;
		buffer.Text = "Trainning Andy...";
		//buffer.Text = "Train # 1 out of "+ numTimes +"\n" + buffer.Text;		
		trainThread.Start();		
	}

	protected void OnBtnStopTrainClicked (object sender, System.EventArgs e)
	{
		txtNumTrials.IsEditable = true;
		
		if(trainThread.IsAlive)
		{			
			trainThread.Abort();
		}
		
		btnStartTrain.Hide();		
	}

	protected void OnBtnStartSimClicked (object sender, System.EventArgs e)
	{
		isTrainning = false;
		timer.Start();	
	}

	protected void OnBtnStopSimClicked (object sender, System.EventArgs e)
	{
		timer.Stop();
	}
	
	protected void OnButton1Clicked (object sender, System.EventArgs e)
	{
		txtOut.Buffer.Clear();
		
		buffer.Text = Andy.GetTrainResults();
		
	}

	protected void OnBtnSaveClicked (object sender, System.EventArgs e)
	{
		// Salvando o Agente
		Andy.SaveAgent();
	}

	protected void OnBtnOpenClicked (object sender, System.EventArgs e)
	{
		// Carregando o Agente
		Andy.LoadAgent();
	}
	#endregion
}

