package sim.engine;

import java.util.Collections;

import sim.lib.others.Module;

public class SimulationEngine {

	private EnginePeerList components;		// list of all components
	private NodeList nodes;					// list of data at each node
	private double currentTime;
	private EnginePeerList currentAffected;
	private NodeList mostRecent;
	private EnginePeerList displaymods;

	public SimulationEngine(EnginePeerList gates, NodeList connection) {
		Module.nextID = 0; //reset moduleId index
		//Collections.sort(gates,new ComponentXYComparator());
		this.components = gates;
		this.displaymods = new EnginePeerList();
		for (int k = 0; k < gates.getSize(); k++) {
			if(gates.getItemAt(k).getParent() instanceof sim.lib.others.Module)
				this.displaymods.insertDistinctItem(gates.getItemAt(k));
		}
		this.nodes = connection;
		this.currentTime = 0;

		this.currentAffected = new EnginePeerList();
		this.mostRecent = new NodeList();
	}
	
	public EnginePeerList getComponents(){
		return this.components;
	}

	public double getCurrentTime() {
		return this.currentTime;
	}

	public NodeList getNodes() {
		return this.nodes;
	}

	public void updateCurrentNodes() throws EngineException {
		int loopNumber;

		this.mostRecent.removeAll();
		this.currentAffected.removeAll();

		// find smallest non updated time
		double smallest = Double.MAX_VALUE;
		int occurance = 0;
		int node = 0;

		for (loopNumber = 0; loopNumber < this.nodes.getSize(); loopNumber++) {
			if (!this.nodes.getItemAt(loopNumber).isUpdated()) {
				if (this.nodes.getItemAt(loopNumber).getCurrentSignal().getTime() < smallest) {
					smallest = this.nodes.getItemAt(loopNumber).getCurrentSignal().getTime();
					node = loopNumber;
					occurance = 1;
				} else if (this.nodes.getItemAt(loopNumber).getCurrentSignal().getTime() == smallest) {
					occurance++;
				}
			}
		}

		// find smallest wake up time
		double smallestWakeUp = Double.MAX_VALUE;
		int wakeUps = 0;
		int component = 0;

		for (loopNumber = 0; loopNumber < this.components.getSize(); loopNumber++) {
			if (this.components.getItemAt(loopNumber).getWakeUp() < smallestWakeUp) {
				smallestWakeUp = this.components.getItemAt(loopNumber).getWakeUp();
				component = loopNumber;
				wakeUps = 1;
			} else if (this.components.getItemAt(loopNumber).getWakeUp() == smallestWakeUp) {
				wakeUps++;
			}
		}

		if (smallestWakeUp < smallest) {
			occurance = 0;
		} else if (smallestWakeUp > smallest) {
			wakeUps = 0;
		}

		// insert wakeUps nodes
		for (loopNumber = component; (loopNumber < this.components.getSize()) && (wakeUps > 0); loopNumber++) {
			if (this.components.getItemAt(loopNumber).getWakeUp() == smallest) {
				wakeUps--;
				this.currentAffected.insertDistinctItem(this.components.getItemAt(loopNumber));
			}
		}

		// insert smallest nodes
		for (loopNumber = node; (loopNumber < this.nodes.getSize()) && (occurance > 0); loopNumber++) {
			if (!this.nodes.getItemAt(loopNumber).isUpdated()) {
				if (this.nodes.getItemAt(loopNumber).getCurrentSignal().getTime() == smallest) {
					occurance--;
					this.mostRecent.insertItem(this.nodes.getItemAt(loopNumber));
				}
			}
		}
	}

	private void updateAffectedComponents() {
		int loopNumber1, loopNumber2, connectionSize;
		int recentSize = this.mostRecent.getSize();
		EnginePeerList connection;

		for (loopNumber1 = 0; loopNumber1 < recentSize; loopNumber1++) {
			connection = this.mostRecent.getItemAt(loopNumber1).getConnection();
			connectionSize = connection.getSize();

			for (loopNumber2 = 0; loopNumber2 < connectionSize; loopNumber2++) {
				this.currentAffected.insertDistinctItem(connection.getItemAt(loopNumber2));
			}
		}
	}

	public void step() throws EngineException {
		this.updateCurrentNodes();

		if (!this.mostRecent.isEmpty()) {
			this.currentTime = this.mostRecent.getItemAt(0).getCurrentSignal().getTime();
		} else if (!this.currentAffected.isEmpty()) {
			this.currentTime = this.currentAffected.getItemAt(0).getWakeUp();
		}

		this.skip();
	}

	public void playSlow(double finishTime) throws EngineException {
		boolean finish = false;

		while (!finish) {
			this.updateCurrentNodes();

			if (this.mostRecent.isEmpty() && this.currentAffected.isEmpty()) {
				finish = true;
			} else if (!this.currentAffected.isEmpty()) {
				if (this.currentAffected.getItemAt(0).getWakeUp() <= finishTime) {
					this.currentTime = this.currentAffected.getItemAt(0).getWakeUp();
					this.skip();
				} else {
					finish = true;
				}
			} else {
				if (this.mostRecent.getItemAt(0).getCurrentSignal().getTime() <= finishTime) {
					this.currentTime = this.mostRecent.getItemAt(0).getCurrentSignal().getTime();
					this.skip();
				} else {
					finish = true;
				}
			}
		}
	}

	private void skip() throws EngineException {
		boolean updateDisp = false;
		if (!this.mostRecent.isEmpty()) {
			for (int loopNumber = 0; loopNumber < this.mostRecent.getSize(); loopNumber++) {
				this.mostRecent.getItemAt(loopNumber).updateHistory();
			}

			this.updateAffectedComponents();
		}

		if (!this.currentAffected.isEmpty()) {
			for (int loop = 0; loop < this.currentAffected.getSize(); loop++) {
				this.currentAffected.getItemAt(loop).simulateComponent(this.currentTime);
				if(this.currentAffected.getItemAt(loop).getParent() instanceof sim.lib.memory.Register){
					updateDisp = true;
				}
			}

			if(updateDisp){
				for (int i = 0; i < this.displaymods.getSize(); i++) {
						this.displaymods.getItemAt(i).simulateComponent(this.currentTime);
				}
			}

			this.currentAffected.removeAll();
		}
	}
}
