import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.*;
import java.util.ArrayList;
import java.util.Vector;


public class EdgeObj 

{
    public static final int    	WIDTH = 7;
    private String	    		label;
    private Node	    		startNode, endNode;
    private boolean     		selected;
    private String				equation;	
    private	int 				xpos_equation;
	private int 				ypos_equation;
    private boolean				manual_startPt;
    private boolean 			config_text_condition;
    
    int text_x = 0;
    int text_y = 0;
    int width_string = 0;
    int height_string = 0;
    
    int shift_text_x = 0;
    int shift_text_y = 0;
    
    boolean shift_text_auto = true;
    boolean shift_text_manual = false;
    
    private Color				edge_color;
    private Color				text_color;

    public EdgeObj(Node start, Node end) 
    { 
    	this("", start, end); 
    	this.edge_color = Color.BLACK;
    	this.text_color = Color.BLACK;
    }

    public EdgeObj(Node start, Node end, String equation) 
    { 
    	
    	this.startNode = start;
  		this.endNode = end;
    	this.equation = equation;
    	
    	this.edge_color = Color.BLACK;
    	this.text_color = Color.BLACK;
    }
    
    public EdgeObj(String aLabel, Node start, Node end) 
    {
        label = aLabel;
        startNode = start;
        endNode = end;
        selected = false;
        equation = "abc..";
        xpos_equation = 0;
    	ypos_equation = 0;

    	shift_text_x = 99;
        shift_text_y = -45;
    	
    	manual_startPt = false;
    	this.edge_color = Color.BLACK;
    	this.text_color = Color.BLACK;
    }

    public String getLabel() { return label; }
    public Node getStartNode() { return startNode; }
    public Node getEndNode() { return endNode; }
    public void setLabel(String newLabel) { label = newLabel; }
    public void setStartNode(Node aNode) { startNode = aNode; }
    public void setEndNode(Node aNode) { endNode = aNode; }
    public boolean isSelected() { return selected;}
    public void setSelected(boolean state) { selected = state;}
    public void toggleSelected() { selected = !selected;}
    public void setUnSelected(boolean state) { selected = state; }
    
    public void setEdgeColor(Color c)
  	{
    	edge_color = c;
  	}
      
  	public Color getEdgeColor()
  	{
  		return edge_color;
  	}
  	
  	public void setTextColor(Color c)
  	{
  		text_color = c;
  	}
  	    
  	public Color getTextColor()
  	{
  		return text_color;
  	}

    public Node otherEndFrom(Node aNode) 
    {
        if (startNode == aNode)
            return endNode;
        else
            return startNode;
    }

    public String toString() 
    {
        return(startNode.toString() + " --> " + endNode.toString());
    }
    
    public void draw(Graphics g) 
    {
        if (selected) 
        {
            g.setColor(Color.RED);

            double xDiff = Math.abs(startNode.getLocation().getX() - endNode.getLocation().getX());
            double yDiff = Math.abs(startNode.getLocation().getY() - endNode.getLocation().getY());
            for (int i= -WIDTH/2; i<=WIDTH/2; i++) 
            {
                if (yDiff > xDiff)
                     g.drawLine((int)startNode.getLocation().getX()+i,
                    		 	(int)startNode.getLocation().getY(),
                    		 	(int)endNode.getLocation().getX()+i,
                    		 	(int)endNode.getLocation().getY());
                else
                     g.drawLine((int)startNode.getLocation().getX(),
                    		 	(int)startNode.getLocation().getY()+i,
                    		 	(int)endNode.getLocation().getX(),
                    		 	(int)endNode.getLocation().getY()+i);
            }
        }
        else
        {    
             g.setColor(getEdgeColor());
        }    
       
        drawArrowHead( g ,(int)startNode.getLocation().getX(), (int)startNode.getLocation().getY(),   (int)endNode.getLocation().getX(), (int)endNode.getLocation().getY());
    }
    
