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

/**
 * Class Canvas - a class to allow for simple graphical
 * drawing on a canvas.
 *
 * ---> This is the primitives-free version of Canvas! <---
 *
 * @author Bruce Quig
 * @author Michael Kolling (mik)
 * @author Dave Musicant - made some local changes
 * @author Amy Csizmar Dalal - more minor local changes
 * @author Jeff Ondich - added setFontSize and getInkColor
 * @author David Liben-Nowell - renamed fillX() and height/width parameters
 *
 * 1.7.0:  primitive-free version.
 * 1.7.1:  renamed wait()->pause().
 * 1.7.2:  added eraseFilledX(), renamed internal variables.
 *
 * @version 1.7.2
 * date:    05.04.2006
 */

public class Canvas
{
    private JFrame frame;
    protected CanvasPane canvas;
    private Graphics2D graphic;
    private Color backgroundColor;
    private Color inkColor;
    private Image canvasImage;


    /**
     * Create a Canvas with default height, width and background color
     * (300, 300, white).
     * @param title  title to appear in Canvas Frame
     */
    public Canvas(String title)
    {
        this(title, 300, 300, Color.white);
    }

    /**
     * Create a Canvas with default title, height, width and background color
     * ("Canvas", 300, 300, white).
     */
    public Canvas()
    {
        this("Canvas", 300, 300, Color.white);
    }

    /**
     * Create a Canvas with default background Color (white).
     * @param title  title to appear in Canvas Frame
     * @param width  the desired width for the canvas
     * @param height  the desired height for the canvas
     */
    private Canvas(String title, Integer width, Integer height)
    {
        this(title, width, height, Color.white);
    }

    /**
     * Create a Canvas.
     * @param title  title to appear in Canvas Frame
     * @param width  the desired width for the canvas
     * @param height  the desired height for the canvas
     * @param bgColor  the desired background color of the canvas
     */
    private Canvas(String title, Integer width, Integer height, Color bgColor)
    {
        frame = new JFrame();
        canvas = new CanvasPane();
        frame.setContentPane(canvas);
        frame.setTitle(title);
        canvas.setPreferredSize(new Dimension(width, height));
        backgroundColor = bgColor;
        inkColor = Color.black;
        frame.pack();

        // Added by acd:  this is a hack that will allow other components,
        // like buttons, to be added to the canvas and be visible.
        // basically, it's saying "when you repaint, don't fill the entire
        // canvas with the background color", i.e. leave the buttons and
        // stuff alone!
        canvas.setOpaque(false);
        // end of hack

        frame.addWindowListener(new WindowAdapter() {
                public void windowClosing(WindowEvent e) {
                    setVisible(false);
                    System.exit(0);
                }
            });

        if(graphic == null) {
            // first time: instantiate the offscreen image and fill it with
            // the background color
            Dimension size = canvas.getSize();
            canvasImage = canvas.createImage(size.width, size.height);
            graphic = (Graphics2D)canvasImage.getGraphics();
            graphic.setColor(backgroundColor);
            graphic.fillRect(0, 0, size.width, size.height);
            graphic.setColor(inkColor);
        }
    }

    /**
     * Sets the "pen" (outline) color for the Canvas.
     * @param newColor  The color to which to set the pen/drawing tool.
     */
    public void setInkColor(Color newColor)
    {
        inkColor = newColor;
        graphic.setColor(inkColor);
    }

    /**
     * Returns the current pen color.
     */
    public Color getInkColor()
    {
        return inkColor;
    }

    /**
     * Set the canvas visibility and brings canvas to the front of screen
     * when made visible. This method can also be used to bring an already
     * visible canvas to the front of other windows.
     * @param visible  Boolean value representing the desired visibility of
     * the canvas (true or false)
     */
    public void setVisible(Boolean visible)
    {
        /*if (visible)
            frame.show();
        else
        frame.hide();*/
        /* EDITED BY ACD:  show() and hide() are deprecated in
           Java 1.5; replace with setVisible(Boolean) */
        frame.setVisible(visible);
    }

    /**
     * Provide information on visibility of the Canvas.
     * @return  true if canvas is visible, false otherwise
     */
    public Boolean isVisible()
    {
        return frame.isVisible();
    }

    /**
     * Draw a given shape onto the canvas.
     * @param  shape  the shape object to be drawn on the canvas
     */
    private void draw(Shape shape)
    {
        graphic.draw(shape);
        canvas.repaint();
    }

    /**
     * Fill the internal dimensions of a given shape with the current
     * foreground color of the canvas.
     * @param  shape  the shape object to be filled
     */
    private void fill(Shape shape)
    {
        graphic.fill(shape);
        canvas.repaint();
    }

    /**
     * Erase the whole canvas.
     */
    public void erase()
    {
        Color original = graphic.getColor();
        graphic.setColor(backgroundColor);
        Dimension size = canvas.getSize();
        graphic.fill(new Rectangle(0, 0, size.width, size.height));
        graphic.setColor(original);
        canvas.repaint();
    }

