import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.io.*;
import java.util.*;

public class Graph 
{
    private String            label;
    private ArrayList<Node>   nodes;

    public Graph() 
    { 
    	this("", new ArrayList<Node>()); 
  	}

    public Graph(String aLabel) 
    { 
    	this(aLabel, new ArrayList<Node>()); 
   	}

    public Graph(String aLabel, ArrayList<Node> initialNodes) 
    {
        label = aLabel;
        nodes = initialNodes;
    }
    
    public ArrayList<Node> getNodes() { return nodes; }
    public String getLabel() { return label; }
    public void setLabel(String newLabel) { label = newLabel; }

    // Graphs look like this: label(6 nodes, 15 edges)
    public String toString() 
    {
        return(label + "(" + nodes.size() + " nodes, " + getEdges().size() + " edges)");
    }
    
    public ArrayList<EdgeObj> getEdges() 
    {
        ArrayList<EdgeObj>  edges = new ArrayList<EdgeObj>();

        for (Node n: nodes) 
        {
            for (EdgeObj e: n.incidentEdges()) 
            {
                if (!edges.contains(e)) //so that it is not added twice
                    edges.add(e);
            }
        }

        return edges;
    }

    // Add the given Node to the graph
    public void addNode(Node aNode) 
    {
        nodes.add(aNode);
    }

    // Add an edge to the graph from the given start Node to the end Node
    public void addEdge(Node start, Node end) 
    {
        // First make the edge
        EdgeObj anEdge = new EdgeObj(start, end);

        // Now tell the nodes about the edge
        start.addIncidentEdge(anEdge);
        end.addIncidentEdge(anEdge);
    }

    // Add an edge by connecting the Nodes with the given labels
    public void addEdge(String startLabel, String endLabel) 
    {
        Node start = nodeNamed(startLabel);
        Node end = nodeNamed(endLabel);
        if ((start != null) && (end != null))
            addEdge(start, end);
    }

    // Delete the given Edge from the graph
    public void deleteEdge(EdgeObj anEdge) 
    {
        // Just ask the nodes to remove it
        anEdge.getStartNode().incidentEdges().remove(anEdge);
        anEdge.getEndNode().incidentEdges().remove(anEdge);
    }

    // Delete the given Node from the graph
    public void deleteNode(Node aNode) 
    {
        // Remove the opposite node's incident edges
        for (EdgeObj e:   aNode.incidentEdges())
            e.otherEndFrom(aNode).incidentEdges().remove(e);

        nodes.remove(aNode);  // Remove the node now
    }

    // Find and return the Node with the given label
    public Node nodeNamed(String aLabel) 
    {
        for (Node n:  nodes)
            if (n.getLabel().equals(aLabel))  return n;

        return null;  // If we don't find one
    }

    
 // Find and return the Node with the given label
    public int nodePosition(String aLabel) 
    {
    	int cpt = 0;
    	
        for (Node n:  nodes)
        {	
            if (n.getLabel().equals(aLabel)) 
            {	
            	return cpt;
            }
            cpt = cpt + 1;
        }    
            
        return cpt;  
    }
    
    public static Graph example() 
    {
        Graph    myMap = new Graph("Dev and debug FSM ");
     //   myMap.addNode(new Node("Ottawa", new Point2D.Double(250,100)));
     //   myMap.addNode(new Node("Toronto", new Point2D.Double(100,120)));
     //   myMap.addNode(new Node("Kingston", new Point2D.Double(200,130)));
     //   myMap.addNode(new Node("Montreal", new Point2D.Double(300,70)));
     //   myMap.addEdge("Ottawa", "Toronto");
     //   myMap.addEdge("Ottawa", "Montreal");
     //   myMap.addEdge("Ottawa", "Kingston");
     //   myMap.addEdge("Kingston", "Toronto");
        
        myMap.addNode(new Node("State0", new Point2D.Double(250,100)));
        myMap.addNode(new Node("State1", new Point2D.Double(400,350)));
        
        return myMap;
    }

    public void draw(Graphics g) 
    {
        ArrayList<EdgeObj>    edges = getEdges();
        
        for (EdgeObj e: edges)  // Draw the edges first
            e.draw(g);
        for (Node n: nodes)   // Draw the nodes second
            n.draw(g);
    }
    
    public void effacer_tout() 
    {
        ArrayList<EdgeObj>    edges = getEdges();
        
        edges.clear();
        nodes.clear();
        /*
         for (Edge e: edges)  
                 deleteEdge(e);
        
         for (Node n: nodes)   
                  deleteNode(n);
        */
         
    }
    
    
    public  ArrayList<EdgeObj> aqw()
    {
    	   ArrayList<EdgeObj>    edges = getEdges();
		return edges;
    }
    
    
    // Return the first node in which point p is contained, if none, return null
    public Node nodeAt(double x, double y) 
    {
        for (int i=nodes.size()-1; i>=0; i--)
        {
            Node n = nodes.get(i);
            Point2D c = n.getLocation();
            double d = (x - c.getX()) * (x - c.getX()) +  (y - c.getY()) * (y - c.getY());
            if (d <= (Node.RADIUS*Node.RADIUS))
                return n;
        }
        return null;
    }