    public void drawArrowHead(Graphics g, int x1 , int y1, int x2, int y2 ) //, Color color)
    {
        int RADIUS = 15; 
        
        Ellipse2D shape_circle;
        Ellipse2D circle1;
        Ellipse2D circle2;
         
        int w = 2 * RADIUS;
        int h = w;
        
        circle1 = null;
        circle1 = new Ellipse2D.Double(x1 - w/2, y1 - h/2, w, h);
        circle2 = null;
        circle2 = new Ellipse2D.Double(x2 - w/2, y2 - h/2, w, h);          
        
        double from = angleBetween(circle1, circle2);
        double to = angleBetween(circle2, circle1);
        Point2D pointFrom = getPointOnCircle(circle1, from);
        Point2D pointTo = getPointOnCircle(circle2, to); 
         
        double x1_new = pointFrom.getX();
        double y1_new = pointFrom.getY();
        double x2_new = pointTo.getX();
        double y2_new = pointTo.getY();
    
        double degrees = 30.0;
        double radians = Math.toRadians(degrees);
        double L1 = Math.sqrt((x2_new-x1_new)*(x2_new-x1_new) + (y2_new-y1_new)*(y2_new-y1_new));	
        double L2 = 10;
        double coef = (double) L2 / L1;
        double x3_new = x2_new + coef * ( (x1_new - x2_new) * Math.cos(radians) + (y1_new - y2_new) * Math.sin(radians) )  ; 
        double y3_new = y2_new + coef * ( (y1_new - y2_new) * Math.cos(radians) - (x1_new - x2_new) * Math.sin(radians) )  ; 
        double x4_new = x2_new + coef * ( (x1_new - x2_new) * Math.cos(radians) - (y1_new - y2_new) * Math.sin(radians) )  ; 
        double y4_new = y2_new + coef * ( (y1_new - y2_new) * Math.cos(radians) + (x1_new - x2_new) * Math.sin(radians) )  ;  
        //g.setColor(Color.RED);
        g.drawLine((int)x2_new, (int)y2_new, (int)x3_new, (int)y3_new); 
        g.drawLine((int)x2_new, (int)y2_new, (int)x4_new, (int)y4_new);
        
        g.drawLine((int)x1_new, (int)y1_new, (int)x2_new, (int)y2_new); 
    }    
    
    protected Point2D center(Rectangle2D bounds) 
    {
        return new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
    }

    protected double angleBetween(Shape from, Shape to) 
    {
        return angleBetween(center(from.getBounds2D()), center(to.getBounds2D()));
    }

    protected double angleBetween(Point2D from, Point2D to) 
    {
        double x = from.getX();
        double y = from.getY();

        double deltaX = to.getX() - x;
        double deltaY = to.getY() - y;

        // Calculate the angle...
      
        double rotation = -Math.atan2(deltaX, deltaY);
        rotation = Math.toRadians(Math.toDegrees(rotation) + 180);

        return rotation;
    }

    protected Point2D getPointOnCircle(Shape shape, double radians) 
    {
    	Rectangle2D bounds = shape.getBounds();
        Point2D point = center(bounds);
        return getPointOnCircle(point, radians, Math.max(bounds.getWidth(), bounds.getHeight()) / 2d);
    }

    protected Point2D getPointOnCircle(Point2D center, double radians, double radius) 
    {
    	double x = center.getX();
        double y = center.getY();

        radians = radians - Math.toRadians(90.0); // 0 becomes the top
        // Calculate the outter point of the line
        double xPosy = Math.round((float) (x + Math.cos(radians) * radius));
        double yPosy = Math.round((float) (y + Math.sin(radians) * radius));
        return new Point2D.Double(xPosy, yPosy);
     }  
    
    public void saveTo(PrintWriter aFile) 
    {
    	aFile.println(label);
        aFile.println((int)startNode.getLocation().getX());
        aFile.println((int)startNode.getLocation().getY());
        aFile.println((int)endNode.getLocation().getX());
        aFile.println((int)endNode.getLocation().getY());
        aFile.println(selected);
        aFile.println(equation);
      
    }
   
    public static EdgeObj loadFrom(BufferedReader aFile) throws IOException 
    {
        EdgeObj    anEdge;
        String  aLabel = aFile.readLine();
        Node    start = new Node("TEMP");
        Node    end = new Node("TEMP");

        start.setLocation(Integer.parseInt(aFile.readLine()), Integer.parseInt(aFile.readLine()));
        end.setLocation(Integer.parseInt(aFile.readLine()),  Integer.parseInt(aFile.readLine()));

        anEdge = new EdgeObj(aLabel, start, end);
        anEdge.setSelected(Boolean.valueOf(aFile.readLine()).booleanValue());
        anEdge.setEquation( aFile.readLine() );
        
        return anEdge;
    }
    
