import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

import javax.swing.JPanel;

/**
 * saves things that are drawn to the window
 * 
 * @author hollanbm. Created Apr 9, 2008.
 */
public class Canvas extends JPanel implements MouseListener,
		MouseMotionListener, KeyListener
{
	@SuppressWarnings("unused")
	private String tool;

	// stored when mouse is pressed
	private Point p = null;

	// stored when mouse is released
	private Point p1 = null;

	// this array holds everything that is drawn to the canvas
	private final ArrayList<ShapeWithStyle> storage = new ArrayList<ShapeWithStyle>();

	// used to determine if shift is held
	private boolean shiftKey = false;

	// used to determine if fill is set
	private boolean fill = false;

	// used for brush
	private int width = 1;

	// used for pencil
	private Point currentPoint = null;

	// used for pencil
	private Point previousPoint = null;

	// current color
	private Color color;

	/**
	 * this will be the canvas for drawing
	 */
	public Canvas()
	{
		// add the respective listeners
		this.addMouseListener(this);
		this.addMouseMotionListener(this);
		this.addKeyListener(this);
		this.setPreferredSize(new Dimension(600, 800));
	}

	@SuppressWarnings("cast")
	@Override
	public void paintComponent(final Graphics g)
	{
		super.paintComponent(g);
		final Graphics2D g2 = (Graphics2D) g;

		for (int i = 0; i < this.storage.size(); i++)
		{
			g2
					.setStroke(new BasicStroke((float) this.storage.get(i)
							.getWidth()));
			// loop through and set the color of the shape before it is drawn to
			// what is selected, then draw that shape
			g2.setColor(this.storage.get(i).getColor());
			if (this.storage.get(i).getFill() == true)
			{
				// if fill is true then fill that shape
				g2.fill(this.storage.get(i).getShape());
			}
			g2.draw(this.storage.get(i).getShape());

		}

	}

	public void mouseClicked(final MouseEvent e)
	{
		if (this.tool == "polygon")
		{
			// if left click is pressed
			if (e.getButton() == MouseEvent.BUTTON1)
			{

				if (this.previousPoint == null)
				{
					this.previousPoint = new Point(e.getX(), e.getY());
				}
				this.currentPoint = new Point(e.getX(), e.getY());
				// sets previous point to current point, then connects the
				// points with a line
				this.storage.add(new ShapeWithStyle(new Line2D.Double(
						this.previousPoint, this.currentPoint), this.color,
						this.fill, this.width));
				this.previousPoint = new Point(e.getX(), e.getY());
				this.repaint();
			}
			// if a user right clicks, stop the polygon
			if (e.getButton() == MouseEvent.BUTTON3)
			{
				// System.out.println("right-click");
				this.previousPoint = null;
				this.currentPoint = null;

			}
		}
		this.repaint();
	}

	public void mouseEntered(final MouseEvent e)
	{
		// any time the mouse enters the canvas grab focus, this is for the
		// keyListener to properly work
		this.requestFocusInWindow(true);
	}

	public void mousePressed(final MouseEvent e)
	{
		// captures original press location
		this.p = new Point(e.getX(), e.getY());
		// System.out.print("" + this.p.getX());
		// System.out.println(", " + this.p.getY());
	}

	public void mouseDragged(final MouseEvent e)
	{
		if (this.tool == "pencil")
		{
			if (this.previousPoint == null)
			{
				this.previousPoint = new Point(e.getX(), e.getY());
			}

			this.currentPoint = new Point(e.getX(), e.getY());
			// connects previous and current points with a line then instantly
			// repaints
			this.storage.add(new ShapeWithStyle(new Line2D.Double(
					this.previousPoint, this.currentPoint), this.color,
					this.fill, this.width));
			this.previousPoint = new Point(e.getX(), e.getY());
		}
		this.repaint();
	}

	public void mouseReleased(final MouseEvent e)
	{
		this.p1 = new Point(e.getX(), e.getY());
		// System.out.print("" + this.p1.getX());
		// System.out.println(", " + this.p1.getY());

		if (this.tool == "pencil")
		{
			this.currentPoint = null;
			this.previousPoint = null;
		}

		if (this.tool == "line")
		{
			// adds line to storage array
			final Line2D.Double l = new Line2D.Double(this.p, this.p1);
			this.storage.add(new ShapeWithStyle(l, this.color, this.fill,
					this.width));

		}
		// determines wheter or not shift is held down for perfect squares
		if ((this.tool == "rectangle") && !this.shiftKey)
		{
			// the following if statements handle which way the rectangle is
			// being drawn. IE bottom left to top right, top right to
			// bottom left, bottom right to top left etc, top left to bottom
			// right

			if ((this.p.getX() > this.p1.getX())
					&& (this.p.getY() > this.p1.getY()))
			{

				this.storage.add(new ShapeWithStyle(new Rectangle2D.Double(
						this.p1.getX(), this.p1.getY(), this.p.getX()
								- this.p1.getX(), this.p.getY()
								- this.p1.getY()), this.color, this.fill,
						this.width));
			} else if ((this.p.getX() < this.p1.getX())
					&& (this.p.getY() < this.p1.getY()))
			{

				this.storage.add(new ShapeWithStyle(
						new Rectangle2D.Double(this.p.getX(), this.p.getY(),
								this.p1.getX() - this.p.getX(), this.p1.getY()
										- this.p.getY()), this.color,
						this.fill, this.width));
			}

			else if ((this.p.getX() > this.p1.getX())
					&& (this.p.getY() < this.p1.getY()))
			{

				this.storage.add(new ShapeWithStyle(new Rectangle2D.Double(
						this.p1.getX(), this.p.getY(), this.p.getX()
								- this.p1.getX(), this.p1.getY()
								- this.p.getY()), this.color, this.fill,
						this.width));
			}

			else if ((this.p.getX() < this.p1.getX())
					&& (this.p.getY() > this.p1.getY()))
			{

				this.storage.add(new ShapeWithStyle(
						new Rectangle2D.Double(this.p.getX(), this.p1.getY(),
								this.p1.getX() - this.p.getX(), this.p.getY()
										- this.p1.getY()), this.color,
						this.fill, this.width));
			}
		}
		if ((this.tool == "rectangle") && this.shiftKey)
		{
			// the following variables are used in determining the sides for the
			// perfect rectangle
			// one per case of each way the rectangle can be drawn

			final double size1 = Math.max(this.p.getX() - this.p1.getX(), this.p
					.getY()
					- this.p1.getY());
			final double size2 = Math.max(this.p1.getX() - this.p.getX(), this.p1
					.getY()
					- this.p.getY());
			final double size3 = Math.max(this.p.getX() - this.p1.getX(), this.p1
					.getY()
					- this.p.getY());
			final double size4 = Math.max(this.p1.getX() - this.p.getX(), this.p
					.getY()
					- this.p1.getY());

			// the following if statements handle which way the rectangle is
			// being drawn. IE bottom left to top right, top right to
			// bottom left, bottom right to top left etc, top left to bottom
			// right

			if ((this.p.getX() > this.p1.getX())
					&& (this.p.getY() > this.p1.getY()))
			{
				this.storage.add(new ShapeWithStyle(new Rectangle2D.Double(
						this.p1.getX(), this.p1.getY(), size1, size1),
						this.color, this.fill, this.width));
			} else if ((this.p.getX() < this.p1.getX())
					&& (this.p.getY() < this.p1.getY()))
			{
				this.storage.add(new ShapeWithStyle(new Rectangle2D.Double(
						this.p.getX(), this.p.getY(), size2, size2),
						this.color, this.fill, this.width));
			}

			else if ((this.p.getX() > this.p1.getX())
					&& (this.p.getY() < this.p1.getY()))
			{
				this.storage.add(new ShapeWithStyle(new Rectangle2D.Double(
						this.p1.getX(), this.p.getY(), size3, size3),
						this.color, this.fill, this.width));
			}

			else if ((this.p.getX() < this.p1.getX())
					&& (this.p.getY() > this.p1.getY()))
			{
				this.storage.add(new ShapeWithStyle(new Rectangle2D.Double(
						this.p.getX(), this.p1.getY(), size4, size4),
						this.color, this.fill, this.width));
			}

		}

		if ((this.tool == "oval") && !this.shiftKey)
		{
			// the following if statements handle which way the Oval is
			// being drawn. IE bottom left to top right, top right to
			// bottom left, bottom right to top left etc, top left to bottom
			// right

			if ((this.p.getX() > this.p1.getX())
					&& (this.p.getY() > this.p1.getY()))
			{
				this.storage.add(new ShapeWithStyle(new Ellipse2D.Double(
						this.p1.getX(), this.p1.getY(), this.p.getX()
								- this.p1.getX(), this.p.getY()
								- this.p1.getY()), this.color, this.fill,
						this.width));
			} else if ((this.p.getX() < this.p1.getX())
					&& (this.p.getY() < this.p1.getY()))
			{
				this.storage.add(new ShapeWithStyle(new Ellipse2D.Double(this.p
						.getX(), this.p.getY(), this.p1.getX() - this.p.getX(),
						this.p1.getY() - this.p.getY()), this.color, this.fill,
						this.width));
			}

			else if ((this.p.getX() > this.p1.getX())
					&& (this.p.getY() < this.p1.getY()))
			{
				// System.out.println("oval");
				this.storage.add(new ShapeWithStyle(new Ellipse2D.Double(
						this.p1.getX(), this.p.getY(), this.p.getX()
								- this.p1.getX(), this.p1.getY()
								- this.p.getY()), this.color, this.fill,
						this.width));
			}

			else if ((this.p.getX() < this.p1.getX())
					&& (this.p.getY() > this.p1.getY()))
			{
				this.storage.add(new ShapeWithStyle(new Ellipse2D.Double(this.p
						.getX(), this.p1.getY(),
						this.p1.getX() - this.p.getX(), this.p.getY()
								- this.p1.getY()), this.color, this.fill,
						this.width));
			}
		}

		if ((this.tool == "oval") && this.shiftKey)
		{
			// the following variables are used in determining the sides for the
			// perfect rectangle
			// one per case of each way the rectangle can be drawn

			final double size1 = Math.max(this.p.getX() - this.p1.getX(), this.p
					.getY()
					- this.p1.getY());
			final double size2 = Math.max(this.p1.getX() - this.p.getX(), this.p1
					.getY()
					- this.p.getY());
			final double size3 = Math.max(this.p.getX() - this.p1.getX(), this.p1
					.getY()
					- this.p.getY());
			final double size4 = Math.max(this.p1.getX() - this.p.getX(), this.p
					.getY()
					- this.p1.getY());

			// the following if statements handle which way the Oval is
			// being drawn. IE bottom left to top right, top right to
			// bottom left, bottom right to top left etc, top left to bottom
			// right

			if ((this.p.getX() > this.p1.getX())
					&& (this.p.getY() > this.p1.getY()))
			{
				this.storage.add(new ShapeWithStyle(new Ellipse2D.Double(
						this.p1.getX(), this.p1.getY(), size1, size1),
						this.color, this.fill, this.width));
			} else if ((this.p.getX() < this.p1.getX())
					&& (this.p.getY() < this.p1.getY()))
			{
				this.storage.add(new ShapeWithStyle(new Ellipse2D.Double(this.p
						.getX(), this.p.getY(), size2, size2), this.color,
						this.fill, this.width));
			}

			else if ((this.p.getX() > this.p1.getX())
					&& (this.p.getY() < this.p1.getY()))
			{
				// System.out.println("oval");
				this.storage.add(new ShapeWithStyle(new Ellipse2D.Double(
						this.p1.getX(), this.p.getY(), size3, size3),
						this.color, this.fill, this.width));
			}

			else if ((this.p.getX() < this.p1.getX())
					&& (this.p.getY() > this.p1.getY()))
			{
				this.storage.add(new ShapeWithStyle(new Ellipse2D.Double(this.p
						.getX(), this.p1.getY(), size4, size4), this.color,
						this.fill, this.width));
			}
		}
		this.repaint();
	}

	public void keyPressed(final KeyEvent e)
	{
		if (e.getKeyCode() == KeyEvent.VK_SHIFT)
		{
			this.shiftKey = true;
			// System.out.println("Shift ON");
		}

	}

	public void keyReleased(final KeyEvent e)
	{
		if (e.getKeyCode() == KeyEvent.VK_SHIFT)
		{
			this.shiftKey = false;
			// System.out.println("Shift OFF");
		}
	}

	public void keyTyped(final KeyEvent e)
	{
		// null
	}

	public void mouseMoved(final MouseEvent e)
	{
		// null does nothing

	}

	// the following methods are all helper methods

	/**
	 * this method sets whether or not a shape should be filled
	 * 
	 * @param f
	 *            true/false
	 */
	public void setFill(final boolean f)
	{
		this.fill = f;
		// System.out.println("fill is set");
	}

	/**
	 * method for removing last shape that was drawn
	 */
	public void Undo()
	{
		if (this.storage.size() > 0)
		{
			// removes last item in the array then repaints
			this.storage.remove(this.storage.size() - 1);
			this.repaint();
		}

	}

	/**
	 * helper method to communicate with Paint to reset storage array
	 */
	public void clearAll()
	{
		// clears out the storage array, trims it and resets any points used
		// prior

		// System.out.println("reset");
		this.storage.clear();
		this.storage.trimToSize();
		// System.out.println(this.storage.size());
		this.currentPoint = null;
		this.previousPoint = null;
		this.p = null;
		this.p1 = null;
		this.repaint();
	}

	/**
	 * method of communication between paint and canvas adds chosen color to the
	 * array
	 * 
	 * @param c
	 *            Color
	 */
	public void setColor(final Color c)
	{
		this.color = c;
		// System.out.println("Color added");
	}

	/**
	 * sets the width of the item being drawn
	 * 
	 * @param w
	 *            width
	 */
	public void setWidth(final int w)
	{
		this.width = w;
		// System.out.println("width changed" + this.width);
	}

	/**
	 * method of communication betweeen paint and canvas
	 * 
	 * @param tool
	 *            current tool being used
	 */
	public void setTool(final String tool)
	{
		this.tool = tool;
		// System.out.println("setting tool " + this.tool);
	}

	public void mouseExited(final MouseEvent arg0)
	{
		// null

	}

	// end helper methods
}
