package sim.lib.wires;

import java.awt.*;

import sim.*;
import sim.engine.*;

public abstract class Wire extends WrapperPainted
{
/* ==================================================================
	GUI part
	================================================================= */
	protected int orientation;
	public static final int VERTICAL_ORIENTAION = 0;
	public static final int HORIZONTAL_ORIENTATION = 1;
	public static final int THICKNESS = 7;
	
	public Wire()
	{
		super();
	}
	
	public void initializeGridSize()
	{
		this.setGridSize(1, 0);
	}
	
	public void setGridLocation(int x, int y)
	{
		this.gridLocation.x = x;
		this.gridLocation.y = y;
		int increment = Wire.THICKNESS / 2;
		
		int size = CentralPanel.ACTIVE_GRID.getCurrentGridGap();
		
		if(this.orientation == Wire.VERTICAL_ORIENTAION)
			this.setLocation(x * size - increment, y * size);
		else
			this.setLocation(x * size, y * size - increment);
	}
	
	public void setGridSize(int gridWidth, int gridHeight)
	{
		this.gridSize.width = gridWidth;
		this.gridSize.height = gridHeight;
		
		if(gridWidth == 0)
			this.orientation = Wire.VERTICAL_ORIENTAION;
		else
			this.orientation = Wire.HORIZONTAL_ORIENTATION;
			
		int gap = CentralPanel.ACTIVE_GRID.getCurrentGridGap();
		
		if(this.orientation == this.VERTICAL_ORIENTAION)
			this.setSize(Wire.THICKNESS, gap * this.gridSize.height);
		else
			this.setSize(gap * this.gridSize.width, Wire.THICKNESS);
		
		this.setGridLocation(this.getGridLocation());
	}
	
	public boolean isVertical()
	{
		return (this.orientation == Wire.VERTICAL_ORIENTAION);
	}
	
	public boolean isHorizontal()
	{
		return (this.orientation == Wire.HORIZONTAL_ORIENTATION);
	}
	
/* ==================================================================
	Maintanance Part
	================================================================= */
	private Junction start = null;
	private Junction end = null;
	private int numberOfNodes;
	
	public int getNumberOfNodes()
	{
		return this.numberOfNodes;
	}
	
	protected void setNumberOfNodes(int value)
	{
		this.numberOfNodes = value;
	}
	
	public Junction getStart()
	{
		return start;
	}
	
	public Junction getEnd()
	{
		return end;
	}
	
	private void setStart(Junction j)
	{
		this.start = j;
	}
	
	private void setEnd(Junction j)
	{
		this.end = j;
	}
	
	public void breakAt(Junction j)
	{
		if((this.end != null) && (this.start != null))
		{
			Point gridPosition = j.getGridLocation();
			
			Wire w;
			
			w = (Wire)this.createWrapper();
			w.setNumberOfNodes(this.getNumberOfNodes());
			w.setGridLocation(gridPosition);
			
			if(this.isHorizontal())
			{
				w.setGridSize(this.getGridSize().width + this.getGridLocation().x - gridPosition.x, 0);
				this.setGridSize(this.getGridSize().width - w.getGridSize().width, 0);
				
				j.setWireOnRight(w);
				w.setStart(j);
				w.setEnd(this.end);
				this.end.setWireOnLeft(w);
				
				j.setWireOnLeft(this);
				this.end = j;
			}
			else
			{
				w.setGridSize(0, this.getGridSize().height + this.getGridLocation().y - gridPosition.y);
				this.setGridSize(0, this.getGridSize().height - w.getGridSize().height);
				
				j.setWireBelow(w);
				w.setStart(j);
				w.setEnd(this.end);
				this.end.setWireAbove(w);
				
				j.setWireAbove(this);
				this.end = j;
			}
			
			CentralPanel.ACTIVE_GRID.addModule(w);
		}
	}
	
	public void mergeWith(Wire w)	// used in Junction.check() and WireAddListener.handleMouseClick()
	{
		if((this.end != null) && (this.start != null))
		{
			Junction j;
			
			if(this.isHorizontal())
			{
				this.setGridSize(this.getGridSize().width + w.getGridSize().width, 0);
				
				j = w.getEnd();
				j.setWireOnLeft(this);
				this.setEnd(j);
				
				CentralPanel.ACTIVE_GRID.removeModule(w, false);
			}
			else
			{
				this.setGridSize(0, this.getGridSize().height + w.getGridSize().height);
				
				j = w.getEnd();
				j.setWireAbove(this);
				this.setEnd(j);
				
				CentralPanel.ACTIVE_GRID.removeModule(w, false);
			}
		}
	}
	
	public void selected()
	{
		if(this.isHorizontal())
		{
			this.start.resetWireOnRight();
			this.end.resetWireOnLeft();
		}
		else
		{
			this.start.resetWireBelow();
			this.end.resetWireAbove();
		}
		
		this.changeColor(Color.green);
	}
	
