//###################################################################################### 
package pipe.dataLayer;
//###################################################################################### 
import java.awt.BasicStroke;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;

import pipe.gui.Constants;
import pipe.gui.CreateGui;
//###################################################################################### 
/**
 * <b>Transition</b> - Petri-Net Transition Class
 *
 * @see <p><a href="..\PNMLSchema\index.html">PNML  -  Petri-Net XMLSchema (stNet.xsd)</a>
 * @see </p><p><a href="..\..\..\UML\dataLayer.html">UML  -  PNML Package </a></p>
 * @version 1.0
 * @author James D Bloom
 */
//###################################################################################### 
public class Transition extends PlaceTransitionObject implements Constants {
//###################################################################################### 
	public final static String type = "Transition";

	/** Transition is of Rectangle2D.Double*/
	private GeneralPath transition;
	private Shape proximityTransition;
	/** Place Width */
	public static final int TRANSITION_HEIGHT = PLACE_TRANSITION_HEIGHT;
	/** Place Width */
	public static final int TRANSITION_WIDTH = TRANSITION_HEIGHT/3;
	
	private int angle;
	private boolean enabled = false;
	private boolean enabledBackwards = false;
	public boolean highlighted = false;
	
	
	/** The transition rate */
	private double rate = 1;
	
	/** Is this a timed transition or not?*/
	private boolean timed = false;
	
	private static final double rootThreeOverTwo = 0.5 * Math.sqrt(3); 
	
	/** The transform */
//	private AffineTransform transform;
	
	private ArrayList arcAngleList = new ArrayList();
	/** Counter for the Objects  */
	// private static int count = 0;
	
	/** Used for a unique id */
	//private static int idCount = 0;
//###################################################################################### 
	/**
	 * Create Petri-Net Transition object
	 *
	 * @param positionXInput X-axis Position
	 * @param positionYInput Y-axis Position
	 * @param idInput Transition id
	 * @param nameInput Name
	 * @param nameOffsetXInput Name X-axis Position
	 * @param nameOffsetYInput Name Y-axis Position
	 * @param color Color
	 */
	public Transition(double positionXInput, double positionYInput, String idInput, String nameInput, double nameOffsetXInput, double nameOffsetYInput, double aRate ,boolean timedTransition, int angleInput){
		super(positionXInput, positionYInput, idInput, nameInput, nameOffsetXInput, nameOffsetYInput);
		componentWidth = TRANSITION_HEIGHT; //sets width
		componentHeight = TRANSITION_HEIGHT;//sets height
		rate = aRate;
		timed = timedTransition;
		constructTransition();
		angle = 0;
		setCentre((int)positionX, (int)positionY);
		rotate(angleInput);
		updateBounds();
		//rotate(0);
	}
	
	
	//  /**
	//   * Create Petri-Net Transition object
	//   *
	//   * @param positionXInput X-axis Position
	//   * @param positionYInput Y-axis Position
	//   * @param idInput Transition id
	//   * @param color Color
	//   */
	//  public Transition(double positionXInput, double positionYInput, String idInput){
	//    super(positionXInput, positionYInput, idInput);
	//    
	//    width = WIDTH; //sets width
	//    height = HEIGHT;//sets height
	//
	//    transition = new Rectangle2D.Double(0, 0, width, height);
	//    // enabled = true; //this is for testing purposes only
	//
	//
	//    //transition = new Rectangle2D.Double(0, 0, width, height);
	//    transition = new Rectangle2D.Double( (boundsWidth()-width)/2, (boundsHeight()-height)/2, width, height);
	//
	//  }
	
//###################################################################################### 
	/**
	 * Create Petri-Net Transition object
	 *
	 * @param positionXInput X-axis Position
	 * @param positionYInput Y-axis Position
	 * @param color Color
	 */
	public Transition(double positionXInput, double positionYInput) {
		super(positionXInput, positionYInput);
		componentWidth = TRANSITION_HEIGHT; //sets width
		componentHeight = TRANSITION_HEIGHT;//sets height
		
		constructTransition();
		setCentre((int)positionX, (int)positionY);
		updateBounds();
	}
//###################################################################################### 
	/**
	 * Create Petri-Net Transition object
	 *
	 */
	public Transition(){
	}
//###################################################################################### 
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2 = (Graphics2D)g;
		
