/* Originally Created By:
 * Sean Dougherty
 *
 * Modified for Circles, Rectangles, Single Shape Queue (crosses have priority) by:
 * Matt Lew
 *
 * Support for dynamic bounds, single frame controlled
 * animation and double buffering with a second shapequeue added by:
 * Brian Krznarich 
 */

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import java.util.*;

public class CanvasPlus extends JComponent 
  {
  private String title;
  private Color currentColor;
  private boolean done = false;
  private java.util.List shapeQueue;
  private java.util.List shapeQueueDraw;
  private int xSize, ySize;
  float xMax,yMax;

  long lastFrameTime;
  int msPerFrame;
  
  JFrame myFrame=null;

  // Default constructor
  CanvasPlus() 
    {
    this("untitled");
    }

  // Create a new canvas with specified title
  CanvasPlus(String title) 
    {
    this(title, 512,512,1024,1024);
    }

  //xSize/ySize are the initial size of the window in pixels(i think :)
  //xMax and yMax are the upper bounds of the points being drawn 
  CanvasPlus(String title,int xSize, int ySize, float xMax, float yMax) 
    {
    this.title = title;
    currentColor = Color.black;
//    shapeQueue = new Vector(10,5);
    shapeQueue= new LinkedList(); //new ArrayList(10000); //new LinkedList();
    shapeQueueDraw = new LinkedList(); //new ArrayList(10000); //new LinkedList();
//    shapeQueue1= new ArrayList(5000);
//    shapeQueue2 = new ArrayList(5000);
//    shapeQueue = shapeQueue1;
    lastFrameTime =0;
    msPerFrame = 10;
    this.xSize=xSize;
    this.ySize=ySize;
    this.xMax=xMax;
    this.yMax=yMax;
    }

  int setMinMsPerFrame(int ms)
    {
    int oldMs = msPerFrame;
    msPerFrame = ms;
    return(msPerFrame);
    }

  // Clears the canvas
  public void clear() 
    {
    shapeQueue.clear();
    }

  // Change the pen color
  public void setColor(Color newColor) 
    {
    currentColor = newColor;
    }

  public void setSize(int xSize, int ySize)
    {
    this.xSize=xSize;
    this.ySize=ySize;
    }

  public void setBounds(int xMax, int yMax)
    {
    this.xMax=xMax;
    this.yMax=yMax;
    }

 
  public  void draw() throws Exception 
    {
    //cap the max frames per second
    while(System.currentTimeMillis() - lastFrameTime < msPerFrame);
    lastFrameTime=System.currentTimeMillis();
    
//    done=false;
    if(myFrame==null)
      {
      try
        {
        myFrame = new JFrame(title);
        myFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); //kills jframe thread(s) on window close
 
     // Continue after window is closed
//      f.addWindowListener( 
//          new WindowAdapter() 
//            {
//            public void windowClosing(WindowEvent e) {done = true;} 
//            }
//          );
      myFrame.addWindowListener( 
          new WindowAdapter() 
            {
            public void windowClosing(WindowEvent e) {System.exit(0);} 
            }
          );

        myFrame.setSize(xSize,ySize);
        myFrame.getContentPane().add(this);
        myFrame.setVisible(true);

      // Pause program execution while window open
//      try 
//        {
//        while (done == false) 
//          Thread.sleep(1000);
//        }
//      catch (InterruptedException e) {done = true;};
//      }
    // Error opening windows
        }
      catch(Error e) 
        {
        throw new Exception("Can't connect to display.");
        }
      }
    synchronized(shapeQueueDraw) //can't swap buffers while drawing
      {
      java.util.List tempList;
      tempList = shapeQueueDraw;
      shapeQueueDraw=shapeQueue;
      shapeQueue= tempList;
      }
    myFrame.repaint();
    }

  // Add a new line to the canvas
  public void addLine(float x1, float y1, float x2, float y2) 
    {

    // Adjust the coordinate system(y increases going up the monitor instead of down)
    y1 = yMax-y1;
    y2 = yMax-y2;

    // Add the line
    shapeQueue.add(new Line(x1,y1,x2,y2));
    }

  // Add a cross to the canvas
  public void addCross(float x, float y, float radius) 
    {
    shapeQueue.add(0,new Line(x-radius, (yMax - y), x+radius, (yMax - y)));
    shapeQueue.add(0,new Line(x, (yMax - y - radius), x, (yMax - y + radius)));
    }

  //add a rectangle centered at x_c,y_c with width 2*x_h and height 2*y_h
  public void addRectangle(float x_c, float y_c, float x_h, float y_h) 
    {
    // Add the lines that create the rectangle
    addLine((x_c - x_h),(y_c + y_h),(x_c + x_h),(y_c + y_h));
    addLine((x_c - x_h),(y_c + y_h),(x_c - x_h),(y_c - y_h));
    addLine((x_c - x_h),(y_c - y_h),(x_c + x_h),(y_c - y_h));
    addLine((x_c + x_h),(y_c + y_h),(x_c + x_h),(y_c - y_h));
    }

  // Add a labeled point to the canvas
  public void addPoint(String caption, float x, float y)  
    {
    // Adjust the coordinate system
    y = yMax-y;

    // Add the point
    shapeQueue.add(new Point(caption,x,y));
    }  

  // Add a circle to the canvas

  public void addCircle(float x, float y, float r, boolean filled, Color c)
    {
    float x1, y1;

    x1 = x - r;
    y1 = (yMax - y) - r;

    shapeQueue.add(new Circle(x1,y1,r,filled,c));
    }
  public void addCircle(float x, float y, float r, boolean filled)
    {
    float x1, y1;

    x1 = x - r;
    y1 = (yMax - y) - r;

    // Add the circle
    shapeQueue.add(new Circle(x1,y1,r,filled));
    }

  public void addCircle(float x, float y, float r)
    {
    addCircle(x,y,r,false, currentColor);
    }

  //this is set as a child of the jframe created in draw, which causes the paint()
  //method to be called
  public void paint(Graphics g) 
    {
    Graphics2D g2 = (Graphics2D)g;
  
    float adjustment = 0.9f; //make everything slightly smaller than the window so that borders are visible
    float offset = getSize().width * (1 - adjustment)/2f;
    int width = getSize().width;
    int height = getSize().height;
    float xscale = (width * adjustment)/xMax;
    float yscale = (height * adjustment)/yMax;
    boolean fillShape;

    synchronized(shapeQueueDraw) 
      {
      for(Iterator i = shapeQueueDraw.iterator(); i.hasNext(); )
        {
        fillShape = false;

        Shape s;
        Object obj = i.next();
  //      String shapeClass = obj.getClass().getName();

  //      if(shapeClass.equals("CanvasPlus$Line"))
        if(obj instanceof CanvasPlus.Line)
          {
          Line l = (Line)obj;

          float x1 = l.x1 * xscale + offset;
          float y1 = l.y1 * yscale + offset;
          float x2 = l.x2 * xscale + offset;
          float y2 = l.y2 * yscale + offset;
      
          g2.setPaint(l.color);
          s = new Line2D.Float(x1,y1,x2,y2);
          }
        else if(obj instanceof CanvasPlus.Circle)
  //      else if(shapeClass.equals("CanvasPlus$Circle"))
          {
          Circle c = (Circle)obj;
          float x = c.x * xscale + offset;
          float y = c.y * yscale + offset;
          float w = c.r * xscale * 2;
          float h = c.r * yscale * 2;
        
          if(c.filled)
            fillShape=true;
          g2.setPaint(c.color);
          s = new Ellipse2D.Float(x,y,w,h);
          }
  //      else if(shapeClass.equals("CanvasPlus$Point"))
        else if(obj instanceof CanvasPlus.Point)
          {
          Point p = (Point)obj;
          float x = p.x * xscale + offset;
          float y = p.y * yscale + offset;

          fillShape = true;

          g2.setPaint(Color.black);
          s = new Ellipse2D.Float(x-2,y-2,4,4);
         
          if(p.caption.length()>0)
            {
            g2.setFont(new Font("Dialog", Font.PLAIN, 10));
            g2.drawString(p.caption,x+7,y-5); //slightly to the right and above
            }
          }
        else
          {
          // you shouldn't ever get this, but without it you get a compile error
          s = new Ellipse2D.Float(0,0,0,0);
          }

        g2.draw(s);
        if(fillShape)
          {
          g2.fill(s);
          fillShape=false;
          }
        }
      }
    }

  // Represents a single line
  private class Line 
    {
    float x1, y1, x2, y2;
    Color color;
    
    Line(float x1, float y1, float x2, float y2) 
      {
      this.color = currentColor;
      this.x1 = x1;
      this.x2 = x2;
      this.y1 = y1;
      this.y2 = y2;
      }
    }

  // Represents a single point
  private class Point 
    {
    float x, y;
    String caption;
    
    Point(String caption, float x, float y) 
      {
      this.caption = caption;
      this.x = x;
      this.y = y;
      }
    }

  // Represents a circle
  private class Circle 
    {
    float x, y, r;
    Color color;
    boolean filled;
    Circle(float x, float y, float r, boolean filled) 
      {
      this.x = x;
      this.y = y;
      this.r = r;
      this.color = currentColor;
      this.filled=filled;
      }
    Circle(float x, float y, float r, boolean filled, Color c) 
      {
      this.x = x;
      this.y = y;
      this.r = r;
      this.color = c;
      this.filled=filled;
      }
    }
  }