	public void checkAfterSelected()
	{
		if(CentralPanel.ACTIVE_GRID.isAncestorOf(this.start))
			this.start.check();
			
		this.start = null;
		
		if(CentralPanel.ACTIVE_GRID.isAncestorOf(this.end))
			this.end.check();
			
		this.end = null;
	}
	
	public boolean canDrop()
	{
		int loop;
		Component comp;
		boolean result = true;
		
		if(this.isHorizontal())
		{
			result = Wrapper.canDropJuncion(this.gridLocation, this.numberOfNodes);
			
			if(result)
				result = Wrapper.canDropJuncion(this.gridLocation.x + this.gridSize.width, this.gridLocation.y, this.numberOfNodes);
			
			for(loop = 1; (loop < this.gridSize.width) && result; loop++)
			{
				comp = CentralPanel.ACTIVE_GRID.getComponentAt(CentralPanel.ACTIVE_GRID.convertToNormal(this.gridLocation.x + loop, this.gridLocation.y));
				if(comp instanceof Junction)
					result = ( ((Junction)comp).getNumberOfNodes() == this.numberOfNodes );
			}
		}
		else
		{
			result = Wrapper.canDropJuncion(this.gridLocation, this.numberOfNodes);
			
			if(result)
				result = Wrapper.canDropJuncion(this.gridLocation.x, this.gridLocation.y + this.gridSize.height, this.numberOfNodes);
			
			for(loop = 1; (loop < this.gridSize.height) && result; loop++)
			{
				comp = CentralPanel.ACTIVE_GRID.getComponentAt(CentralPanel.ACTIVE_GRID.convertToNormal(this.gridLocation.x, this.gridLocation.y + loop));
				if(comp instanceof Junction)
					result = ( ((Junction)comp).getNumberOfNodes() == this.numberOfNodes );
			}
		}
		
		return result;
	}
	
	public void droped()
	{
		this.changeColor(Color.black);
		
		if(this.isHorizontal())
			this.dropedHorizontal();
		else
			this.dropedVertical();
	}
	
	public static Wire getOverlapedHorizontalWire(Point gridPosition)
	{
		Point right = CentralPanel.ACTIVE_GRID.convertToNormal(gridPosition);
		Point left = CentralPanel.ACTIVE_GRID.convertToNormal(gridPosition);
			
		left.x = left.x + CentralPanel.ACTIVE_GRID.getCurrentGridGap() / 2;
		right.x = right.x - CentralPanel.ACTIVE_GRID.getCurrentGridGap() / 2;
		
		Wrapper wRight = (Wrapper)CentralPanel.ACTIVE_GRID.getComponentAt(right);
		Wrapper wLeft = (Wrapper)CentralPanel.ACTIVE_GRID.getComponentAt(left);
		
		Wire result = null;
		
		if((wRight instanceof Wire) && (wRight == wLeft))
			result = (Wire)wRight;
		
		return result;
	}
	
	private boolean isOverlapedByHorizontalWire()
	{
		Point right = this.getGridLocation();
		right = CentralPanel.ACTIVE_GRID.convertToNormal(right);
		right.x = right.x +  this.getSize().width + CentralPanel.ACTIVE_GRID.getCurrentGridGap() / 2;
		
		Point left = this.getGridLocation();
		left = CentralPanel.ACTIVE_GRID.convertToNormal(left);
		left.x = left.x - CentralPanel.ACTIVE_GRID.getCurrentGridGap() / 2;
		
		Wrapper wRight = (Wrapper)CentralPanel.ACTIVE_GRID.getComponentAt(right);
		Wrapper wLeft = (Wrapper)CentralPanel.ACTIVE_GRID.getComponentAt(left);
		
		return ((wRight instanceof Wire) && (wRight == wLeft));
	}
	
	public static Wire getOverlapedVerticalWire(Point gridPosition)
	{
		Point up = CentralPanel.ACTIVE_GRID.convertToNormal(gridPosition);
		Point down = CentralPanel.ACTIVE_GRID.convertToNormal(gridPosition);
		
		down.y = down.y + CentralPanel.ACTIVE_GRID.getCurrentGridGap() / 2;
		up.y = up.y - CentralPanel.ACTIVE_GRID.getCurrentGridGap() / 2;
		
		Wrapper wUp = (Wrapper)CentralPanel.ACTIVE_GRID.getComponentAt(up);
		Wrapper wDown = (Wrapper)CentralPanel.ACTIVE_GRID.getComponentAt(down);
		
		Wire result = null;
		
		if((wUp instanceof Wire) && (wUp == wDown))
			result = (Wire)wUp;
		
		return result;
	}
	