		g2.translate(COMPONENT_DRAW_OFFSET, COMPONENT_DRAW_OFFSET);
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		
    if(selected && !ignoreSelection)
      g2.setColor(SELECTION_FILL_COLOUR);
    else
		  g2.setColor(ELEMENT_FILL_COLOUR);
		if (timed) g2.fill(transition);
		
		if (highlighted)
			g2.setPaint(ENABLED_TRANSITION_COLOUR);
		else if (selected && !ignoreSelection)
			g2.setPaint(SELECTION_LINE_COLOUR);
		else
			g2.setPaint(ELEMENT_LINE_COLOUR);
		
		g2.draw(transition);
		if (!timed)
			g2.fill(transition);
	}
//###################################################################################### 
	/** Rotates the Transition through the specified angle around the midpoint */
	public void rotate(int angleInc) {
		angle = (angle + angleInc) % 360;
		transition.transform(AffineTransform.getRotateInstance(Math.toRadians(angleInc),
				componentWidth/2,componentHeight/2));
		outlineTransition();
		Iterator arcIterator = arcAngleList.iterator();
		while (arcIterator.hasNext()) ((ArcAngleCompare)arcIterator.next()).calcAngle();
		Collections.sort(arcAngleList);
		updateEndPoints();
		repaint();
	}
//###################################################################################### 
	private void outlineTransition()
	{
		proximityTransition =
			(new BasicStroke(PLACE_TRANSITION_PROXIMITY_RADIUS)).createStrokedShape(transition);
	}
//###################################################################################### 
	/**
	 * Determines whether Transition is enabled
	 * @param animationStatus Anamation status
	 * @return True if enabled
	 */
	public boolean isEnabled(boolean animationStatus){
		if(animationStatus==true){
			
			// System.out.println("is Enabled called within Transition");
			if(enabled==true){
				highlighted = true;
				return true;
			}
			else {
				highlighted = false;
				return false;
			}
		}
		else return false;
	}
//###################################################################################### 
	/**
	 * Determines whether Transition is enabled backwards
	 * @return True if enabled
	 */
	public boolean isEnabledBackwards(){
		return enabledBackwards;
	}
//###################################################################################### 
	/**
	 * Determines whether Transition is enabled
	 * @return True if enabled
	 */
	public boolean isEnabled(){
		return enabled;
	}
//###################################################################################### 
	/**
	 * Sets whether Transition is enabled
	 * @return enabled if True
	 */
	public void setEnabled(boolean status){
		enabled = status;
	}
//###################################################################################### 
	/**
	 * Sets whether Transition is enabled
	 * @return enabled if True
	 */
	public void setEnabledBackwards(boolean status){
		enabledBackwards = status;
	}
//###################################################################################### 
	/* called at the end of animation to reset Transitions to false*/
	public void setEnabledFalse(){
		enabled = false;
		highlighted = false;
	}
//###################################################################################### 
	/** Returns the width bounds we want to use when initially creating the place on the gui */
	public int boundsWidth() {
		return WIDTH+25;
	}
//###################################################################################### 
	/** Returns the height bounds we want to use when initially creating the place on the gui */
	public int boundsHeight() {
		return HEIGHT+15;
	}
//###################################################################################### 
	/** Returns the distance between the outside of the component to the centre, in order to position the centre of the place where the mouse clicks on the screen */
	public int topOffset() {
		return boundsHeight() /2;
	}
//###################################################################################### 
	/** Returns the distance between the outside of the component to the centre, in order to position the centre of the place where the mouse clicks on the screen */
	public int leftOffset() {
		return boundsWidth() /2;
	}
//###################################################################################### 
	public void setRate(double _rate){
		rate = _rate;
	}
//###################################################################################### 
	public double getRate() {
		return rate;
	}
//######################################################################################
	public int getAngle(){
		return angle;
	}
//###################################################################################### 
	/**Set the timed transition attribute (for GSPNs)*/
	public void setTimed (boolean change) {
		if (timed != change)
		{	
			timed = change;
			repaint();
		}
	}
//###################################################################################### 
	/**Get the timed transition attribute (for GSPNs)*/
	public boolean getTimed () {
		return timed;
	}
