import java.awt.*;
import java.util.Vector;

/**
 * The DrawPanel object is Kali's actual drawing area.  It receives
 * and handles the mouse events related to drawing, maintains the list
 * of line segments that have been drawn (and their colors), keeps
 * track of whether we're drawing a temporary segment (the segment
 * that follows the mouse during drawing), and if so, its coordinates.
 * <p>
 * The screen is drawn in Kali whenever the system calls the
 * DrawPanel's paint() method; this method goes through the list of
 * current segments, and calls the Panorama's drawSegement() method
 * for each one.
 */
class DrawPanel extends Panel {
  /**
   * The lists of segments and their colors
   */
  Vector segments = new Vector();
  Vector colors = new Vector();

  /**
   * The endpoints of the temporary line segment, in internal
   * coordinates.
   */
  DVector v1, v2;

  /**
   * Are we drawing a temporary segment?
   */
  boolean drawing = false;

  /**
   * Max amount of time, in milliseconds, between the clicks
   * of a double-click.  (Two clicks within this amount of time
   * are considered to be a double-click.)  A double-click
   * ends the current polyline.
   */
  long doubleClickTime = 400;

  /**
   * Are we doing double-buffering?  (In general we should,
   * for flicker-free drawing, but this flag is here so we
   * can turn it off during testing/debugging if we want).
   */
  boolean doublebuffered = true;

  /**
   * ids of the KaliCanvas and Panorama objects that this
   * DrawPanel relates to.
   */
  KaliCanvas kaliCanvas;
  Panorama panorama;


  /**
   * lastMouseTime records the time at which the last MOUSE_DOWN
   * event happened; it's used in detecting double clicks.
   */
  long lastMouseTime = 0;

  /**
   * lastMouseX and lastMouseY record the (raw) screen coordinates
   * at which the last MOUSE_DOWN occurred.
   */
  int lastMouseX = -1000;
  int lastMouseY = -1000;

  /**
   * closeMouseDistanceSquared is the square of the distance, in
   * pixels, which is considered "close" for mouse clicks.
   */
  int closeMouseDistanceSquared = 36;

  /**
   * Pointer to the offscreen image used for double-buffering.
   */
  static Image offscreen;
  
  /**
   * Create a new DrawPanel object.
   * @param panorama	The Panorama object that this DrawPanel
   *			relates to.
   * @param kaliCanvas	The KaliCanvas object that this DrawPanel
   *			relates to.
   */
  public DrawPanel(Panorama panorama, KaliCanvas kaliCanvas) {
    setBackground(Color.white);
    setForeground(Color.black);
    this.panorama = panorama;
    this.kaliCanvas = kaliCanvas;
  }

  private double screenDistanceSquared(int x1, int y1, int x2, int y2) {
    int dx = x1 - x2;
    int dy = y1 - y2;
    return dx*dx + dy*dy;
  }

  /**
   * Erase the screen and clear the list of segments
   */
  public void clear() {
    segments.removeAllElements();
    colors.removeAllElements();
    drawing = false;
    repaint();
  }

  /**
   * Handle mouse events
   */
  public boolean handleEvent(Event e) {
    boolean doubleClicked;
    boolean mouseClose;

    switch (e.id) {
    case Event.MOUSE_DOWN:
      doubleClicked = (e.when - lastMouseTime < doubleClickTime);
      mouseClose = (screenDistanceSquared(e.x,e.y, lastMouseX, lastMouseY)
		    <= closeMouseDistanceSquared);
      lastMouseX = e.x;
      lastMouseY = e.y;
      lastMouseTime = e.when;
      if (drawing) {
	// we drop the line (quit drawing) if either the MOUSE_DOWN event
	// happened within doubleClickTime of the previous one, or if
	// it's considered "close" to the previous one.
	if (doubleClicked || mouseClose) {
	  drawing = false;
	  repaint();
	} else {
	  colors.addElement(getForeground());
	  segments.addElement(new Segment(v1.copy(), kaliCanvas.rawScreenToInternal(e.x, e.y)));
	  repaint();
	  v1 = kaliCanvas.rawScreenToInternal(e.x,e.y);
	}
      } else {
	// Don't start a line with a double-click
	if (!doubleClicked) {
	  v1 = kaliCanvas.rawScreenToInternal(e.x,e.y);
	  drawing = true;
	}
      }
      return true;
      
    case Event.MOUSE_DRAG:
    case Event.MOUSE_MOVE:
      if (drawing) {
	v2 = kaliCanvas.rawScreenToInternal(e.x,e.y);
	repaint();
      }
      return true;
      
    case Event.WINDOW_DESTROY:
      System.exit(0);
      return true;
      
    default:
      return false;
      
    }
  }
  
/**  
 * Paint the panel, by looping through all the segements
 * and calling the panorama object's drawSegment() method
 * for each one.
 */
  public void paint(Graphics g) {
    int np = segments.size();
    if ( (np > 0) || drawing ) {
      Color foreground = getForeground();
      kaliCanvas.setGraphics(g, size().width, size().height);
      panorama.prepareToDraw();
      for (int i=0; i < np; i++) {
	Segment s = (Segment)segments.elementAt(i);
	Color c = (Color)colors.elementAt(i);
	panorama.drawSegment(s, c);
      }
      if (drawing) {
	Segment s = new Segment(v1, v2);
	panorama.drawSegment(s, foreground);
      }
    }
  }

  /**
   * Update the screen, with doublebuffering.
   */
  public void update(Graphics screeng){

  // This update method should be pluggable into anything that's a Component
  // (e.g. a subclass of Panel.)
  // We assume two instance variables declared above:
  //	 Image offscreen;		// Scratch space
  //	 boolean doublebuffered;	// If true, do double-buffering
  // With luck this may be all you need.
  // [Given to mbp by asr on Tue Aug 20 15:52:17 1996; originally by slevy?]

    Rectangle area = screeng.getClipRect();
    Graphics g;
    
    if (doublebuffered) {
      /* If we're double-buffering, we need an (off-screen) image to
       * draw into.  Keep one lying around in "offscreen".
       * If that hasn't yet been initialized, or if our size has
       * changed since then, then create a new one.
       */
      if(offscreen == null || 
	 offscreen.getWidth(null) != area.width || 
	 offscreen.getHeight(null) != area.height) {
	offscreen = createImage(area.width, area.height);
      }
      /* We'll render into the off-screen image's graphics area.
       * Start by clearing it to the background color.
       */
      g = offscreen.getGraphics();
      g.clipRect(area.x, area.y, area.width, area.height);
      
    } 
    else {
      /* Otherwise, we're single-buffered; render directly to the screen.  */
      g = screeng;
    }
    
    g.setColor(getBackground());
    g.clearRect(0, 0, area.width, area.height);
    g.setColor(getForeground());
    paint(g);
    
    /*
     * If we were double-buffering, finish by copying the image we just
     * drew into the relevant piece of the screen.
     */
    if (doublebuffered) {
      screeng.drawImage(offscreen,
			area.x, area.y, area.width, area.height,
			this);
    }
  }
}