    /**********************************************************/
    /**********************************************************/
    
    
    private boolean selectStatus = false;

    private int detection;
    
    public Point startPt, endPt, startCtrlPt,endCtrlPt;
    public int startStateIndex, endStateIndex;
    public CubicCurve2D.Double curve;
    
    private Vector<Point> startBorderPts;
    private Vector<Point> endBorderPts;
    private boolean ready = false;
    
    int xTemp, yTemp;
    
    String startS, endS;
    
    private Color color;	
    
    String objName;

    Node obj_start_node;
    Node obj_end_node;
    
    int xcentre;
	int ycentre;
	int radius = 50;
	int x0;
	int y0;
	int x1;
	int y1;
    int w;
    int h;
    
    int NONE = 0;
    int START = 1;
    int STARTCTRL = 2;
    int ENDCTRL = 3;
    int END = 4;
    
    public EdgeObj()
	{
    	    	
	}
    /*
    public EdgeObj(int numb, Node start_node, Node end_node, Color c)
   	{
       		objName = "trans" + numb;
   	       
       		obj_start_node = start_node;
       		obj_end_node = end_node;
       		
   	        startPt = new Point(0,0);
   	        endPt = new Point(0,0);
   	        startCtrlPt = new Point(0,0);
   	        endCtrlPt = new Point(0,0);
   	        curve = new CubicCurve2D.Double();
   	        color = c;
   	}
    */
    
    public void Edge_init() //   StateObj start, StateObj end)
    {
            setEndPts();
            ready = true;
    }
    