//###################################################################################### 
	private void constructTransition() {
		transition = new GeneralPath();
		transition.append(new Rectangle2D.Double((componentWidth-TRANSITION_WIDTH)/2,0,
				TRANSITION_WIDTH, TRANSITION_HEIGHT), false);
		outlineTransition();
	}
//###################################################################################### 
	public boolean contains(int x, int y) {
		
		someArc = CreateGui.getView().createArc;
		if (someArc != null)			// Must be drawing a new Arc if non-NULL.
		{
			if ((proximityTransition.contains(x-COMPONENT_DRAW_OFFSET, y-COMPONENT_DRAW_OFFSET)
					|| transition.contains(x-COMPONENT_DRAW_OFFSET, y-COMPONENT_DRAW_OFFSET))
					&& areNotSameType(someArc.getSource())){
				// assume we are only snapping the target...
				if (someArc.getTarget() != this)
					someArc.setTarget(this);
        someArc.updateArcPosition();
				return true;
			}
			else {
				if (someArc.getTarget() == this) {
					someArc.setTarget(null);
					removeArcCompareObject(someArc);
					updateConnected();
				}
				return false;
			}	
		}
		else
		return transition.contains(x-COMPONENT_DRAW_OFFSET,y-COMPONENT_DRAW_OFFSET);
	}
//###################################################################################### 	
	public void removeArcCompareObject(Arc a)
	{
		Iterator arcIterator = arcAngleList.iterator();
		while (arcIterator.hasNext())
		{
			if (((ArcAngleCompare)arcIterator.next()).arc == a)
				arcIterator.remove();
		}
	}
