package sim;

import java.awt.*;

import sim.engine.*;
import sim.lib.wires.SplitterModule;

import sim.util.StatusBox;

import sim.engine.EngineException;

public class GuiEngineLink
{
/* ==================================================================
	Supply Engine Modules Part
	================================================================= */
	private SimulationEngine createPeer()
	{
		int loop;
		
		int wires = CentralPanel.ACTIVE_GRID.getNumberOfWires();
		int junctions = CentralPanel.ACTIVE_GRID.getNumberOfJunctions();
		int splitters = CentralPanel.ACTIVE_GRID.getNumberOfSplitters();
		int components = CentralPanel.ACTIVE_GRID.getNumberOfComponents();
		int total;
		
		NodeList nl = new NodeList();
		EnginePeerList epl = new EnginePeerList();
		
		for(loop = 0; loop < junctions; loop++)
			((NodeModule)CentralPanel.ACTIVE_GRID.getComponent(loop)).createNode(nl);
		
		total = junctions + wires + splitters;
		for(loop = junctions + wires; loop < total; loop++)
			((SplitterModule)CentralPanel.ACTIVE_GRID.getComponent(loop)).mergeNodes(nl);
		
		total = total + components;
		for(loop = junctions + wires + splitters; loop < total; loop++)
			((EngineModule)CentralPanel.ACTIVE_GRID.getComponent(loop)).createEnginePeer(epl);
		
		return (new SimulationEngine(epl ,nl));
	}
	
	private void reset()
	{
		int loop;
		
		int wires = CentralPanel.ACTIVE_GRID.getNumberOfWires();
		int junctions = CentralPanel.ACTIVE_GRID.getNumberOfJunctions();
		int splitters = CentralPanel.ACTIVE_GRID.getNumberOfSplitters();
		int total = junctions + wires + splitters + CentralPanel.ACTIVE_GRID.getNumberOfComponents();
		
		for(loop = 0; loop < junctions; loop++)
			((NodeModule)CentralPanel.ACTIVE_GRID.getComponent(loop)).reset();
		
		for(loop = junctions + wires + splitters; loop < total; loop++)
			((EngineModule)CentralPanel.ACTIVE_GRID.getComponent(loop)).reset();
	}
	
/* ==================================================================
	Service Provided to RunShortcut
	================================================================= */
	private SimulationEngine peer;
	private PlayThread continuousPlay;
	private StepThread stepPlay;
	private SlowPlayThread timeStepPlay;
	private double realTime;
	
	public GuiEngineLink()
	{
		this.peer = null;
		this.continuousPlay = null;
		this.realTime = 0;
	}
	
	public double getRealTime()
	{
		return this.realTime;
	}
	
	private SimulationEngine getSimulationPeer()
	{
		return this.peer;
	}
	
	private void incrementRealTime()
	{
		this.realTime = this.realTime + SimulationProperties.STEP_SIZE;
		SouthPanel.STATUS_BOX.setComment("time = " + Double.toString(this.realTime));
	}
	
	public void setRealTime(double value)
	{
		this.realTime = value;
		SouthPanel.STATUS_BOX.setComment("time = " + Double.toString(this.realTime));
	}
	
	public void play()
	{
		if(this.peer == null)
			this.peer = this.createPeer();
		
		this.continuousPlay = new PlayThread(this);
		
		RunShortcut.PLAY_THREAD = this.continuousPlay;
		
		this.continuousPlay.start();
	}
	
	public void step()
	{	
		if(this.peer == null)
			this.peer = this.createPeer();
		
		if(this.stepPlay == null)
		{
			this.stepPlay = new StepThread(this);
			this.stepPlay.start();
		}
		else if(!this.stepPlay.isAlive())
		{
			this.stepPlay = new StepThread(this);
			this.stepPlay.start();
		}
	}
	
	public void slowPlay()
	{
		if(this.peer == null)
			this.peer = this.createPeer();
		
		if(this.timeStepPlay == null)
		{
			this.timeStepPlay = new SlowPlayThread(this);
			this.timeStepPlay.start();
		}
		else if(!this.timeStepPlay.isAlive())
		{
			this.timeStepPlay = new SlowPlayThread(this);
			this.timeStepPlay.start();
		}
	}
	
	public void pause()
	{
		this.continuousPlay.pause();
	}
	
	public void stop()
	{
		if(this.continuousPlay != null)
			if(this.continuousPlay.isAlive())
			{
				this.continuousPlay.stop();
				this.continuousPlay = null;
				RunShortcut.PLAY_THREAD = null;
			}
		
		for(int loop = 0; loop < this.peer.getNodes().getSize(); loop++)
			this.peer.getNodes().getItemAt(loop).getWires().reset();
		
		this.realTime = 0;
		this.peer = null;
		this.reset();
		CentralPanel.ACTIVE_GRID.paintAll();
	}
	
	public void halt()
	{	
		for(int loop = 0; loop < this.peer.getNodes().getSize(); loop++)
			this.peer.getNodes().getItemAt(loop).getWires().reset();
		
		this.realTime = 0;
		this.peer = null;
		this.reset();
		CentralPanel.ACTIVE_GRID.paintAll();
		
		this.continuousPlay = null;
		RunShortcut.PLAY_THREAD = null;
	}
	
/* ==================================================================
	Thread to execute step
	================================================================= */
	private class StepThread extends Thread
	{
		private GuiEngineLink link;
		
