
//////////////////////////////////////////////////
package gui;

// Standard packages.
import java.awt.*;
import java.util.Enumeration;

// Packages specific to Network Design Tool.
import general.GeneralProperties;
import network.Switch;
import network.Link;
import network.Network;
//////////////////////////////////////////////////
public class GUICanvas extends Canvas {
  private GeneralProperties gpData;   // Reference to the gpData of
  // GUIManager. 
  private double prevScale;
  private Dimension prevSize;
  private int dx, dy;
  private double scale;
  public  String editMode = GUICanvas.MOVE_SWITCH;
  private Switch switchPicked = null;
  private Link   linkPicked = null;
  private Switch leftSwitchPicked = null;
  private Switch rightSwitchPicked = null;


  Image offscreen;
  Dimension offscreensize;
  Graphics offgraphics;

  // minimum distance for a mouse to activate a switch.
  final int MIN_DIST = 10;

  final static String ADD_SWITCH = "add switch";
  final static String MOVE_SWITCH = "move switch";
  final static String REMOVE_SWITCH = "remove switch";
  final static String EDIT_SWITCH = "edit switch";
  final static String ADD_LINK = "add link";
  final static String REMOVE_LINK = "remove link";
  final static String EDIT_LINK = "edit link";
  final static String ZOOM_IN = "Zoom In";
  final static String ZOOM_OUT = "Zoom Out";

  final static Color SELECTED_SWITCH_COLOR = Color.pink;
  final static Color LINK_COLOR = Color.black;
  final static Color SELECTED_LINK_COLOR = Color.red;
  final static Color SWITCH_COLOR = new Color(250, 220, 100);

  final static double ZOOM_FACTOR = 2.0;

  //---------------------------------------------------
  public GUICanvas(GeneralProperties gp) {
    gpData = gp;
    scale = 1.0;
  }

  //---------------------------------------------------
  public void setScale(double s) {
    if (s <= 0) return;
    scale = s;
  }

  /**
   * set the edit mode
   */
  public void setEditMode(String mode) {
    editMode = mode;

    // clear all pointers
    switchPicked = null;
    linkPicked = null;
    leftSwitchPicked = null;
    rightSwitchPicked = null;
    repaint();
  }

  //----------------------------------------------------
  public double getScale() {
    return scale;
  }

  public double getRealX(int x) {
    return (x+dx)/scale;
  }

  public double getRealY(int y) {
    return (y+dy)/scale;
  }

  //----------------------------------------------------
  public void paint(Graphics g) {
    g.translate(-dx, -dy);
    g.setColor(getBackground());
    Dimension d = size();
    g.fillRect(dx, dy, d.width - 1, d.height - 1);
    g.setColor(getForeground());
    g.drawRect(dx, dy, d.width - 1, d.height - 1);
    paintNetwork(g);
  }

  /**
   * change the scale so the whole network can fit into the canvas
   */
  public void autoScale() {
    Dimension minD = minimumSize();
    Dimension curD = size();
    double ratioX = (double)curD.width/(double)minD.width;
    double ratioY = (double)curD.height/(double)minD.height;
    setScale( (ratioX < ratioY)? ratioX: ratioY );
  }

  /**
   * minimum size is based upon unscaled network dimension
   */
  public Dimension minimumSize() {
    int w, h;
    Rectangle r = ((Network)gpData.get("Network")).getBounds();
    Dimension dDef = (Dimension)gpData.get("Default Dimensions of Canvas");
    w = r.width>dDef.width ? r.width : dDef.width;
    h = r.height>dDef.height ? r.height : dDef.height;
    w = (int)(w*scale);
    h = (int)(h*scale);
    return new Dimension(w, h);
  }

  //------------------------------------------------
  public Dimension preferredSize() {
    return minimumSize();
  }

  //------------------------------------------------
  public boolean mouseMove(Event e, int x, int y) {
    Label l = (Label)gpData.get("Status Bar Message");
    int u, v;
    u = (int)getRealX(x);
    v = (int)getRealY(y);
    l.setText("(x=" + u + ",y=" + v + ")");
    return true;
  }