//###################################################################################### 
	/* (non-Javadoc)
	 * @see pipe.dataLayer.PlaceTransitionObject#updateEndPoint(pipe.dataLayer.Arc)
	 */
	
	public void updateEndPoint(Arc arc) {
		Iterator arcIterator = arcAngleList.iterator();
		boolean match = false;
		while (arcIterator.hasNext()) {
			ArcAngleCompare thisArc = (ArcAngleCompare)arcIterator.next();
			if (thisArc.arc == arc) {
				thisArc.calcAngle();
				match = true;
				break;
			}
		}
		if (!match)
			arcAngleList.add(new ArcAngleCompare(arc, this));
		Collections.sort(arcAngleList);
		
		updateEndPoints();
	}

	public void updateEndPoints() {
		Iterator arcIterator = arcAngleList.iterator();

		ArrayList top = new ArrayList();
		ArrayList bottom = new ArrayList();
		ArrayList left = new ArrayList();
		ArrayList right = new ArrayList();
		
		arcIterator = arcAngleList.iterator();
		while (arcIterator.hasNext()) {
			ArcAngleCompare thisArc = (ArcAngleCompare)arcIterator.next();
			double thisAngle = thisArc.angle - Math.toRadians(angle);
			if (Math.cos(thisAngle) > (rootThreeOverTwo)){
				top.add(thisArc);
				thisArc.arc.setPathToTransitionAngle(angle+90);
			}
			else if (Math.cos(thisAngle) < -rootThreeOverTwo){
				bottom.add(thisArc);
				thisArc.arc.setPathToTransitionAngle(angle+270);
			}
			else if (Math.sin(thisAngle) > 0){
				left.add(thisArc);
				thisArc.arc.setPathToTransitionAngle(angle+180);
			}
			else{
				right.add(thisArc);
				thisArc.arc.setPathToTransitionAngle(angle);
			}
		}
		
		AffineTransform transform = AffineTransform.getRotateInstance(Math.toRadians(angle+Math.PI));
		Point2D.Double transformed = new Point2D.Double();
		
		arcIterator = top.iterator();
		transform.transform(new Point2D.Double(1, 0.5*TRANSITION_HEIGHT), transformed); // +1 due to rounding making it off by 1
		while (arcIterator.hasNext()) {
			ArcAngleCompare thisArc = (ArcAngleCompare)arcIterator.next();
			if (thisArc.sourceOrTarget())
				thisArc.arc.setTargetLocation(positionX+centreOffsetLeft()+transformed.x, positionY+centreOffsetTop()+transformed.y);
			else
				thisArc.arc.setSourceLocation(positionX+centreOffsetLeft()+transformed.x, positionY+centreOffsetTop()+transformed.y);
			thisArc.arc.updateArrow();
		}

		arcIterator = bottom.iterator();
		transform.transform(new Point2D.Double(0, -0.5*TRANSITION_HEIGHT), transformed);
		while (arcIterator.hasNext()) {
			ArcAngleCompare thisArc = (ArcAngleCompare)arcIterator.next();
			if (thisArc.sourceOrTarget())
				thisArc.arc.setTargetLocation(positionX+centreOffsetLeft()+transformed.x, positionY+centreOffsetTop()+transformed.y);
			else
				thisArc.arc.setSourceLocation(positionX+centreOffsetLeft()+transformed.x, positionY+centreOffsetTop()+transformed.y);
			thisArc.arc.updateArrow();
		}

		arcIterator = left.iterator();
		double inc = TRANSITION_HEIGHT/(left.size()+1);
		double current = TRANSITION_HEIGHT/2 - inc;
		while (arcIterator.hasNext()) {
			ArcAngleCompare thisArc = (ArcAngleCompare)arcIterator.next();
			transform.transform(new Point2D.Double(-0.5*TRANSITION_WIDTH, current+1), transformed); // +1 due to rounding making it off by 1
			if (thisArc.sourceOrTarget())
				thisArc.arc.setTargetLocation(positionX+centreOffsetLeft()+transformed.x, positionY+centreOffsetTop()+transformed.y);
			else
				thisArc.arc.setSourceLocation(positionX+centreOffsetLeft()+transformed.x, positionY+centreOffsetTop()+transformed.y);
			current-=inc;
			thisArc.arc.updateArrow();
		}

		inc = TRANSITION_HEIGHT/(right.size()+1);
		current = -TRANSITION_HEIGHT/2 + inc;
		arcIterator = right.iterator();
		while (arcIterator.hasNext()) {
			ArcAngleCompare thisArc = (ArcAngleCompare)arcIterator.next();
			transform.transform(new Point2D.Double(+0.5*TRANSITION_WIDTH, current), transformed);
			if (thisArc.sourceOrTarget())
				thisArc.arc.setTargetLocation(positionX+centreOffsetLeft()+transformed.x, positionY+centreOffsetTop()+transformed.y);
			else
				thisArc.arc.setSourceLocation(positionX+centreOffsetLeft()+transformed.x, positionY+centreOffsetTop()+transformed.y);
			current+=inc;
			thisArc.arc.updateArrow();
		}
	}
	
	class ArcAngleCompare implements Comparable {
		public final static boolean SOURCE = false;
		public final static boolean TARGET = true;
		Arc arc;
		private Transition transition;
		double angle;
		
		public ArcAngleCompare(Arc _arc, Transition _transition) {
			arc = _arc;
			transition = _transition;
			calcAngle();
		}
		public int compareTo(Object arg0) {
			double angle2 = ((ArcAngleCompare)arg0).angle;
			return (angle < angle2 ? -1 : (angle == angle2 ? 0 : 1));
		}
		
		void calcAngle() {
			int index = sourceOrTarget() ? arc.getArcPath().getEndIndex()-1 : 1;
			Point2D.Double p1 = new Point2D.Double(positionX+centreOffsetLeft(), positionY+centreOffsetTop());
			Point2D.Double p2 = new Point2D.Double(arc.getArcPath().getPoint(index).x, arc.getArcPath().getPoint(index).y);
			
			if (p1.y <= p2.y)
				angle = Math.atan((p1.x - p2.x) / (p2.y - p1.y));
			else
				angle = Math.atan((p1.x - p2.x) / (p2.y - p1.y))+Math.PI;
			
			// This makes sure the angle overlap lies at the intersection between edges of a transition
			// Yes it is a nasty hack (aka ingeneous solution). But it works!
			if (angle<(Math.toRadians(30+transition.getAngle()))) angle+=(2*Math.PI);

			// Needed to eliminate an exception on Windows
			if (p1.equals(p2))
				angle = 0;
			
//			if (sourceOrTarget())
//				angle = arc.getArcPath().getEndAngle();
//			else 
//				angle = arc.getArcPath().getStartAngle();
		}
		
		public boolean sourceOrTarget() {
			return (arc.getSource() == transition ? SOURCE : TARGET);
		}
	}
}
//###################################################################################### 


