package sim.engine;

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;
	
	public SimulationEngine(EnginePeerList gates, NodeList connection)
	{	
		this.components = gates;
		this.nodes = connection;
		this.currentTime = 0;
		
		this.currentAffected = new EnginePeerList();
		this.mostRecent = new NodeList();
	}
	
	public double getCurrentTime()
	{
		return this.currentTime;
	}
	
	public NodeList getNodes()
	{
		return this.nodes;
	}
		
	private 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
	{
		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);
			
			this.currentAffected.removeAll();
		}	
	}
}