import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

import javax.swing.JPanel;


/**
 * Creates a canvas panel to be displayed in the paint frame, on which the user draws.
 *
 * @author swansom1.
 *         Created Apr 8, 2008.
 */
public class CanvasPanel extends JPanel implements MouseListener,MouseMotionListener,MouseWheelListener {

	private Class toolClass;
	private BufferedImage image;
	private Graphics2D g2image;
	@SuppressWarnings("hiding")
	private final static int WIDTH = 1000;
	@SuppressWarnings("hiding")
	private final static int HEIGHT = 1000;
	/**
	 * The color that will be drawn
	 */
	protected Color color;
	private ArrayList<Object> drawArray;
	private ArrayList<Color> colorArray;
	private ArrayList<BasicStroke> strokeArray;
	private Pencil pencil;
	private Brush brush;
	private Line line;
	private Rectangle rectangle;
	private Oval oval;
	private Polygon polygon;
	private SprayCan sprayCan;
	private Eraser eraser;
	private Eyedrop dropper = new Eyedrop(this);
	
	
	/**
	 * Checks to see if a new polygon can be made
	 */
	private boolean checkState;
	private BasicStroke checkStroke;
	private int density;
	private DisplayPanel display;
	private ColorPanel colorChooser;
	private MenuBar menuBar;
	
	/**
	 * Create the CanvasPanel.
	 * 
	 * @param toolPanel 
	 */
	CanvasPanel() {
		super();
		this.image = new BufferedImage(WIDTH,HEIGHT, BufferedImage.TYPE_INT_ARGB);
		this.g2image = this.image.createGraphics();
		this.addMouseListener(this);
		this.addMouseMotionListener(this);
		this.addMouseWheelListener(this);
		//Draw background
		this.g2image.setColor(Color.WHITE);
		this.g2image.fillRect(0,0,WIDTH,HEIGHT);
		//Set initial color
		this.g2image.setColor(Color.BLACK);
		this.drawArray = new ArrayList<Object>();
		this.colorArray = new ArrayList<Color>();
		this.strokeArray = new ArrayList<BasicStroke>();
		this.setPreferredSize(new Dimension(WIDTH,HEIGHT));
		this.checkState = true;
		this.checkStroke = new BasicStroke(1,BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER);
		this.density = 1;
	}

	public void mouseClicked(MouseEvent arg0) {
		if(this.toolClass == null) {
			return;
		}
		else if(this.toolClass.getName() == "Polygon") {
			this.drawArray.add(this.polygon.finalPoint(arg0.getX(),arg0.getY()));
			this.drawArray.remove(this.drawArray.size()-2);
		}	
	}

	public void mouseEntered(MouseEvent e) {
		// empty
	}

	public void mouseExited(MouseEvent e) {
		if(this.toolClass == null) {
			return;
		}
		if(this.toolClass.getName() == "Polygon" && this.checkState == false) {
			this.drawArray.add(this.polygon.exited());
			this.drawArray.remove(this.drawArray.size()-2);
			this.checkState = true;
			this.repaint();
		}
	}

	public void mousePressed(MouseEvent e) {
		if(this.toolClass == null){
			return;
		}
		else if(this.toolClass.getName() == "Pencil"){
			this.pencil = new Pencil(e.getX(),e.getY());
			this.drawArray.add(this.pencil.draw(e.getX(),e.getY()));
			this.strokeArray.add(new BasicStroke(this.pencil.getStroke()));
		}
		else if(this.toolClass.getName() == "Brush"){
			this.brush = new Brush(e.getX(),e.getY());
			this.drawArray.add(this.brush.draw(e.getX(),e.getY()));
			int newStroke = (int) (this.checkStroke.getLineWidth()+1);
			this.strokeArray.add(new BasicStroke(newStroke));
		}
		else if(this.toolClass.getName() == "Eraser"){
			this.eraser = new Eraser(e.getX(),e.getY());
			this.drawArray.add(this.eraser.draw(e.getX(),e.getY()));
			this.strokeArray.add(new BasicStroke(this.checkStroke.getLineWidth()*2));
		}
		else if(this.toolClass.getName() == "SprayCan") {
			this.sprayCan = new SprayCan(e.getX(),e.getY(),this.checkStroke.getLineWidth(),this.density);
			this.drawArray.add(this.sprayCan.draw(e.getX(),e.getY()));
			this.strokeArray.add(this.checkStroke);
		}
		else if(this.toolClass.getName() == "Rectangle"){
			this.rectangle = new Rectangle(e.getX(),e.getY());
			this.drawArray.add(this.rectangle.draw(e.getX(),e.getY()));
			this.strokeArray.add(this.checkStroke);
		}
		else if(this.toolClass.getName() == "Oval"){
			this.oval = new Oval(e.getX(),e.getY());
			this.drawArray.add(this.oval.draw(e.getX(),e.getY()));
			this.strokeArray.add(this.checkStroke);
		}
		else if(this.toolClass.getName() == "Line"){
			this.line = new Line(e.getX(),e.getY());
			this.drawArray.add(this.line.draw(e.getX(),e.getY()));
			this.strokeArray.add(this.checkStroke);
		}
		else if(this.toolClass.getName() == "Polygon"){
			if(this.checkState == true){
				this.polygon = new Polygon(e.getX(),e.getY());
				this.drawArray.add(this.polygon.draw(e.getX(),e.getY()));
				this.strokeArray.add(new BasicStroke(this.checkStroke.getLineWidth(),this.checkStroke.getEndCap(),BasicStroke.JOIN_BEVEL));
			}
		}
		else if(this.toolClass.getName() == "Eyedrop"){
			return;
		}
		if(this.checkState == true){
			if(this.toolClass.getName() == "Eraser"){
				this.colorArray.add(Color.WHITE);
			}else{
			this.colorArray.add(this.g2image.getColor());
			}
		}
		this.menuBar.clearRedo();	
	}