    public void setEndPts()
    {
    	
    	    obj_start_node = startNode;
    	    obj_end_node = 	endNode;
    	
        	startBorderPts = getBorderPts(obj_start_node);
        	endBorderPts = getBorderPts(obj_end_node);
       
            Point startCoords = getRealCenter(obj_start_node);
            Point endCoords = getRealCenter(obj_end_node);
            
            double temp;
            double max = 1000000;
            	   max = 10000000;
            for(int i = 0; i < 36; i++)
            {
                temp = startCoords.distanceSq(endBorderPts.get(i));
                if (temp < max)
                {
                    endStateIndex = i;
                    max = temp;
                }
            }
            max = 1000000;
            max = 10000000;
            for(int i = 0; i < 36; i++)
            {
                temp = endCoords.distanceSq(startBorderPts.get(i));
                if (temp < max)
                {
                    startStateIndex = i;
                    max = temp;
                }
            }
            
            //try to prevent overlapping
            if(startCoords.getX()<endCoords.getX())
            {
                if(startCoords.getY()<endCoords.getY())
                {

                }
                else
                {

                }
            }
            else
            {
                if(startCoords.getY()<endCoords.getY())
                {

                }
                else
                {

                }
            }
            
            startStateIndex-=1;
            if(startStateIndex==-1)
                startStateIndex=35;
            endStateIndex+=1;
            if(endStateIndex==36)
                endStateIndex=0;

            startPt = startBorderPts.get(startStateIndex);
            endPt = endBorderPts.get(endStateIndex);
            
            // distances entre les states
            int dx = (int) (obj_start_node.getLocation().getX()  -	obj_end_node.getLocation().getX());
        	int dy = (int) (obj_start_node.getLocation().getY()  -	obj_end_node.getLocation().getY());
            
            int dx_text = (int) Math.abs(obj_start_node.getLocation().getX()  -	obj_end_node.getLocation().getX());
        	int dy_text = (int) Math.abs(obj_start_node.getLocation().getY()  -	obj_end_node.getLocation().getY());
        	        	
        	int xmin = (int) Math.min(obj_start_node.getLocation().getX() , obj_end_node.getLocation().getX() );      
        	int ymin = (int) Math.min(obj_start_node.getLocation().getY() , obj_end_node.getLocation().getY() );

        	// fait partie des datas de la class EdgeObj
        	xpos_equation = xmin + dx_text / 2;
        	ypos_equation = ymin + dy_text / 2;

        	// configuration
        	/*
        	if( obj_start_node.getLocation().getX() < obj_end_node.getLocation().getX() ) // mode 1
        	{
        		xpos_equation = xmin + dx / 2;
        	}
        	
        	if( obj_start_node.getLocation().getX() > obj_end_node.getLocation().getX() ) // mode 2
        	{
        		xpos_equation = xmin + dx / 2;
        	}
        	
        	
        	if( obj_start_node.getLocation().getY() < obj_end_node.getLocation().getY() ) // mode 1
        	{
        		ypos_equation = ymin + dy / 2;
        	}
        	
        	if( obj_start_node.getLocation().getY() > obj_end_node.getLocation().getY() ) // mode 2
        	{
        		ypos_equation = ymin + dy / 2;
        	}
        	*/
        	
        	
            //scaling for distance of control point from start or end point
            int dxs = (int) (endPt.getX() - startPt.getX()) / 3;
            int dys = (int) (endPt.getY() - startPt.getY()) / 3;
            if(dxs < 0)
                dxs = -dxs;
            if(dys < 0)
                dys = -dys;

            //find angle between states
            double theta = 0;
            if(dx == 0)
            {
                if(dy <= 0)
                    theta = Math.PI/2;
                else
                    theta = 3*Math.PI/2;
            }
            else if (dx > 0 && dy > 0)
                theta = 2*Math.PI - Math.atan((double) dy/dx);
            else if (dx > 0 && dy <= 0)
            {
                if(dy == 0)
                    theta = 0;
                else
                    theta = -Math.atan((double) dy/(dx));
            }
            else if (dx < 0)
                theta = Math.PI - Math.atan((double) dy/dx);

            //determine angle away from start point for start control point
            double adj = Math.PI/6;
            double angleStart = 0;
            if(dx >= 0 && dy >= 0)
                angleStart = -Math.PI + theta + adj;//+

            else if(dx >= 0 && dy < 0)
                angleStart = Math.PI + theta + adj;//+

            else if(dx < 0 && dy >= 0)
                angleStart = -Math.PI + theta + adj;

            else if(dx < 0 && dy < 0)
                angleStart = Math.PI + theta + adj;

            //determine angle away from end point for end control point
            double angleEnd = 0;
            if(dx >= 0)
                angleEnd = theta - adj;
            else if(dx < 0)
                angleEnd = theta - adj;

            //create control point
            startCtrlPt = new Point((int) (startPt.getX()+ (Math.cos(angleStart)*dxs)),(int)(startPt.getY()-(Math.sin(angleStart)*dys)));
            endCtrlPt = new Point((int) (endPt.getX()+ (Math.cos(angleEnd)*dxs)),(int)(endPt.getY()-(Math.sin(angleEnd)*dys)));

            curve = new CubicCurve2D.Double(startPt.getX(),startPt.getY(),startCtrlPt.getX(),startCtrlPt.getY(),endCtrlPt.getX(),endCtrlPt.getY(),endPt.getX(),endPt.getY());

    }
    
    
    public Point getRealCenter(Node obj) 
    {
    	int x = (int) obj.getLocation().getX();
    	int y = (int)obj.getLocation().getY();
    	Point pt = new Point(x,y);
        return pt;
    }