    // Return first edge in which point p is near midpoint; if none, return null
    public EdgeObj edgeAt(double x, double y) 
    {
        for (EdgeObj e:  getEdges()) 
        {
            Node n1 = e.getStartNode();
            Node n2 = e.getEndNode();
            double xDiff = n2.getLocation().getX() - n1.getLocation().getX();
            double yDiff = n2.getLocation().getY() - n1.getLocation().getY();
            double distance = Math.abs(xDiff*(n1.getLocation().getY() - y) -  (n1.getLocation().getX() - x)*yDiff) /   Math.sqrt(xDiff*xDiff + yDiff*yDiff);
            
            if (distance <= 5) 
            {
                if (Math.abs(xDiff) > Math.abs(yDiff)) 
                {
                    if (((x < n1.getLocation().getX()) && (x > n2.getLocation().getX())) || ((x > n1.getLocation().getX()) && (x < n2.getLocation().getX())))
                        return e;
                }
                else
                if (((y < n1.getLocation().getY()) && (y > n2.getLocation().getY())) ||  ((y > n1.getLocation().getY()) && (y < n2.getLocation().getY())))
                    return e;
            }
        }
        return null;
    }

    public EdgeObj textEdgeAt(double x, double y) 
    {
    	
    	for (EdgeObj e:  getEdges()) 
        {
    		int x1 = e.text_x;
        	int y1 = e.text_y - e.height_string;
        	int x2 = x1 + e.width_string;
        	int y2 = e.text_y; 
        	
        	if( x >= x1 && x <= x2 && y >= y1 && y <= y2  )
            {
        		return e;
            }
    		
        } 
    	return null;
    }

    // Get all the nodes that are selected
    public ArrayList<Node> selectedNodes() 
    {
        ArrayList<Node>   selected = new ArrayList<Node>();
        for (Node n:  nodes)
            if (n.isSelected()) selected.add(n);
        return selected;
    }

   
    public int compte_selectedNodes() 
    {
        //ArrayList<Node>   selected = new ArrayList<Node>();
        int cpt = 0;
        
        for (Node n:  nodes)
            if (n.isSelected()) cpt = cpt + 1;
        return cpt;
    }
    
    
    public String find_selected()
    {
        int position = 0;
        
        for (Node n:  nodes)
        {    
            if (n.isSelected())
            {     
                  String label =  n.getLabel();
                  String retour= label; // + "-" + Integer.toString(position) ;
                  return(retour);  
            } 
            position = position + 1;
        }          
        
        return(null);
    }
    
    
    // Get all the edges that are selected
    public ArrayList<EdgeObj> selectedEdges() 
    {
        ArrayList<EdgeObj>   selected = new ArrayList<EdgeObj>();
        for (EdgeObj e:  getEdges())
            if (e.isSelected()) selected.add(e);
        return selected;
    }

    // Save the graph to the given file.
    public void saveTo(PrintWriter aFile) 
    {
        aFile.println(label);

        // Output the nodes
        aFile.println(nodes.size());
        for (Node n:  nodes)
            n.saveTo(aFile);

        // Output the edges
        ArrayList<EdgeObj> edges = getEdges();
        aFile.println(edges.size());
        for (EdgeObj e:  edges)
            e.saveTo(aFile);
    }

    // Load a Graph from the given file.  After the nodes and edges are loaded,
    // We'll have to go through and connect the nodes and edges properly.
    public static Graph loadFrom(BufferedReader aFile) throws IOException 
    {
        // Read the label from the file and make the graph
        Graph    aGraph = new Graph(aFile.readLine());

        // Get the nodes and edges
        int numNodes = Integer.parseInt(aFile.readLine());
        for (int i=0; i<numNodes; i++)
            aGraph.addNode(Node.loadFrom(aFile));

        // Now connect them with new edges
        int numEdges = Integer.parseInt(aFile.readLine());
        for (int i=0; i<numEdges; i++) 
        {
            EdgeObj tempEdge = EdgeObj.loadFrom(aFile);
            Node start = aGraph.nodeAt(tempEdge.getStartNode().getLocation().getX(),  tempEdge.getStartNode().getLocation().getY());
            Node end = aGraph.nodeAt(tempEdge.getEndNode().getLocation().getX(),  tempEdge.getEndNode().getLocation().getY());
            aGraph.addEdge(start, end);
        }
        
        return aGraph;
    }
  
    
    /*******************************************************/
    /******************************************************/
    
