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();
		}
		int gridGap = CentralPanel.ACTIVE_GRID.getCurrentGridGap();
		//System.out.println("start = (" + this.start.getLocation().x / gridGap + ", " + this.start.getLocation().y / gridGap + ")");
		//System.out.println("end = (" + this.end.getLocation().x / gridGap + ", " + this.end.getLocation().y / gridGap + ")");

	}

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