	public void mouseReleased(MouseEvent e) {
		if(this.toolClass == null) {
			return;
		}else if(this.toolClass.getName() != "Eyedrop"){
			this.drawArray.remove(this.drawArray.size()-1);
		}
		if(this.toolClass.getName() == "Pencil"){
			this.drawArray.add(this.pencil.draw(e.getX(),e.getY()));
		}
		else if(this.toolClass.getName() == "Brush"){
			this.drawArray.add(this.brush.draw(e.getX(),e.getY()));
		}
		else if(this.toolClass.getName() == "Eraser"){
			this.drawArray.add(this.eraser.draw(e.getX(),e.getY()));
		}
		else if(this.toolClass.getName() == "SprayCan"){
			this.drawArray.add(this.sprayCan.draw(e.getX(),e.getY()));
		}
		else if(this.toolClass.getName() == "Rectangle"){
			if(e.getModifiersEx() == InputEvent.SHIFT_DOWN_MASK){
				this.drawArray.add(this.rectangle.drawSquare(e.getX(),e.getY()));
			}else{
				this.drawArray.add(this.rectangle.draw(e.getX(),e.getY()));				
			}
		}
		else if(this.toolClass.getName() == "Oval"){
			if(e.getModifiersEx() == InputEvent.SHIFT_DOWN_MASK){
				this.drawArray.add(this.oval.drawCircle(e.getX(),e.getY()));
			}else{
				this.drawArray.add(this.oval.draw(e.getX(),e.getY()));
			}
		}
		else if(this.toolClass.getName() == "Line"){
			if(e.getModifiersEx() == InputEvent.SHIFT_DOWN_MASK){
				this.drawArray.add(this.line.drawStraight(e.getX(),e.getY()));
			}else{
				this.drawArray.add(this.line.draw(e.getX(),e.getY()));
			}
		}
		else if(this.toolClass.getName() == "Polygon"){
			this.drawArray.add(this.polygon.finalPoint(e.getX(),e.getY()));
			this.checkState = this.polygon.checkState(e.getX(),e.getY());
		}
		else if(this.toolClass.getName() == "Eyedrop"){
			this.colorChooser.getColorChooser().setColor(this.dropper.draw(e.getX(),e.getY()));
		}
		this.repaint();
	}

	public void mouseDragged(MouseEvent e) {
		if(this.toolClass == null) {
			return;
		}
		else if(this.toolClass.getName() == "Pencil") {
			this.drawArray.add(this.pencil.draw(e.getX(),e.getY()));
		}
		else if(this.toolClass.getName() == "Brush") {
			this.drawArray.add(this.brush.draw(e.getX(),e.getY()));
		}
		else if(this.toolClass.getName() == "Line") {
			if(e.getModifiersEx() == InputEvent.SHIFT_DOWN_MASK + InputEvent.BUTTON1_DOWN_MASK){
				this.drawArray.add(this.line.drawStraight(e.getX(),e.getY()));
			}else{
				this.drawArray.add(this.line.draw(e.getX(),e.getY()));
			}
		}
		else if(this.toolClass.getName() == "SprayCan") {
			this.drawArray.add(this.sprayCan.draw(e.getX(),e.getY()));
		}
		else if(this.toolClass.getName() == "Rectangle") {
			if(e.getModifiersEx() == InputEvent.SHIFT_DOWN_MASK + InputEvent.BUTTON1_DOWN_MASK){
				this.drawArray.add(this.rectangle.drawSquare(e.getX(),e.getY()));
			}else{
				this.drawArray.add(this.rectangle.draw(e.getX(),e.getY()));
			}
		}
		else if(this.toolClass.getName() == "Oval") {
			if(e.getModifiersEx() == InputEvent.SHIFT_DOWN_MASK + InputEvent.BUTTON1_DOWN_MASK){
				this.drawArray.add(this.oval.drawCircle(e.getX(),e.getY()));
			}else{
				this.drawArray.add(this.oval.draw(e.getX(),e.getY()));
			}
		}
		else if(this.toolClass.getName() == "Polygon") {
			this.drawArray.add(this.polygon.draw(e.getX(),e.getY()));
		}
		else if(this.toolClass.getName() == "Eraser"){
			this.drawArray.add(this.eraser.draw(e.getX(),e.getY()));
		}
		else if(this.toolClass.getName() == "Eyedrop"){
			return;
		}
		this.drawArray.remove(this.drawArray.size()-2);
		this.repaint();
	}

