// GraphCanvas.java
// by Brian Goldsmith and Alexander Smith
// Adapted from SGraphEditorApplet.java
// Copyright (C) 2000 Linyuan Lu
// Code based on David Binger's GraphPanel.java 
// Copyright (C) 1997 David Binger

import java.awt.*;
import java.awt.event.*;

class GraphCanvas extends Canvas implements FocusListener, ActionListener,
    KeyListener, MouseListener, MouseMotionListener, ComponentListener 
{
    // graph contains all information about graph and labels.
    public SGraph graph = new SGraph();
    
    //mode control;
    private int mode=1; //edit mode
    private boolean isEditMode(){return mode==1;};
    private boolean isViewMode(){return mode==0;};
    private boolean initd = false;
    private boolean steinicized = false;

    //set edit mode;
    public void setEditMode(){mode=1;};
    public void setViewMode(){mode=0;};
       
    protected static final int EDIT=1;


    // These are used for off screen buffering.  
    public Image img = null;
    private Dimension imageSize = new Dimension(0,0);
    
    // When all objects are being moved, this gives the last base point.
    private transient Point moveAllFrom = null;
  
    // When a label is being moved, this is the offset of the from
    // the labelBase to the mouse point.
    Point labelOffset = null;

    // This is only used in hitEdge(int,int).
    // It is outside the method to improve speed.
    private List hits = new List();
    
    // keeps track of overall # of Steps 
    private int stepCount = 0;
    
  public GraphCanvas()
  /* no arg constructor */
  {
    super();
    //   addNotify();
    //	setSize(600,400);
    //	show();
    try {initListen();} 
       catch(NullPointerException e){};
  };

  // Register this as the great Listener. (not my comment)
  private final void initListen() {
    addFocusListener((FocusListener)this);
    addKeyListener((KeyListener)this);
    addMouseListener((MouseListener)this);
    addMouseMotionListener((MouseMotionListener)this);
    addComponentListener((ComponentListener)this);
  }
  
  private final void paintParts(Graphics g) {
    g.setColor(Color.white);
    g.fillRect(0,0,imageSize.width,imageSize.height);
    graph.paint(g);
    repaint(0);
  }

  public final void paint(Graphics g) {
    if (img==null) {
      imageSize = getSize(); 
      img = createImage(imageSize.width,imageSize.height);
    }
    Graphics bg = img.getGraphics();
    paintParts(bg);
    g.drawImage(img,0,0,null);
  }
  
  public final void update(Graphics g) {
    paint(g);
  }

  // FocusListener
  public void focusGained(FocusEvent e) { repaint(0); }
  public void focusLost(FocusEvent e){}

  // ActionListener
  public void actionPerformed(ActionEvent e){  }
  // MouseListener
  public void mouseMoved(MouseEvent e) {}

  // Handle the possibility that we are dragging a Vertex or a Vertex label.
  public final boolean mouseDraggedVertex(int x,int y,MouseEvent e)
  {
    if(!isEditMode()) return false;
    if(graph.selected instanceof Vertex) {
      Vertex v = (Vertex)(graph.selected);
      if (labelOffset==null) {
        v.move(x,y);
      }
      else v.moveLabel(x-labelOffset.x,y-labelOffset.y);
      repaint(0);
      return true;
    } else return false;
  }

  // Handle the possibility that we are dragging an Edge or an Edge label.
  public final boolean mouseDraggedEdge(int x,int y,MouseEvent e)
  { 
      if(!isEditMode()) return false;
    if (graph.selected instanceof Edge) {
      Edge ed = (Edge)(graph.selected);
      Vertex v = ed.end;
      if (labelOffset==null) {
        if (!graph.vertices.contains(v)) {
	    //   setCursor(Cursor.getPredefinedCursor(CROSSHAIR_CURSOR));
          v.move(x,y);
        }
        else return false;
      } else {
        ed.moveLabel(x-labelOffset.x,y-labelOffset.y);
      }
      repaint(0);
      return true;
    } else return false;
  }
  
  // MouseListener
  public void mouseDragged(MouseEvent e) { 
      if(!isEditMode()) return;
    int x = e.getX();
    int y = e.getY();
    if (moveAllDrag(x,y,e)) return;
    if (mouseDraggedVertex(x,y,e)) return;
    if (mouseDraggedEdge(x,y,e)) return;
  }

  // MouseListener
  public void mouseExited(MouseEvent e) { 
      if(!isEditMode()) return ;
    labelOffset=null;
    moveAllFrom=null;
    graph.selected=null;
  }

  // MouseListener
  public void mouseEntered(MouseEvent e) {}


  // What Vertex, if any, is at the point x,y?
  public final Vertex hitVertex(int x,int y) {
    Vertex v;
    int n = graph.vertices.length;
    Object a[] = graph.vertices.elements;
    for (int j=0;j<n;j++) {
      v = (Vertex)a[j];
      if (v.hitDisk(x,y)) return v;
    }
    return null;
  }

  // What Edge, if any, is at the point x,y?
  public final Edge hitEdge(int x,int y) {
    Edge e;
    int n = graph.edges.length;
    Object a[] = graph.edges.elements;
    hits.length = 0;
    int limit = 8;
    for (int j=0;j<n;j++) {
      e = (Edge)a[j];
      if (e.near(x,y,limit)) hits.push(e);
    }
    if (hits.length==0) return null;
    while (hits.length>1) {
	if(hits.length==2){//two direction of one edge.
            Edge e0= (Edge)hits.elements[0];
	    Edge e1= (Edge)hits.elements[1];
	    if(e0.start.equals(e1.end) && e1.start.equals(e0.end)){
		Position c=e0.start.position;
		Position b=e0.end.position;
		if((c.x-x)*(c.x-x)+(c.y-y)*(c.y-y)
		   >(b.x-x)*(b.x-x)+(b.y-y)*(b.y-y)) return e0;
		else return e1;
	    }
	}
      hits.length = 0;
      limit--;
      for (int j=0;j<n;j++) {
        e = (Edge)a[j];
        if (e.near(x,y,limit)) hits.push(e);
      }      
    }
    return (Edge)(hits.elements[0]);
  }

  // What Vertex, if any, has a label at the point x,y?
  public final Vertex hitVertexLabel(int x,int y) {
    Vertex v;
    int n = graph.vertices.length;
    Object a[] = graph.vertices.elements;
    for (int j=0;j<n;j++) {
      v = (Vertex)a[j];
      if (v.hitLabel(x,y)) return v;
    }
    return null;
  }

  // What Edge, if any, has a label at the point x,y?
  public final Edge hitEdgeLabel(int x,int y) {
    Edge e;
    int n = graph.edges.length;
    Object a[] = graph.edges.elements;
    for (int j=0;j<n;j++) {
      e = (Edge)a[j];
      if (e.hitLabel(x,y)) return e;
    }
    return null;
  }

  // Make v be the selected item.
  private final void select(Selectable v) {
    graph.select(v);
  }

  // There was a mousePress on vertex v.
  public final boolean pressedOnVertex(Vertex v,MouseEvent e) {
    if (v==null) return false;
    if (0!=(e.getModifiers() & e.ALT_MASK)) {
        v.alter();
        select(v);
        repaint(0);
        return true;
    } 
    if (v.equals(graph.selected)) {
      // Start edge.
      Vertex v2 = new Vertex(e.getX(),e.getY());
      v2.size = 1;  
      Edge ed = graph.addEdge(v,v2);
      select(ed);
    } 
    else { select(v); }
    repaint(0);
    return true;
  }

  // There was a mousePress on the label of v.
  public boolean pressedOnVertexLabel(Vertex v,MouseEvent e) {
    if (v==null) return false;
    labelOffset = new Point(e.getX()-v.labelBase.x,
                    e.getY()-v.labelBase.y);
    select(v);
    repaint(0);
    return true;

  }

  // There was a mousePress on the label of ed.
  public boolean pressedOnEdgeLabel(Edge ed,MouseEvent e) {
    if (ed==null) return false;
    labelOffset = new Point(e.getX()-ed.labelBase.x,
                    e.getY()-ed.labelBase.y);
    select(ed);
    repaint(0);
    return true;
  }
  // There was a mousePress on ed.
  public boolean pressedOnEdge(Edge ed,MouseEvent e) {
    if (ed==null) return false;
    int x = e.getX();
    int y = e.getY();
    select(ed);
    repaint(0);
    return true;
  }  
  /* Handle the possibility that this event should create
     a new Vertex. */
  public boolean newVertex(int x,int y,MouseEvent e) {
    if (e.getClickCount() > 1) {
      Vertex v=new Vertex(x,y);
      graph.addVertex(v);
      int n = graph.vertices.length;
      for(int i=0 ; i < n-1 ; i++){
        Edge eg=new Edge(v,graph.vertex(i));
        int m=graph.edges.length;
        boolean b=false;
        for(int j=0;j<m;j++){
          b= b || graph.isCrossing(eg,graph.edge(j));
        }
        if(!b) graph.addEdge(eg.start,eg.end);
      }
      select(v);	  

      repaint(0);
      return true;
    } else {
      graph.unselect();
      repaint(0);
      return false;
    }
  }
  // MouseListener
  public void mousePressed(MouseEvent e) {
    if(!isEditMode()) return;
    int x = e.getX();
    int y = e.getY();
    if((e.getModifiers() & e.ALT_MASK)!=0){
	//add PopupColorMenu
	PopupColorMenu pcMenu=new PopupColorMenu(Selectable.selected);
	add(pcMenu);
	pcMenu.show(this,e.getX(),e.getY());
	e.consume();
	return;
     }
    if ((e.getModifiers() & (e.SHIFT_MASK | e.CTRL_MASK))!=0) {
	//add PopupColorMenu
	
	if (pressedOnVertex(hitVertex(x,y),e)) return;
	if (pressedOnEdge(hitEdge(x,y),e)) return;
    }
    if (pressedOnVertexLabel(hitVertexLabel(x,y),e)) return;
    if (pressedOnVertex(hitVertex(x,y),e)) return;
    if (pressedOnEdgeLabel(hitEdgeLabel(x,y),e)) return;
    if (pressedOnEdge(hitEdge(x,y),e)) return;
    if (newVertex(x,y,e)) return;
    if (moveAllStart(x,y,e)) return;
  }

  // Begin moving the entire graph.
  public boolean moveAllStart(int x,int y,MouseEvent e) { 
    if(!isEditMode()) return false;
    if (0!=(e.getModifiers() & e.CTRL_MASK)) {
      moveAllFrom = new Point(x,y);
      return true;
    } else return false;    
  }

  // Continue moving the entire graph.
  public boolean moveAllDrag(int x,int y,MouseEvent e) { 
    if(!isEditMode()) return false;
    if (moveAllFrom==null) return false;
    graph.moveRelative(x-moveAllFrom.x,y-moveAllFrom.y);
    moveAllFrom.x = x;
    moveAllFrom.y = y;
    repaint(0);
    return true;
  }
 // Stop moving the entire graph.
  public void moveAllEnd() {
    moveAllFrom = null;
    //    setCursor(Cursor.getPredefinedCursor(DEFAULT_CURSOR));
  }

  // MouseListener
  public void mouseClicked(MouseEvent e) { }

  // MouseListener
  public void mouseReleased(MouseEvent e) {
    Vertex v = hitVertex(e.getX(),e.getY());
    if (labelOffset!=null) mouseDragged(e);
    labelOffset=null;
    if (moveAllFrom!=null) {
      mouseDragged(e);
      moveAllEnd();
      return;
    }
    if (graph.selected instanceof Edge) {
      Edge ed = (Edge)(graph.selected);
      // setCursor(Cursor.getPredefinedCursor(DEFAULT_CURSOR));
      if ((v!=null) && (v!=ed.start)) {
        ed.end = v;
        v.addOutEdge(ed);
        repaint(0);
      } else {
        if (!graph.vertices.contains(ed.end)) {
          graph.delEdge(ed);
          repaint(0);
        }
      }
   }
  }

  // Shorten the selected label.
  public void deleteChar() {
    if (graph.selected!=null) {
      graph.selected.shortenLabel();
      repaint(0);
    }
  }

  // Remove the selected item from the graph.
  public void deleteObject() {
    if (graph.selected!=null) {
      if (graph.selected instanceof Edge) {
        graph.delEdge((Edge)(graph.selected));
      } else if (graph.selected instanceof Vertex) {
        graph.delVertex((Vertex)(graph.selected));
      }
      repaint(0);
    }
    else {
	graph=new SGraph();
	repaint(0);
    }
    
  }

  // Alter the size of the selected item by d.
  public void adjustSize(int d) {
    if (graph.selected!=null) {
      graph.selected.adjustSize(d);
      repaint(0);
    }
  }

 // Alter the size (by d) of all items of the
  // same type as the selected item.
  public void adjustSizes(int d) {
    if ((graph.selected!=null) &&
        (graph.selected instanceof Vertex)) {
      int n = graph.vertices.length;
      for (int j=0;j<n;j++) {
        graph.vertex(j).adjustSize(d);
      }
    }
    if ((graph.selected!=null) &&
        (graph.selected instanceof Edge)) {
      int n = graph.edges.length;
      for (int j=0;j<n;j++) {
        graph.edge(j).adjustSize(d);
      }
    }
    repaint(0);
  }
  // Alter the font size of the selected.
  public void adjustFont(int d) {
    if (graph.selected!=null) {
      graph.selected.adjustFont(d);
    } else graph.adjustFont(d);
    repaint(0);
  }
  /* Alter the font size of all items of the same
     type as the selected. */
  public void adjustFonts(int d) {
    int n = graph.vertices.length;
    Object a[] = graph.vertices.elements;
    for (int j=0;j<n;j++) {
      ((Vertex)a[j]).adjustFont(d);
    }
    n = graph.edges.length;
    a = graph.edges.elements;
    for (int j=0;j<n;j++) {
      ((Edge)a[j]).adjustFont(d);
    }
    repaint(0);
  }
  
  // KeyListener
  public void keyPressed(KeyEvent e) {
    switch (e.getKeyCode()) {
      case 127: // delete
        if (e.getModifiers()==0) deleteChar();
        else deleteObject();
        break;
      case 37: // left
        if (e.getModifiers()==0) adjustSize(-1);
        else adjustSizes(-1);
        break;
      case 39: // right
        if (e.getModifiers()==0) adjustSize(+1);
        else adjustSizes(+1);
        break;
      case 38: // up
        if (e.getModifiers()==0) adjustFont(+1);
        else adjustFonts(+1);
        break;
      case 40: // down
        if (e.getModifiers()==0) adjustFont(-1);
        else adjustFonts(-1);
        break;
    }
  }

  // KeyListener
  public void keyReleased(KeyEvent e) {}

  // KeyListener
  public void keyTyped(KeyEvent e) {
    if (13==e.getKeyCode()) { // newline
      graph.unselect();
      repaint(0);
      return;
    } 
    if (0!=(e.getModifiers() & e.CTRL_MASK)) { return; }
    if (graph.selected!=null) { 
      graph.selected.extendLabel(e.getKeyChar());
      repaint(0);
    }
  }
/*
  public void computeDual()
  {
      Edge e;
      Vertex v;
      boolean isClear = false;

   //reset the dual graph
    if (graph.dualEdges.length > 0)
        isClear = true;

    if (graph.dualVertices.length > 0)
        isClear = true;

    graph.resetDual();

    if (isClear == false)
    {
        //Compute all the face for the given graph
        //Note: in this function, it also place points into faces
        graph.findAllFaces();

        //Determine external face
        graph.findExtFace();

        //Find adjcent faces and connect points
        graph.findDualGraph();
    }
    //Draw the dual graph
    repaint(0);
  } */
  
  public void init()
  { 
    //reset graph
    graph.reset();
    //set up all faces of the graph
    graph.computeFaces();
    //do all the computations for the Steinitz representation
    graph.initAvgR();
    
    int v = graph.vertices.length;
    int e = graph.edges.length;
    int f = graph.faces.length;
    double r = graph.getAvgR();
        
    GraphApplet.gField.setText("vertices = " +v+ "  edges = " +e+ "  faces = "
      +f+ " AvgR = " +r);
    initd = true;
    stepCount = 0;
    //display initial calculations
    graph.writeSteiny("Graph has undergone " +stepCount+ " steps.");
    
  }

  public void stepd()
  {
    if(initd) { //make sure init has been done
      int c = Integer.parseInt((GraphApplet.stepField.getText()).trim());
      if(c > 0) {
        graph.step(c, false);
        stepCount += c;
        graph.writeSteiny("Graph has undergone " +stepCount+ " steps.");
      }
    }
    else
      GraphApplet.gField.setText("Please click the Init button first to initialize.");
      
    GraphApplet.stepField.setText("# steps");
  }

  public void stepInf()
  {
    if(initd){ //make sure init done
      int c = stepCount + graph.infStep();
      steinicized = true;
      graph.writeSteiny("Step Process Completed After " + c + " Steps");
      int v = graph.vertices.length;
      int e = graph.edges.length;
      int f = graph.faces.length;
      double r = graph.getAvgR();
        
      GraphApplet.gField.setText("vertices = " +v+ "  edges = " +e+ "  faces = "
        +f+ " AvgR = " +r);
    }
    else
      GraphApplet.gField.setText("Please click the Init button first to initialize.");
  }
  
  public void writeIt()
  {
    graph.writeInfo(initd);
    //before Init button clicked aka steinicized = true,
    //we haven't initialized faces,
    //so we shouldn't output info about them
  }

  public void getCoord()
  {
    if(steinicized) //make sure init done
      graph.writeCoord();
    else if(initd) {
      stepInf();
      getCoord();
    }
    else { //no initd
      init();
      getCoord();
    }
  }

  // ComponentListener
  public void componentResized(ComponentEvent e) { img = null; }
  public void componentMoved(ComponentEvent e) {}
  public void componentShown(ComponentEvent e){}
  public void componentHidden(ComponentEvent e) {}

}