		public StepThread(GuiEngineLink gel)
		{
			super();
			this.link = gel;
		}
		
		public void run()
		{
			try
			{
				this.link.getSimulationPeer().step();
				
				if(this.link.getSimulationPeer().getCurrentTime() > link.getRealTime())
					this.link.setRealTime(this.link.getSimulationPeer().getCurrentTime());
			}
			catch(EngineException ex)
			{
				HighlightThread warning = new HighlightThread(ex.getFirstAffected().getParentWrapper(), ex.getSecondAffected().getParentWrapper());
				warning.start();
				
				MainWindow.OK_WINDOW.setDescription("Simulation exception");
				MainWindow.OK_WINDOW.setMessage(ex.getMessage());
				MainWindow.OK_WINDOW.show();
				
				warning.stop();
				while(warning.isAlive())
				{
				}
				warning.cleanUp();
				
				MainWindow.NORTH_PANEL.RUN_SHORTCUT.halt();
			}
		}
	}
	
/* ==================================================================
	Thread to execute slow play
	================================================================= */
	private class SlowPlayThread extends Thread
	{
		private GuiEngineLink link;
		
		public SlowPlayThread(GuiEngineLink gel)
		{
			super();
			this.link = gel;
		}
		
		public void run()
		{
			try
			{
				this.link.incrementRealTime();
				this.link.getSimulationPeer().playSlow(this.link.getRealTime());
			}
			catch(EngineException ex)
			{
				HighlightThread warning = new HighlightThread(ex.getFirstAffected().getParentWrapper(), ex.getSecondAffected().getParentWrapper());
				warning.start();
				
				MainWindow.OK_WINDOW.setDescription("Simulation exception");
				MainWindow.OK_WINDOW.setMessage(ex.getMessage());
				MainWindow.OK_WINDOW.show();
				
				warning.stop();
				while(warning.isAlive())
				{
				}
				warning.cleanUp();
				
				MainWindow.NORTH_PANEL.RUN_SHORTCUT.halt();
			}
		}
	}
	
/* ==================================================================
	Thread to execute play
	================================================================= */
	private class PlayThread extends Thread
	{
		private GuiEngineLink link;
		private boolean keepGoing;
		
		public PlayThread(GuiEngineLink gel)
		{
			super();
			this.link = gel;
			this.keepGoing = true;
		}
		
		public boolean isGoing()
		{
			return this.keepGoing;
		}
		
		public void pause()
		{
			this.keepGoing = false;
		}
		
		public void run()
		{
			while(this.isGoing())
			{
				try
				{
					this.link.incrementRealTime();
					this.link.getSimulationPeer().playSlow(this.link.getRealTime());
					
					try
					{
						this.sleep(SimulationProperties.SPEED);
					}
					catch(InterruptedException ie)
					{
					}
				}
				catch(EngineException ex)
				{
					HighlightThread warning = new HighlightThread(ex.getFirstAffected().getParentWrapper(), ex.getSecondAffected().getParentWrapper());
					warning.start();
					
					MainWindow.OK_WINDOW.setDescription("Simulation exception");
					MainWindow.OK_WINDOW.setMessage(ex.getMessage());
					MainWindow.OK_WINDOW.show();
					
					warning.stop();
					while(warning.isAlive())
					{
					}
					warning.cleanUp();
					
					MainWindow.NORTH_PANEL.RUN_SHORTCUT.halt();
					this.keepGoing = false;
				}
			}
		}
	}
	
/* ==================================================================
	Thread to highlight affected components
	================================================================= */
	
	private class HighlightThread extends Thread
	{
		private Wrapper affected1;
		private Wrapper affected2;
		
		public HighlightThread(Wrapper w1, Wrapper w2)
		{
			super();
			this.affected1 = w1;
			this.affected2 = w2;
		}
		
		public void run()
		{
			while(true)
			{
				this.affected1.changeColor(Color.red);
				this.affected1.paint(this.affected1.getGraphics());
				
				if(this.affected1 != this.affected2)
				{
					this.affected2.changeColor(Color.red);
					this.affected2.paint(this.affected2.getGraphics());
				}
				
				try
				{
					this.sleep(300);
				}
				catch(InterruptedException ie)
				{
				}
				
				this.affected1.changeColor(Color.black);
				this.affected1.paint(this.affected1.getGraphics());
				
				if(this.affected1 != this.affected2)
				{
					this.affected2.changeColor(Color.black);
					this.affected2.paint(this.affected2.getGraphics());
				}
				
				try
				{
					this.sleep(300);
				}
				catch(InterruptedException ie)
				{
				}
			}
		}
		
		public void cleanUp()
		{
			this.affected1.changeColor(Color.black);
			this.affected1.paint(this.affected1.getGraphics());
			
			if(this.affected1 != this.affected2)
			{
				this.affected2.changeColor(Color.black);
				this.affected2.paint(this.affected2.getGraphics());
			}
		}
	}
}
