package sim.engine;

import java.util.concurrent.atomic.AtomicInteger;

import sim.lib.wires.Wire;
import sim.lib.wires.WireList;
import sim.lib.wires.Junction;
import sim.lib.wires.JunctionList;
import sim.SimulationProperties;

public class Node
{
	private Signal history;
	private SignalList future = new SignalList();
	private EnginePeerList connection = new EnginePeerList();	// list of components connected
	
	private WireList wires = new WireList();
	private JunctionList junctions = new JunctionList();
	static AtomicInteger nextId = new AtomicInteger();
	final int id;
	
	public Node()
	{
		this.future.insertItem(new Signal(false, 0, false, null, 0));
		this.history = new Signal(true, 0, true, null, 0);
		id = nextId.incrementAndGet();
	}
	public int getId(){
		return this.id;
	}
	
	public WireList getWires()
	{
		return this.wires;
	}
	
	public JunctionList getJunctiions()
	{
		return this.junctions;
	}
	
	public boolean isUpdated()
	{
		boolean result = true;
		
		// remove unnecessry floats and same signals
		
		if(!this.future.isEmpty())
		{
			if(this.history.wantToFloat())
			{
				while(result && !this.future.isEmpty())
				{
					if(this.future.getItemAt(0).wantToFloat())
						this.future.removeItemAt(0);
					else
						result = false;
				}	
			}
			else if(!this.future.getItemAt(0).wantToFloat())
			{
				while(result && !this.future.isEmpty())
				{
					if((this.history.getValue() == this.future.getItemAt(0).getValue()) && (this.history.isUndefined() == this.future.getItemAt(0).isUndefined()) && (this.history.getSource() == this.future.getItemAt(0).getSource()) && (this.history.getPin() == this.future.getItemAt(0).getPin()) && !this.future.getItemAt(0).wantToFloat())
						this.future.removeItemAt(0);
					else
						result = false;
				}
			}
			else
			{
				while(result && !this.future.isEmpty())
				{
					if(((this.history.getSource() != this.future.getItemAt(0).getSource()) || (this.history.getPin() != this.future.getItemAt(0).getPin())) && this.future.getItemAt(0).wantToFloat())
						this.future.removeItemAt(0);
					else
						result = false;
				}
			}
		}
		
		return this.future.isEmpty();
	}
	
	public void updateHistory() throws EngineException
	{
		// find signals with same source and same time as history (if any) for possible floats
		Signal result = null;
		double currentTime = future.getItemAt(0).getTime();
		
		for(int index = 0; (index < this.future.getSize()) && (result == null); index++)
		{
			result = this.future.getItemAt(index);
			
			if((result.getSource() != this.history.getSource()) || (result.getPin() != this.history.getPin()) || (result.getTime() != currentTime))
				result = null;
		}
		
		if(result != null)
		{
			this.future.removeItem(result);
			this.future.insertItemAt(0, result);
		}
		
		if(!this.future.getItemAt(0).wantToFloat() && !this.history.wantToFloat())
		{
			if(this.future.getItemAt(0).getSource() != this.history.getSource())
			{
				if(this.history.getSource() != null) // handle initialisation
				{
					EngineException ex = new EngineException("Several components are trying to drive same node: " + this.history.getSource().getParent().toString() + " & " + this.future.getItemAt(0).getSource().getParent().toString());
					
					ex.setFirstAffected(this.history.getSource());
					ex.setSecondAffected(this.future.getItemAt(0).getSource());
					throw ex;
				}
			}
			else if(this.future.getItemAt(0).getPin() != this.history.getPin())
			{
				EngineException ex = new EngineException("Several pin on component are trying to drive same node");
				ex.setFirstAffected(this.future.getItemAt(0).getSource());
				ex.setSecondAffected(this.future.getItemAt(0).getSource());
				throw ex;
			}
		}
		
		
		this.history = this.future.getItemAt(0);
		
		if(SimulationProperties.SHOW_FLOW)
			this.wires.respond(this.history);
		
		this.future.removeItemAt(0);
	}
	
	public void addComponent(EnginePeer newValue)
	{
		this.connection.insertDistinctItem(newValue);		// insert item at beginning of List
	}
	
	public void removeComponent(EnginePeer oldValue)
	{
		this.connection.removeItem(oldValue);
	}
	
	public SignalList getFuture()
	{
		return this.future;
	}
	
	public Data getHistory()
	{
		return this.history;
	}
	
	public Signal getCurrentSignal()
	{
		return this.future.getItemAt(0);
	}
	
	public EnginePeerList getConnection()
	{
		return this.connection;
	}
	
	public void addWire(Wire w)
	{
		this.wires.insertItem(w);
	}
	
	public void addJunction(Junction j)
	{
		this.junctions.insertItem(j);
	}
	
	public void reset()
	{
		this.wires.reset();
	}
}