	private boolean isOverlapedByVerticalWire()
	{
		Point up = this.getGridLocation();
		up = CentralPanel.ACTIVE_GRID.convertToNormal(up);
		up.y = up.y - CentralPanel.ACTIVE_GRID.getCurrentGridGap() / 2;
		
		Point down = this.getGridLocation();
		down = CentralPanel.ACTIVE_GRID.convertToNormal(down);
		down.y = down.y + this.getSize().height + CentralPanel.ACTIVE_GRID.getCurrentGridGap() / 2;
		
		Wrapper wUp = (Wrapper)CentralPanel.ACTIVE_GRID.getComponentAt(up);
		Wrapper wDown = (Wrapper)CentralPanel.ACTIVE_GRID.getComponentAt(down);
		
		return ((wUp instanceof Wire) && (wUp == wDown));
	}
	
	private void dropedHorizontal()
	{
		int end = this.getGridLocation().x + this.getGridSize().width;
		boolean stop = false;
		boolean remove = false;
		Wrapper intercept;
		Junction jIntercept;
		Wire overlap;
		
		// check that wire is not completely overlaped by other wire
		if(this.isOverlapedByHorizontalWire())
		{
			stop = true;
			remove = true;
		}
		
		// check right end
		intercept = (Wrapper)CentralPanel.ACTIVE_GRID.getComponentAt(CentralPanel.ACTIVE_GRID.convertToNormal(this.getGridLocation()));
		
		if(intercept instanceof Junction)
		{
			jIntercept = (Junction)intercept;
			if(!jIntercept.wireOnRight())
			{
				this.start = jIntercept;
				this.start.setWireOnRight(this);
			}
			else
			{
				stop = true;
				overlap = jIntercept.getWireOnRight();
				
				if(this.getGridSize().width <= overlap.getGridSize().width)
					remove = true;
				else
				{
					CentralPanel.ACTIVE_GRID.eraseComponent(this);
					this.setGridLocation(overlap.getGridLocation().x + overlap.getGridSize().width, this.getGridLocation().y);
					this.setGridSize(this.getGridSize().width - overlap.getGridSize().width, 0);
					CentralPanel.ACTIVE_GRID.paintComponent(this);
					this.dropedHorizontal();
				}
			}
		}
		
		// if not stopped
		for(int x = this.getGridLocation().x + 1; (x <= end) && (!stop); x++)
		{	
			intercept = (Wrapper)CentralPanel.ACTIVE_GRID.getComponentAt(CentralPanel.ACTIVE_GRID.convertToNormal(x, this.getGridLocation().y));
			if(intercept instanceof Junction)
			{
				stop = true;
				jIntercept = (Junction)intercept;
				
				if(!jIntercept.wireOnLeft())
				{
					this.end = jIntercept;
					this.end.setWireOnLeft(this);
					
					if(x != end)
					{	
						this.setGridSize(x - this.getGridLocation().x, 0);
						
						overlap = (Wire)this.createWrapper();
						overlap.setGridLocation(x, this.getGridLocation().y);
						overlap.setGridSize(end - x, 0);
						CentralPanel.ACTIVE_GRID.addModule(overlap);
						overlap.dropedHorizontal();
					}
				}
				else
				{
					if(x != end)
					{
						CentralPanel.ACTIVE_GRID.eraseComponent(this);
						this.setGridSize(this.getGridSize().width - x + this.getGridLocation().x, 0);
						this.setGridLocation(x, this.getGridLocation().y);
						CentralPanel.ACTIVE_GRID.paintComponent(this);
						this.dropedHorizontal();
					}
					else
						remove = true;
				}
			}
		}
		
		if(remove)
			CentralPanel.ACTIVE_GRID.removeModule(this);
		else if(CentralPanel.ACTIVE_GRID.isAncestorOf(this))
		{
			if(this.start == null)
			{
				this.start = new Junction(this.getNumberOfNodes());
				this.start.setWireOnRight(this);
				this.start.setGridLocation(this.getGridLocation());
				CentralPanel.ACTIVE_GRID.addModule(this.start, false);
				
				overlap = this.getOverlapedVerticalWire(this.getGridLocation());
				
				if(overlap != null)
					overlap.breakAt(this.start);
			}
			
			Point pRight = new Point(end, this.getGridLocation().y);
			
			if(this.end == null)
			{
				this.end = new Junction(this.getNumberOfNodes());
				this.end.setWireOnLeft(this);
				this.end.setGridLocation(pRight);
				CentralPanel.ACTIVE_GRID.addModule(this.end, false);
				
				overlap = this.getOverlapedVerticalWire(pRight);
				
				if(overlap != null)
					overlap.breakAt(this.end);
			}
			
			this.end.check();
			this.start.check();
		}
	}
	
