package sim;

import java.awt.*;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;

import sim.engine.*;
import sim.lib.others.Module;
import sim.lib.wires.SplitterModule;
import sim.util.WaitDialog;

public class GuiEngineLink {

	private static WaitDialog WD = new WaitDialog();
	/* ==================================================================
	 Supply Engine Modules Part
	 ================================================================= */

	public SimulationEngine createPeer() {
		WD.setVisible(true);
		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);
		}

		ArrayList<Component> modules = new ArrayList<Component>();
		
		total = total + components;
		for (loop = junctions + wires + splitters; loop < total; loop++) {
			if (!((EngineModule) CentralPanel.ACTIVE_GRID.getComponent(loop)).getClass().toString().equals("class sim.lib.others.Module")) {
				((EngineModule) CentralPanel.ACTIVE_GRID.getComponent(loop)).createEnginePeer(epl);
			}else{
				modules.add(CentralPanel.ACTIVE_GRID.getComponent(loop));
			}
		}
		
		modules.sort(new ComponentXComparator());
		modules.sort(new ComponentYComparator());

		Grid temp = CentralPanel.ACTIVE_GRID;
		Point[] loaded = new Point[100];
		Boolean setup = true;
		Boolean mod = false;
		String gname = "";
		Module.nextID = 0;
		//for (loop = junctions + wires + splitters; loop < total; loop++) {
		for(loop = 0; loop < modules.size(); loop++) {
			//if (((EngineModule) temp.getComponent(loop)) instanceof sim.lib.others.Module) {
			if (((EngineModule) modules.get(loop)) instanceof sim.lib.others.Module) {
				setup = true;
				//Point loc = ((sim.lib.others.Module) temp.getComponent(loop)).getLocation();
				Point loc = ((sim.lib.others.Module)modules.get(loop)).getLocation();
				if (loaded[0] == null) {
					loaded[0] = loc;
				} else {
					for (int k = 0; k < 100; k++) {
						Point p = loaded[k];
						if (loc.equals(p)) {
							setup = false;
							break;
						}
						if (p == null && setup) {
							loaded[k] = loc;
							break;
						}
					}
				}
				if (setup) {
					if (!mod) {
						BufferedWriter outStream;
						try {
							outStream = new BufferedWriter(new FileWriter("temp.toym"));
							SaveLoadShortcut.GUI_FILE_LINK.save(outStream, CentralPanel.ACTIVE_GRID);
							outStream.close();
						} catch (SimException e1) {
							// TODO Auto-generated catch block
							e1.printStackTrace();
						} catch (IOException e2) {
							// TODO Auto-generated catch block
							e2.printStackTrace();
						}
					}
					gname = MainWindow.MAIN_WINDOW.getTitle();
					//((sim.lib.others.Module) temp.getComponent(loop)).createEnginePeer(epl, nl);
					((sim.lib.others.Module) modules.get(loop)).createEnginePeer(epl, nl);
					mod = true;
				}
			} else {
			}
		}

		if (mod) {
			String inFile = "temp.toym";
			Reader inStream;
			try {
				CentralPanel.ACTIVE_GRID = new Grid();
				MainWindow.CENTRAL_PANEL.createGrid(gname);
				MainWindow.CENTRAL_PANEL.setVisible(true);
				inStream = new BufferedReader(new FileReader(inFile));
				SaveLoadShortcut.GUI_FILE_LINK.load(inStream, CentralPanel.ACTIVE_GRID);
				inStream.close();

			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (SimException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		WD.setVisible(false);
		SimulationEngine se = new SimulationEngine(epl,nl);
		return se;
	}

	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(); //SHOULD REPLACE STOP CALLS
				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.setVisible(true);

				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.setVisible(true);

				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.setVisible(true);

					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());
			}
		}
	}
}