  //--------------------------------------------------
  public synchronized boolean mouseDown(Event evt, int x, int y) {
    gpData.put("Coordinates of Last Mouse Click", new Point(x+dx, y+dy));
    double bestdist = Double.MAX_VALUE;
    double realX = getRealX(x);
    double realY = getRealY(y);
    Network n = (Network)gpData.get("Network");
    Switch s;

    if (editMode.equals(ADD_SWITCH)) {
      String name = "S" + n.switches.size();
      n.addSwitch( new Switch(name, realX, realY) );
      repaint();
      return true;
    }

    if (editMode.equals(MOVE_SWITCH) ||
	editMode.equals(REMOVE_SWITCH) ||
	editMode.equals(EDIT_SWITCH) ||
	editMode.equals(ADD_LINK) ) {
      for (Enumeration e = n.switches.elements(); e.hasMoreElements();) {
	s = (Switch)e.nextElement();
	double dist = (s.getX() - realX) * (s.getX() - realX)
	  + (s.getY() - realY) * (s.getY() - realY);
	if (dist < bestdist) {
	  switchPicked = s;
	  bestdist = dist;
	}
      }
      if (bestdist*scale*scale > MIN_DIST*MIN_DIST) {
	// the mouse is too far away from any switch
	switchPicked = null;
	return true;
      }
      // else ...
      if (editMode.equals(MOVE_SWITCH)) {
	switchPicked.setLocation(realX, realY);
	repaint();
	return true;
      }
      if (editMode.equals(REMOVE_SWITCH)) {
	repaint();
	return true;
      }
      if (editMode.equals(EDIT_SWITCH)) {
	repaint();
	return true;
      }
      if (editMode.equals(ADD_LINK)) {
	if (leftSwitchPicked == null) {
	  leftSwitchPicked = switchPicked;
	  repaint();
	  return true;
	}
	// else ...
	rightSwitchPicked = switchPicked;
	n.addLink(new Link("Link_From_" + leftSwitchPicked.getName() + "_to_"
+ rightSwitchPicked.getName(),
leftSwitchPicked, rightSwitchPicked));
	leftSwitchPicked = null;
	rightSwitchPicked = null;
	switchPicked = null;
	repaint();
	return true;
      }
      return true;
    }
    if (editMode.equals(REMOVE_LINK) ||
	editMode.equals(EDIT_LINK)) {
      Link link;
      double dist;
      for (Enumeration e = n.links.elements(); e.hasMoreElements();) {
	link = (Link)e.nextElement();
	if ((dist = distance(realX, realY, link)) < bestdist) {
	  bestdist = dist;
	  linkPicked = link;
	}
      }
      if (bestdist*scale > MIN_DIST) {
	linkPicked = null;
	return true;
      }
      repaint();
      return true;
    }
    return true;
  }


  public synchronized boolean mouseDrag(Event evt, int x, int y) {
    Label l = (Label)gpData.get("Status Bar Message");

    int u, v;						
    u = (int)((x + dx)/scale);				
    v = (int)((y + dy)/scale);	
    l.setText("(x=" + u + ",y=" + v + ")");		
			
    if (editMode.equals(MOVE_SWITCH)) {
      if (switchPicked == null) return true;    
      switchPicked.setLocation(u, v);
      repaint();
      return true;
    }
    else if (editMode.equals(REMOVE_SWITCH) ||
	     editMode.equals(EDIT_SWITCH)) {
      if (switchPicked == null) return true;
      double dist = (u-switchPicked.getX())*(u-switchPicked.getX())
	+ (v-switchPicked.getY())*(v-switchPicked.getY());
      // the mouse is out of the region, deselect the switch
      if (dist*scale*scale > MIN_DIST*MIN_DIST) {
	switchPicked = null;
	repaint();
      }
      return true;
    }
    else if (editMode.equals(REMOVE_LINK) ||
	     editMode.equals(EDIT_LINK)) {
      if (linkPicked == null) return true;
      double dist;
      if ( (dist = distance(u, v, linkPicked)*scale) > MIN_DIST) {
	linkPicked = null;
	repaint();
      }
      //System.out.println("distance = " + dist + " to link  " + linkPicked);
      return true;
    }
    return true;
  }