    public Vector<Point> getBorderPts(Node obj) 
    {
        Vector<Point> borderPts = new Vector<Point>(36);
        
        xcentre = (int)  obj.getLocation().getX();
    	ycentre = (int)  obj.getLocation().getY();
    	x0  = xcentre - radius;
    	y0  = ycentre - radius;
    	x1  = xcentre + radius;
    	y1 =  ycentre + radius;
                
        w = x1 - x0;
        h = y1 - y0;

        for (int i = 0; i < 36; i++) 
        {
            int x = (int) ((x0 + w / 2) + (w / 2) * Math.cos((double) (2 * Math.PI / 36) * i));
            int y = (int) ((y0 + h / 2) + (h / 2) * Math.sin((double) (2 * Math.PI / 36) * i));
            Point coord = new Point(x, y);
            borderPts.add(i, coord);
        }
        return borderPts;

    }
    
    
    public void draw(Graphics2D g2D)
    {
    	  g2D.draw( curve );
    	/*  
    	  System.out.println("Curve update \n " +
    			  			 startPt.getX()     + " " +
    			  			 startPt.getY() 	   + " " +
    			  			 startCtrlPt.getX() + " " +
    			  			 startCtrlPt.getY() + " " +
    			  			 endCtrlPt.getX()   + " " +
    			  			 endCtrlPt.getY()   + " " +	
    			  			 endPt.getX()       + " " +
    			  			 endPt.getY() );
    	  */
    	  // find angle between end point and end control point
          int dx = (int) endCtrlPt.getX() - (int) endPt.getX();
          int dy = (int) endCtrlPt.getY() - (int) endPt.getY();
          double alpha = 0;
          if(dx == 0)
          {
              if(dy <= 0)
                  alpha = Math.PI/2;
              else
                  alpha = 3*Math.PI/2;
          }
          else if (dx > 0 && dy > 0)
              alpha = 2*Math.PI - Math.atan((double) dy/dx);
          else if (dx > 0 && dy <= 0)
          {
              if(dy == 0)
                  alpha = 0;
              else
                  alpha = -Math.atan((double) dy/(dx));
          }
          else if (dx < 0)
              alpha = Math.PI - Math.atan((double) dy/dx);

          double adj = Math.PI/6;
          int[] xP = {(int)endPt.getX(),(int)endPt.getX() + (int)(13*Math.cos(alpha + adj)),(int)endPt.getX() + (int)(13*Math.cos(alpha - adj))};
          int[] yP = {(int)endPt.getY(),(int)endPt.getY() - (int)(13*Math.sin(alpha + adj)),(int)endPt.getY() - (int)(13*Math.sin(alpha - adj))};
          g2D.drawPolygon(xP,yP,3);
          g2D.fillPolygon(xP,yP,3);
          
          // affichage du texte associe à la transistion
          FontMetrics fm1 = g2D.getFontMetrics();
          String text = equation;
          
          int tH = fm1.getHeight();
          int tW = fm1.stringWidth(text);
          int tX = xpos_equation;
          int tY = ypos_equation + tH*1; // 1
          int txbase = tX-tW/2;
          int tybase = tY;
          
          // calculer l'angle entre les deux points par rapport à l'axe des abscisse x
          Double angle = Angle((int)obj_start_node.getLocation().getX() , (int)obj_start_node.getLocation().getY() ,  (int)obj_end_node.getLocation().getX(),  (int)obj_end_node.getLocation().getY());
          
          //System.out.println(" Angle between 2 points " + angle);  
          
          if(selected == true)
          {
        	  g2D.setColor(Color.green);
          }
          else
        	  g2D.setColor(Color.black);
          
          if(config_text_condition == true)
          {
        	  g2D.drawString(equation,text_x,text_y);
          }
          else
          {
        	  g2D.drawString(equation,txbase,tybase);
        	  
        	  text_x = txbase;
              text_y = tybase;
              width_string = g2D.getFontMetrics().stringWidth(text);
              height_string = g2D.getFontMetrics().getHeight();
              
          }
    	
    }
    
    
    public void drawSelection(Graphics2D g2D)
    {
    	
    	g2D.setColor(Color.BLUE);
        g2D.fillRect((int)startPt.getX()-3,(int)startPt.getY()-3,7,7);

        g2D.setColor(Color.RED);
        g2D.fillRect((int)endPt.getX()-3,(int)endPt.getY()-3,7,7);
        
        g2D.setColor(Color.GREEN);
        g2D.fillRect((int)startCtrlPt.getX()-3,(int)startCtrlPt.getY()-3,7,7);

        g2D.setColor(Color.orange);
        g2D.fillRect((int)endCtrlPt.getX()-3,(int)endCtrlPt.getY()-3,7,7);

        g2D.drawLine((int)startPt.getX(),(int)startPt.getY(),(int)startCtrlPt.getX(),(int)startCtrlPt.getY());
        g2D.drawLine((int)endPt.getX(),(int)endPt.getY(),(int)endCtrlPt.getX(),(int)endCtrlPt.getY());
        g2D.setColor(color);
    }
    