	public void mouseMoved(MouseEvent e) {
		this.setColor(this.colorChooser.getColorChooser().getColor());
	}

	/**
	 * 
	 * @param toolClass Sets the tool selected by the user
	 */
	public void setTool(Class toolClass) {
		this.toolClass = toolClass;
	}
	
	/**
	 *
	 * @param colors Sets the color to be drawn with
	 */
	public void setColor(Color colors) {
		this.g2image.setColor(colors);
	}
	
	/**
	 * 
	 * @return current color of canvas
	 */
	public Color getColor() {
		return this.g2image.getColor();
	}
	
	/**
	 *
	 * @param b sets state of checkState.
	 */
	public void setCheck(){
		this.checkState = true;
	}
	
	/**
	 * 
	 * @param x sets size of stroke to be drawn with
	 */
	public void setStroke(int x){
		this.checkStroke = new BasicStroke(x,BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER);
	}
	
	/**
	 *
	 * @return The drawArray
	 */
	public ArrayList<Object> getDrawArray(){
		return this.drawArray;
	}
	
	/**
	 *
	 * @return The colorArray
	 */
	public ArrayList<Color> getColorArray() {
		return this.colorArray;
	}
	
	/**
	 *
	 * @return The strokeArray
	 */
	public ArrayList<BasicStroke> getStrokeArray() {
		return this.strokeArray;
	}
	
	/**
	 * Sets the colorArray
	 *
	 * @param colorArray
	 */
	public void setColorArray(ArrayList<Color> colorArray){
		this.colorArray = colorArray;
		this.repaint();
	}
	
	/**
	 * Sets the strokeArray
	 *
	 * @param strokeArray
	 */
	public void setStrokeArray(ArrayList<BasicStroke> strokeArray){
		this.strokeArray = strokeArray;
		this.repaint();
	}
	
	/**
	 *Sets the drawArray
	 *
	 * @param arrayList
	 */
	public void setDrawArray(ArrayList<Object> arrayList){
		this.drawArray = arrayList;
		this.repaint();
	}

	@SuppressWarnings("unchecked")
	@Override
	public void paintComponent(Graphics g) {
		Graphics2D g2 = (Graphics2D) g;
		this.g2image.setColor(Color.WHITE);
		this.g2image.fillRect(0,0,WIDTH,HEIGHT);

		if(this.drawArray.size() != 0){
			for (int i = 0; i < this.drawArray.size(); i++) {
				try {
					this.g2image.setColor(this.colorArray.get(i));
					this.g2image.setStroke(this.strokeArray.get(i));
					this.g2image.draw((Shape) this.drawArray.get(i));
				} catch (ClassCastException exception) {
					try {
						this.g2image.setColor(this.colorArray.get(i));
						this.g2image.setStroke(this.strokeArray.get(i));
						ArrayList<Ellipse2D> list = (ArrayList<Ellipse2D>) this.drawArray.get(i);
						for (Ellipse2D ellipse2D : list) {
							this.g2image.fill(ellipse2D);
						}
					} catch (ClassCastException exception2) {
						this.g2image.drawImage((BufferedImage)this.drawArray.get(i),new AffineTransformOp(new AffineTransform(),1), 0, 0);						
					}
				}
			}
		}
		g2.drawImage(this.image, null,0, 0);
	}
	
	/**
	 *
	 * @return The canvas' BufferedImage
	 */
	public BufferedImage getBufferedImage(){
		return this.image;
	}
	
	public void mouseWheelMoved(MouseWheelEvent e) {
		if(e.getModifiersEx() == InputEvent.CTRL_DOWN_MASK){
			this.display.setDensity(e.getWheelRotation());
		}else{
			this.display.setSlider(e.getWheelRotation());
		}
	}

	/**
	 * Sets display panel of canvas
	 *
	 * @param display
	 */
	public void setDisplay(DisplayPanel display) {
		this.display = display;
	}

	/**
	 * Sets value to spray can density
	 *
	 * @param value
	 */
	public void setDensity(int value) {
		this.density = value;
	}

	/**
	 *
	 * @return BufferedImage in canvas
	 */
	public BufferedImage getImage() {
		return this.image;
	}

	/**
	 * Assigns colorChooser to panel
	 *
	 * @param panel
	 */
	public void setColorChooser(ColorPanel panel) {
		this.colorChooser = panel;
	}
	
		
	/**
	 * Sets canvas' BufferedImage to image2
	 *
	 * @param image2
	 */
	public void setBufferedImage(BufferedImage image2) {
		this.drawArray.add(image2);
		this.colorArray.add(this.color);
		this.strokeArray.add(this.checkStroke);
		this.repaint();
	}
	
	/**
	 * Assigns menuBar to menu
	 *
	 * @param menu
	 */
	public void setMenuBar(MenuBar menu) {
		this.menuBar = menu;
	}

}