  public synchronized boolean mouseUp(Event evt, int x, int y) {
    Network n = (Network)gpData.get("Network");

    if (editMode.equals(ZOOM_OUT)) {
      zoomOut(x, y, ZOOM_FACTOR);
    }
    else if (editMode.equals(ZOOM_IN)) {
      zoomIn(x, y, ZOOM_FACTOR);
    }
    else if (editMode.equals(MOVE_SWITCH)) {
      if (switchPicked == null) return true;
      switchPicked.setLocation((x+dx)/scale, (y+dy)/scale);
      switchPicked = null;
      repaint();
      return true;
    }
    else if (editMode.equals(REMOVE_SWITCH)) {
      if (switchPicked == null) return true;
      n.removeSwitch(switchPicked);
      switchPicked = null;
      repaint();
    }
    else if (editMode.equals(EDIT_SWITCH)) {
      if (switchPicked == null) return true;
      //n.setShortestPath(switchPicked);
      new dEditSwitch( (Frame)gpData.get("GUIManager"), switchPicked, gpData );
      switchPicked = null;
      repaint();
    }
    else if (editMode.equals(REMOVE_LINK)) {
      if (linkPicked == null) return true;
      n.removeLink(n.links, linkPicked);
      linkPicked = null;
      repaint();
    }
    else if (editMode.equals(EDIT_LINK)) {
      if (linkPicked == null) return true;
      new dEditLink( (Frame)gpData.get("GUIManager"), linkPicked, gpData );
      linkPicked = null;
      repaint();
    }
    return true;
  }

  /**
   * draw all the links of the network. 
   * first, get the min and max of the capacities of links,
   * then, draw links with thickness corresponding to their capacities.
   */
  private void drawLinks(Graphics g) {
    Network n = (Network)gpData.get("Network");
    Link link;
    double minCap = Double.MAX_VALUE, maxCap = Double.MIN_VALUE;
    double cap;
    int thickness;
    int i;
    for (Enumeration e = n.links.elements(); e.hasMoreElements();) {
      link = (Link)e.nextElement();
      cap = link.getL2R() + link.getR2L();
      if (cap > maxCap)
	maxCap = cap;
      if (cap < minCap)
	minCap = cap;
    }
      
    for (Enumeration e = n.links.elements(); e.hasMoreElements();) {
      link = (Link)e.nextElement();
      cap = link.getL2R() + link.getR2L();
      if (minCap == maxCap)
	thickness = 1;
      else
	thickness = (int)(4*(cap-minCap)/(maxCap-minCap)) + 1;
      if (link == linkPicked)
	g.setColor(SELECTED_LINK_COLOR);
      else
	g.setColor(LINK_COLOR);
      int x1 = (int)( ((Switch)(link.getL())).getX()*scale );
      int y1 = (int)( ((Switch)(link.getL())).getY()*scale );
      int x2 = (int)( ((Switch)(link.getR())).getX()*scale );
      int y2 = (int)( ((Switch)(link.getR())).getY()*scale );
      drawLine(g, x1, y1, x2, y2, thickness);
    }
  }

  private void drawLine(Graphics g, int x1, int y1, int x2, int y2, int thickness) {
    double length = Math.sqrt( (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1) );
    double dx = (y2-y1)/length;
    double dy = (x1-x2)/length;
    for (int i = 0; i < thickness; i++) {
      g.drawLine((int)(x1 + dx*i), (int)(y1 + dy*i),
		 (int)(x2 + dx*i), (int)(y2 + dy*i));
    }
  }

  public void paintSwitch(Graphics g, Switch s, FontMetrics fm) {
    int x = (int)(s.getX()*scale);
    int y = (int)(s.getY()*scale);
    if ( (s==switchPicked) || (s == leftSwitchPicked) )
      g.setColor(SELECTED_SWITCH_COLOR);
    else
      g.setColor(SWITCH_COLOR);
    int w = fm.stringWidth(s.getName()) + 10;
    int h = fm.getHeight() + 4;
    g.fillRect(x - w/2, y - h / 2, w, h);
    g.setColor(Color.black);
    g.drawRect(x - w/2, y - h / 2, w-1, h-1);
    g.drawString(s.getName(), x - (w-10)/2, (y - (h-4)/2) + fm.getAscent());
  }