    public void setSelectStatus(Boolean status) 
    {
    	selected = status;
    }

    public boolean getSelectStatus() 
    {
        return selected;
    }
    
    private double Angle(int startX, int startY, int endX, int endY)
    {
    	double Rad2Deg = 180.0 / Math.PI;
    	double Deg2Rad = Math.PI / 180.0;
    	
        return Math.atan2(startY - endY, endX - startX) * Rad2Deg;
    }
    
    public boolean detection_transistion(int x, int y)
    {
    	BasicStroke bs = new BasicStroke(10);
        
        if(bs.createStrokedShape(curve).contains(new Point2D.Double(x,y)))
        {
        	return true;
        }

        return false;
    }
    
    public void new_draw(Graphics2D g , boolean recalculation ) 
    {
    	// aucun deplacement du point donc on calcul tous les points
    	if( manual_startPt == false)
    	{	
    		Edge_init();
    	}
    	// force le recalcul si on deplace le state
    	if(recalculation == true)
    	{
    		Edge_init();
    	}
    	
    	if (selected) 
        {
            g.setColor(Color.ORANGE); 
            drawSelection(g);
        }
        else
        {    
             g.setColor(Color.BLACK);
        }    
       
    	draw(g);
    
    }
    
   
    
    public void updateObj()
    {
           moveEndPts();
            curve.setCurve(startPt.getX(),startPt.getY(),startCtrlPt.getX(),startCtrlPt.getY(),endCtrlPt.getX(),endCtrlPt.getY(),endPt.getX(),endPt.getY());
    }
    
    private void moveEndPts()
    {
    	  setEndPts();
    }
    
    public void adjustShapeOrPosition(int x, int y) 
    {
            Point currPt = new Point(x,y);
            double temp;
            double max = 1000000;
            for(int i = 0; i < 36; i++)
            {
                temp = currPt.distanceSq(startBorderPts.get(i));
                if (temp < max)
                {
                    startStateIndex = i;
                    max = temp;
                }
            }
            startPt.setLocation(startBorderPts.get(startStateIndex).getX(),startBorderPts.get(startStateIndex).getY());

            //startPt.x = (int) startBorderPts.get(startStateIndex).getX();
            //startPt.y = (int) startBorderPts.get(startStateIndex).getY();
            
            curve.setCurve(startPt.getX(),startPt.getY(),startCtrlPt.getX(),startCtrlPt.getY(),endCtrlPt.getX(),endCtrlPt.getY(),endPt.getX(),endPt.getY());

            System.out.println("------- \n " + startBorderPts.get(startStateIndex).getX() + " " + startBorderPts.get(startStateIndex).getY());  
        
            manual_startPt = true;
    }
    
    public void dynamic(Graphics2D g  ) 
    {
    	g.setColor(Color.ORANGE); 
        drawSelection(g);
        draw(g);
    }
    
    public String getEquation()
    {
    	return equation;
    }
    
    public void setEquation(String val)
    {
    	equation = val;
    }
    
    public void setX(int val )
    {
    	text_x = val;
    }
    
    public void setY(int val )
    {
    	text_y = val;
    }
   
    public int getX()
    {
    	return text_x;
    }
    
    public int getY()
    {
    	return text_y;
    }
    
    public void set_shift_text_X(int val)
    {
    	shift_text_x = val;
    }
    
    public void set_shift_text_Y(int val)
    {
    	shift_text_y = val;
    }
    
    public int get_shift_text_X()
    {
    	return shift_text_x;
    }
    
    public int get_shift_text_Y()
    {
    	return shift_text_y;
    }
    
    public void set_shift_mode_auto(Boolean val)
    {
    	shift_text_auto = val;
    }
    
    public void set_shift_mode_manual(Boolean val)
    {
    	shift_text_manual = val;
    }
   
    public boolean get_shift_mode_auto()
    {
    	return shift_text_auto;
    }
    
    public boolean get_shift_mode_manual()
    {
    	return shift_text_manual;
    }
    
    public void config_text_condition(Boolean val) 
    { 
    	config_text_condition = val;
   	}
    
    /*
    public void redraw_text_condition(Graphics2D g2D)
    {
   	     g2D.drawString(equation,text_x, text_y);
    }
    */
}