    /**
     * Erase a given shape's interior on the screen.
     * @param  shape  the shape object to be erased
     */
    private void erase(Shape shape)
    {
        Color original = graphic.getColor();
        graphic.setColor(backgroundColor);
        graphic.fill(shape);              // erase by filling background color
        graphic.setColor(original);
        canvas.repaint();
    }

    /**
     * Erases a given shape's outline on the screen.
     * @param  shape  the shape object to be erased
     */
    private void eraseOutline(Shape shape)
    {
        Color original = graphic.getColor();
        graphic.setColor(backgroundColor);
        graphic.draw(shape);  // erase by drawing background color
        graphic.setColor(original);
        canvas.repaint();
    }

    /**
     * Draws an image onto the canvas.
     * @param  image   the Image object to be displayed
     * @param  x       x coordinate for Image placement
     * @param  y       y coordinate for Image placement
     * @return  returns Boolean value representing whether the image was
     *          completely loaded
     */
    private Boolean drawImage(Image image, Integer x, Integer y)
    {
        Boolean result = graphic.drawImage(image, x, y, null);
        canvas.repaint();
        return result;
    }

    /**
     * Draws a String on the Canvas.
     * @param  text   the String to be displayed
     * @param  x      x coordinate for text placement
     * @param  y      y coordinate for text placement
     */
    public void drawString(String text, Integer x, Integer y)
    {
        graphic.drawString(text, x, y);
        canvas.repaint();
    }

    /**
     * Erases a String on the Canvas.
     * @param  text     the String to be displayed
     * @param  x        x coordinate for text placement
     * @param  y        y coordinate for text placement
     */
    public void eraseString(String text, Integer x, Integer y)
    {
        Color original = graphic.getColor();
        graphic.setColor(backgroundColor);
        graphic.drawString(text, x, y);
        graphic.setColor(original);
        canvas.repaint();
    }

    /**
     * Draws a line on the Canvas.
     * @param  x1   x coordinate of start of line
     * @param  y1   y coordinate of start of line
     * @param  x2   x coordinate of end of line
     * @param  y2   y coordinate of end of line
     */
    public void drawLine(Integer x1, Integer y1, Integer x2, Integer y2)
    {
        graphic.drawLine(x1, y1, x2, y2);
        canvas.repaint();
    }

    /**
     * Draws a rectangle on the Canvas.
     * @param  x        x coordinate of top left corner
     * @param  y        y coordinate of top left corner
     * @param  width    width
     * @param  height   height
     */
    public void drawRectangle(Integer x, Integer y,
                              Integer width, Integer height)
    {
        graphic.draw(new Rectangle(x, y, width, height));
        canvas.repaint();
    }

    /**
     * Draws a filled rectangle on the Canvas.
     * @param  x        x coordinate of top left corner
     * @param  y        y coordinate of top left corner
     * @param  width    width
     * @param  height   height
     */
    public void drawFilledRectangle(Integer x, Integer y,
                                    Integer width, Integer height)
    {
        graphic.fill(new Rectangle(x, y, width, height));
        canvas.repaint();
    }

    /**
     * Draws a polygon on the Canvas.
     * @param  xs   array of x coordinates of polygon points
     * @param  ys   array of y coordinates of polygon points
     * @param  size the number of points (vertices) in the polygon
     */
    public void drawPolygon(Integer[] xs, Integer[] ys, Integer size)
    {
        Integer length = xs.length;
        int[] primitiveXs = new int[length];
        int[] primitiveYs = new int[length];

        for (Integer i = 0; i < length; i++) {
            primitiveXs[i] = xs[i];
            primitiveYs[i] = ys[i];
        }

        graphic.draw(new Polygon(primitiveXs,primitiveYs,size));
        canvas.repaint();
    }

    /**
     * Draws a filled polygon on the Canvas.
     * @param  xs   array of x coordinates of polygon points
     * @param  ys   array of y coordinates of polygon points
     * @param  size the number of points (vertices) in the polygon
     */
    public void drawFilledPolygon(Integer[] xs, Integer[] ys, Integer size)
    {
        Integer length = xs.length;
        int[] primitiveXs = new int[length];
        int[] primitiveYs = new int[length];

        for (Integer i = 0; i < length; i++) {
            primitiveXs[i] = xs[i];
            primitiveYs[i] = ys[i];
        }

        graphic.fill(new Polygon(primitiveXs,primitiveYs,size));
        canvas.repaint();
    }

    /**
     * Erases a rectangle on the Canvas.
     * @param  x       x coordinate of top left corner
     * @param  y       y coordinate of top left corner
     * @param  width   width
     * @param  height  height
     */
    public void eraseRectangle(Integer x, Integer y,
                               Integer width, Integer height)
    {
        eraseOutline(new Rectangle(x, y, width, height));
    }


    /**
     * Erases a filled rectangle on the Canvas.
     * @param  x       x coordinate of top left corner
     * @param  y       y coordinate of top left corner
     * @param  width   width
     * @param  height  height
     */
    public void eraseFilledRectangle(Integer x, Integer y,
                                     Integer width, Integer height)
    {
        erase(new Rectangle(x, y, width, height));
    }