    public void new_draw(Graphics2D g, boolean recalculation) 
    {
        ArrayList<EdgeObj>    edges = getEdges();
        
        for (EdgeObj e: edges)  // Draw the edges first
        {	
        
        	//Node   state_start_obj   =e.getStartNode();
        	//Node   state_end_obj   =  e.getEndNode();
        	
        	 e.new_draw(g, recalculation);
        	
        }
           
        
        for (Node n: nodes)   // Draw the nodes second
            n.draw(g );
    }
    
    
    public void dynamic_draw(Graphics2D g ) 
    {
        ArrayList<EdgeObj>    edges = getEdges();
        
        for (EdgeObj e: edges)  // Draw the edges first
        {	
        	 e.dynamic(g); 
        }
           
        
        for (Node n: nodes)   // Draw the nodes second
        {
        	n.draw(g);
        }
    }
    
 // Return first edge in which point p is near midpoint; if none, return null
    public EdgeObj New_edgeAt(double x, double y) 
    {
        for (EdgeObj e:  getEdges()) 
        {
        	if( e.detection_transistion( (int) x, (int) y) == true )
        	{
        		System.out.println("Transaction selected");
        		return e;
        	}
        }
        return null;
    }
    
    
    public boolean GetEdgeExists(Node start, Node end) 
    {

    	String new_start = start.getLabel();
    	String new_end = end.getLabel();
    	
    	ArrayList<EdgeObj>    edges = getEdges();
    	
    	if(edges.size() != 0)
    	{
	    	for (EdgeObj e:  edges) 
	    	{
	
	    		String exists_start  = e.getStartNode().getLabel();
	    		String exists_end  = e.getEndNode().getLabel();
	        	
	        	
	    		if( new_start.equals(exists_start)  && new_end.equals(exists_end) )        	
	    		{
	    			return true;
	    		}
	        
	    	}
    	}	
    	
        return false;
    }
    
   
    public void new_draw_transistion(Graphics2D g ) 
    {
    	
    }
    
    
    public Node loopBackAt() //double x, double y) 
    {
        for (int i=nodes.size()-1; i>=0; i--)
        {
            Node n = nodes.get(i);
            
            if( n.Get_loopback_status() == true)
            {
            	return n;
            }	
        }
        return null;
    }
    
    /*
    public void addLoopBack(Node selected, int x, int y) 
    {
    	
    	selected.Set_loopback_status(true);
    	
    	int xcentre = (int) selected.getLocation().getX();
    	int ycentre = (int) selected.getLocation().getY();

        int radius = 50;
        int x0  = xcentre - radius;
        int y0  = ycentre - radius;
        int x1  = xcentre + radius;
        int y1 =  ycentre + radius;
			
        LoopbackObj loopback_obj = new  LoopbackObj(x, y, x0, y0, x1, y1);
        
        selected.setLoop(loopback_obj);
    	
    }
    
   */
    
    
    public void updateLoopBack(Node selected ) 
    {
    	
    	
    	//selected.Set_loopback_status(true);
    	
    	int xcentre = (int) selected.getLocation().getX();
    	int ycentre = (int) selected.getLocation().getY();

        int radius = 50;
        int x0  = xcentre - radius;
        int y0  = ycentre - radius;
        int x1  = xcentre + radius;
        int y1 =  ycentre + radius;
		
        
        selected.updateCoordLoopBack(xcentre,ycentre);
        
     //   selected.updateCoordLoopBack(x0, y0, x1, y1);
        
    	
       
    }
    
    
    public void affiche_draw(Graphics2D g, boolean recalculation) 
    {
        ArrayList<EdgeObj>    edges = getEdges();
        
        for (EdgeObj e: edges)  
        {	
        	 e.new_draw(g, recalculation);
        }
           
        
        for (Node n: nodes)  
        {	
            if(n.Get_loopback_status() == true)
            {
            	System.out.println("refresh \n");
    			LoopbackObj loopback_obj = new LoopbackObj();
    			loopback_obj = n.getLoopBack();
    			loopback_obj.drawing(g); 
            }
        	n.draw(g );
        }    
    }
    
    
    public LoopbackObj LoopAt(int x, int y) 
    {
        for (Node n:  nodes) 
        {
        	LoopbackObj loopback_obj = new LoopbackObj();
        	loopback_obj = n.getLoopBack();
        	
        	if( loopback_obj.detection_loop(x, y) == true )
        	{
            	return loopback_obj;
        	}
        }
        return null;
    }
    
    
    
    public void blablaLoopBack(Node selected ) 
    {
    	int xcentre = (int) selected.getLocation().getX();
    	int ycentre = (int) selected.getLocation().getY();
        
        selected.mise_a_jour_CoordLoopBack(xcentre, ycentre);
    }
    
    
    public void debug_loop(Node selected ) 
    {
        
        selected.debug_loop();
    }
    
    
    public void recalculation(Node selected ) 
    {
        
        selected.recalculation();
    }
 
}