  //----------------------------------------------------
  private void paintNetwork(Graphics g) {
    Dimension d = size();
    int w = d.width;
    int h = d.height;

    Network n = (Network)gpData.get("Network");
    String CanvasTitle = "Network : " + n.getName();
    g.drawString(CanvasTitle, 
		 (w - g.getFontMetrics().stringWidth(CanvasTitle))/2 + dx,
		 10 + dy);   

    g.setFont(new Font("Helvetica", Font.PLAIN, 8));

    // draw all the links and switches
    drawLinks(g);
    FontMetrics fm = g.getFontMetrics();
    Switch s;
    for (Enumeration e = n.switches.elements(); e.hasMoreElements();) {
      s = (Switch)e.nextElement();
      paintSwitch(g, s, fm);
    }
  }

  //---------------------------------------------------
  // change the origin of the canvas.
  public void translate(int x, int y) {
    dx = x;
    dy = y;
    repaint();
  }

  public void zoomIn(int x, int y, double factor) {
    Network n = (Network)gpData.get("Network");
    Rectangle r = n.getBounds();
    Dimension d = size();
    dx = (int)((x+dx)/factor - d.width/2);
    dy = (int)((y+dy)/factor - d.height/2);
    scale /= factor;
    System.out.println("dx = " + dx + ",dy = " + dy + ", scale = " + scale);
    System.out.println(r);
    if (dx < r.x*scale) {
      System.out.println("dx < r.x*scale");
      dx = (int)(r.x*scale);
    }
    else if ( (dx + d.width) > (r.x + r.width)*scale ) {
      System.out.println("dx + d.width > (r.x + r.width)*scale");
      dx = (int)( (r.x + r.width)*scale - d.width );
    }
    if (dy < r.y*scale) {
      System.out.println("dy < r.y*scale");
      dy = (int)(r.y*scale);
    }
    else if ( (dy + d.height) > (r.y + r.height)*scale ) {
      System.out.println("dy + d.height > (r.y + r.height)*scale");
      dy = (int)( (r.y + r.height)*scale - d.height );
    }
    translate(dx, dy);
    GUIManager g = (GUIManager)gpData.get("GUIManager");
    g.setScrollbar();
  }

  public void zoomOut(int x, int y, double factor) {
    zoomIn(x, y, 1/factor);
  }

  public void clear() {
    scale = 1.0;
    dx = 0;
    dy = 0;
  }

  public int getDx() {
    return dx;
  }

  public int getDy() {
    return dy;
  }

  public void setPrevSize(Dimension d) {
    prevSize = d;
  }

  public Dimension getPrevSize() {
    return prevSize;
  }

  public void setPrevScale(double s) {
    prevScale = s;
  }

  public double getPrevScale() {
    return prevScale;
  }

  /**
   * calculate the distance from a given point (x,y) to the line segment
   * of the link.
   */
  public double distance(double x, double y, Link link) {
    double lx, ly, rx, ry;
    double lpSquare, rpSquare, lrSquare;

    lx = link.getL().getX();
    ly = link.getL().getY();
    rx = link.getR().getX();
    ry = link.getR().getY();
    lpSquare = ( x-lx)*( x-lx) + ( y-ly)*( y-ly);
    rpSquare = ( x-rx)*( x-rx) + ( y-ry)*( y-ry);
    lrSquare = (lx-rx)*(lx-rx) + (ly-ry)*(ly-ry);
  
    if (lpSquare + lrSquare < rpSquare)
      return Math.sqrt(lpSquare);
    if (rpSquare + lrSquare < lpSquare)
      return Math.sqrt(rpSquare);
    return Math.abs((x-lx)*(ry-ly)-(rx-lx)*(y-ly))/Math.sqrt(lrSquare);
  }
} // End of class GUICanvas.
//////////////////////////////////////////////////