    /**
     * Draws an oval on the Canvas.
     * @param  x        x coordinate of top left corner
     * @param  y        y coordinate of top left corner
     * @param  width    width
     * @param  height   height
     */
    public void drawOval(Integer x, Integer y, Integer width, Integer height)
    {
        graphic.draw(new Ellipse2D.Double(x, y, width, height));
        canvas.repaint();
    }

    /**
     * Draws a filled oval on the Canvas.
     * @param  x        x coordinate of top left corner
     * @param  y        y coordinate of top left corner
     * @param  width    width
     * @param  height   height
     */
    public void drawFilledOval(Integer x, Integer y,
                               Integer width, Integer height)
    {
        graphic.fill(new Ellipse2D.Double(x, y, width, height));
        canvas.repaint();
    }

    /**
     * Erases an oval on the Canvas.
     * @param  x        x coordinate of top left corner
     * @param  y        y coordinate of top left corner
     * @param  width    width
     * @param  height   height
     */
    public void eraseOval(Integer x, Integer y, Integer width, Integer height)
    {
        eraseOutline(new Ellipse2D.Double(x, y, width, height));
    }

    /**
     * Erases a filled oval on the Canvas.
     * @param  x        x coordinate of top left corner
     * @param  y        y coordinate of top left corner
     * @param  width    width
     * @param  height   height
     */
    public void eraseFilledOval(Integer x, Integer y,
                                Integer width, Integer height)
    {
        erase(new Ellipse2D.Double(x, y, width, height));
    }

    /**
     * Sets the foreground color of the Canvas.
     * @param  newColor   the new color for the foreground of the Canvas
     */
    private void setForegroundColor(Color newColor)
    {
        graphic.setColor(newColor);
    }

    /**
     * Returns the current color of the foreground.
     * @return   the color of the foreground of the Canvas
     */
    private Color getForegroundColor()
    {
        return graphic.getColor();
    }

    /**
     * Sets the background color of the Canvas.
     * @param  newColor   the new color for the background of the Canvas
     */
    private void setBackgroundColor(Color newColor)
    {
        backgroundColor = newColor;
        graphic.setBackground(newColor);
    }

    /**
     * Fills in the Canvas (background) with the specified color.
     * @param newColor  the new color for the background of the Canvas
     */
    public void fillBackground(Color newColor)
    {
         Dimension size = canvas.getSize();
         backgroundColor = newColor;
         graphic.setColor(backgroundColor);
         graphic.fillRect(0, 0, size.width, size.height);
         graphic.setColor(inkColor);
    }

    /**
     * Returns the current color of the background
     * @return   the color of the background of the Canvas
     */
    private Color getBackgroundColor()
    {
        return backgroundColor;
    }

    /**
     * changes the current Font used on the Canvas
     * @param  newFont   new font to be used for String output
     */
    public void setFont(Font newFont)
    {
        graphic.setFont(newFont);
    }

    /**
     * Returns the current font of the canvas.
     * @return     the font currently in use
     **/
    public Font getFont()
    {
        return graphic.getFont();
    }

    /**
     * Sets the point size of the current font to the specified value.
     * The style and font family remain the same.
     *
     * @param  newSize  the new point size
     */
    public void setFontSize( Integer newSize )
    {
        Font f = graphic.getFont().deriveFont( (float)newSize );
        setFont( f );
    }

    /**
     * Sets the size of the canvas.
     * @param  width    new width
     * @param  height   new height
     */
    public void setSize(Integer width, Integer height)
    {
        canvas.setPreferredSize(new Dimension(width, height));
        Image oldImage = canvasImage;
        canvasImage = canvas.createImage(width, height);
        graphic = (Graphics2D)canvasImage.getGraphics();
        graphic.setColor(backgroundColor);
        graphic.fillRect(0, 0, width, height);
        graphic.setColor(inkColor);
        graphic.drawImage(oldImage, 0, 0, null);
        frame.pack();
    }

    /**
     * Returns the size of the canvas.
     * @return     The current dimension of the canvas
     */
    private Dimension getSize()
    {
        return canvas.getSize();
    }

    /**
     * Waits for a specified number of milliseconds before finishing.
     * This provides an easy way to specify a small delay which can be
     * used when producing animations.
     * @param  milliseconds  the number
     */
    public void pause(Integer milliseconds)
    {
        try
        {
            Thread.sleep(milliseconds);
        }
        catch (Exception e)
        {
            // ignoring exception at the moment
        }
    }

    /************************************************************************
     * Nested class CanvasPane - the actual canvas component contained in the
     * Canvas frame. This is essentially a JPanel with added capability to
     * refresh the image drawn on it.
     * MODIFIED by acd:  changed visibility to protected (from private)
     * to allow subclassing (basically, so we can add mouse listeners to
     * the canvas), and added the call to super.paint() (an additional hack
     * to allow us to add components like buttons and menus to the canvas).
     */
    protected class CanvasPane extends JPanel
    {
        public void paint(Graphics g)
        {
            g.drawImage(canvasImage, 0, 0, null);
            super.paint(g);
        }
    }
}