	private void dropedVertical()
	{
		int end = this.getGridLocation().y + this.getGridSize().height;
		boolean stop = false;
		boolean remove = false;
		Wrapper intercept;
		Junction jIntercept;
		Wire overlap;
		
		// check that wire is not completely overlaped by other wire
		if(this.isOverlapedByVerticalWire())
		{
			stop = true;
			remove = true;
		}
		
		// check right end
		intercept = (Wrapper)CentralPanel.ACTIVE_GRID.getComponentAt(CentralPanel.ACTIVE_GRID.convertToNormal(this.getGridLocation()));
		
		if(intercept instanceof Junction)
		{
			jIntercept = (Junction)intercept;
			if(!jIntercept.wireBelow())
			{
				this.start = jIntercept;
				this.start.setWireBelow(this);
			}
			else
			{
				stop = true;
				overlap = jIntercept.getWireBelow();
				
				if(this.getGridSize().height <= overlap.getGridSize().height)
					remove = true;
				else
				{
					CentralPanel.ACTIVE_GRID.eraseComponent(this);
					this.setGridLocation(this.getGridLocation().x, overlap.getGridLocation().y + overlap.getGridSize().height);
					this.setGridSize(0, this.getGridSize().height - overlap.getGridSize().height);
					CentralPanel.ACTIVE_GRID.paintComponent(this);
					this.dropedVertical();
				}
			}
		}
		
		// if not stopped
		for(int y = this.getGridLocation().y + 1; (y <= end) && (!stop); y++)
		{	
			intercept = (Wrapper)CentralPanel.ACTIVE_GRID.getComponentAt(CentralPanel.ACTIVE_GRID.convertToNormal(this.getGridLocation().x, y));
			if(intercept instanceof Junction)
			{
				stop = true;
				jIntercept = (Junction)intercept;
				
				if(!jIntercept.wireAbove())
				{
					this.end = jIntercept;
					this.end.setWireAbove(this);
					
					if(y != end)
					{
						this.setGridSize(0, y - this.getGridLocation().y);
						
						overlap = (Wire)this.createWrapper();
						overlap.setGridLocation(this.getGridLocation().x, y);
						overlap.setGridSize(0, end - y);
						CentralPanel.ACTIVE_GRID.addModule(overlap);
						overlap.dropedVertical();
					}
				}
				else
				{
					if(y != end)
					{
						CentralPanel.ACTIVE_GRID.eraseComponent(this);
						this.setGridSize(0, this.getGridSize().height - y + this.getGridLocation().y);
						this.setGridLocation(this.getGridLocation().x, y);
						CentralPanel.ACTIVE_GRID.paintComponent(this);
						this.dropedVertical();
					}
					else
						remove = true;
				}
			}
		}	
		
		if(remove)
			CentralPanel.ACTIVE_GRID.removeModule(this);
		else if(CentralPanel.ACTIVE_GRID.isAncestorOf(this))
		{
			if(this.start == null)
			{
				this.start = new Junction(this.getNumberOfNodes());
				this.start.setWireBelow(this);
				this.start.setGridLocation(this.getGridLocation());
				CentralPanel.ACTIVE_GRID.addModule(this.start, false);
				
				overlap = this.getOverlapedHorizontalWire(this.getGridLocation());
				
				if(overlap != null)
					overlap.breakAt(this.start);
			}
			
			Point pDown = new Point(this.getGridLocation().x, end);
			
			if(this.end == null)
			{
				this.end = new Junction(this.getNumberOfNodes());
				this.end.setWireAbove(this);
				this.end.setGridLocation(pDown);
				CentralPanel.ACTIVE_GRID.addModule(this.end, false);
				
				overlap = this.getOverlapedHorizontalWire(pDown);
				
				if(overlap != null)
					overlap.breakAt(this.end);
			}
			
			this.end.check();
			this.start.check();
		}
	}
/* ==================================================================
	Wire Responce to Signal
	================================================================= */
	public abstract void respondToUndefined();
	public abstract void respondToTrue();
	public abstract void respondToFalse();
	public abstract void reset();
	
/* ==================================================================
	Storage Part
	================================================================= */
	public String getSpecificParameters()
	{
		return (Integer.toString(this.numberOfNodes) + Wrapper.SEPARATOR);
	}
	
	public void loadWrapper(String[] specificParameters) throws SimException
	{
		if(specificParameters.length == this.getNumberOfSpecificParameters())
		{
			try
			{
				this.setNumberOfNodes(Integer.valueOf(specificParameters[0]).intValue());
			}
			catch(NumberFormatException e)
			{
				throw (new SimException("incorrect parameter type"));
			}
		}
		else
			throw (new SimException("incorrect number of parameters"));
	}
	
	public int getNumberOfSpecificParameters()
	{
		return 1;
	}
}
