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

import javax.swing.JPanel;


/**
 * TODO Put here a description of what this class does.
 *
 * @author ian.
 *         Created Mar 22, 2008.
 */
public class Canvas extends JPanel implements MouseListener, MouseMotionListener{
	private BufferedImage I;
	private ArrayList <BufferedImage>undo = new ArrayList<BufferedImage>();
	//private boolean painting=false;
	private Paint a;
	private Brush b;
	private int x=0,y=0;
	private boolean first;
	private boolean[][] selected;
	private Shape s;
	private float currentscale=1.0f;
	private AffineTransform zoom = new AffineTransform();
	/**
	 * 
	 * creates a new Canvas with width x, height y, and the painter a, with the default brush being b
	 *
	 * @param x
	 * @param y
	 * @param a
	 * @param b
	 */
	public Canvas(int x,int y,Paint a,Brush b){
		super();
		this.I = new BufferedImage(x,y,BufferedImage.TYPE_INT_ARGB);
		this.setSize(x,y);
		this.selected = new boolean[x][y];
		for(int i=0;i<x;i++){
			for(int j=0;j<y;j++){
				this.I.setRGB(i,j,Color.white.getRGB());
				this.selected[i][j]=true;
			}
		}
		
		this.a=a;
		this.b=b;
		int w = this.I.getWidth();
		int h = this.I.getHeight();
		BufferedImage temp = new BufferedImage(w,h,BufferedImage.TYPE_INT_ARGB);
		for(int i=0;i<w;i++){
			for(int j=0;j<h;j++){
				temp.setRGB(i,j,this.I.getRGB(i,j));
			}
		}
		this.setPreferredSize(new Dimension(x,y));
		this.undo.add(temp);
	}
	@Override
	public void paintComponent(Graphics g){
		Graphics2D g2 = (Graphics2D)g;
		AffineTransformOp z = new AffineTransformOp(this.zoom,AffineTransformOp.TYPE_BICUBIC);
		try {
		g2.drawImage(this.I,z,0,0);
		g2.draw(this.s);
		}
		
		catch(NullPointerException ee){
			/**does nothing  but catch a null pointer**/
		}
		try{
			if(this.b instanceof MoverTool){
				int w = ((MoverTool) this.b).img.getWidth();
				int h = ((MoverTool) this.b).img.getHeight();
				BufferedImage temp = new BufferedImage(w, h,
						BufferedImage.TYPE_INT_ARGB);
				for (int i = 0; i < w; i++) {
					for (int j = 0; j < h; j++) {
							temp.setRGB(i, j, ((MoverTool) this.b).img.getRGB(
									i, j));
					}
				}
				
				g2.drawImage(temp,z, ((MoverTool) this.b).getX(), ((MoverTool) this.b).getY());
			} 	
		}
		catch(NullPointerException e){
			//nothing to do
		}
		try {
		int s = this.b.getSize();
				BufferedImage temp = new BufferedImage(2 * s, 2 * s,
						BufferedImage.TYPE_INT_ARGB);
				for (int i = 0; i < 2 * s * 2 * s; i++) {
					if (this.b.getRGB(i % (2 * s), i / (2 * s)) == Color.white
							.getRGB()) {
						Color t = new Color(0, 0, 0, 0);
						temp.setRGB(i % (2 * s), i / (2 * s), t.getRGB());
					} else {
						temp.setRGB(i % (2 * s), i / (2 * s), this.b.getRGB(i
								% (2 * s), i / (2 * s)));
					}
				}
				g2.drawImage(temp, z, this.x - s, this.y - s);
		}
		catch(NullPointerException e){
			//nothing to do
		}
	}
	public void mouseClicked(MouseEvent e) {
		// TODO Auto-generated method stub.

	}

	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub.

	}

	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub.

	}

	public void mousePressed(MouseEvent e) {
		if(e.getButton()==e.BUTTON3){
			if(this.getBrush() instanceof PolygonTool){
				((PolygonTool)this.getBrush()).polyFinish();
				this.setBrush(new PolygonTool());
			}
			
			if(this.getBrush() instanceof PolygonFillTool){
				((PolygonFillTool)this.getBrush()).polyFinish();
				this.setBrush(new PolygonFillTool());
				
			}
		}
		else{
			
			this.b.Paint(this,new Float(e.getX()*this.currentscale).intValue(),new Float(e.getY()*this.currentscale).intValue());
		}
		repaint();
		this.a.repaint();
	}

	public void mouseReleased(MouseEvent e) {
		if(this.b instanceof Tool){
			((Tool)this.b).finish();
		}
		int w = this.I.getWidth();
		int h = this.I.getHeight();
		BufferedImage temp = new BufferedImage(w,h,BufferedImage.TYPE_INT_ARGB);
		for(int i=0;i<w;i++){
			for(int j=0;j<h;j++){
				temp.setRGB(i,j,this.I.getRGB(i,j));
			}
		}
		this.undo.add(temp);
		this.first=true;
		if(this.undo.size()>10){
			this.undo.remove(0);
		}
	}
	public void mouseDragged(MouseEvent e) {
		this.x=e.getX();
		this.y=e.getY();
		this.b.Paint(this,new Float(e.getX()*this.currentscale).intValue(),new Float(e.getY()*this.currentscale).intValue());
		repaint();
		this.a.repaint();
		
	}
	public void mouseMoved(MouseEvent e) {
		this.x=e.getX();
		this.y=e.getY();
		repaint();
		this.a.repaint();
	}
	/**
	 * 
	 * gets the current Paint object
	 *
	 * @return the Paint this canvas references
	 */
	public Paint getPaint(){
		return this.a;
	}
	/**
	 * 
	 * gets the Current Image from the Canvas
	 *
	 * @return the current Image
	 */
	public BufferedImage getImage(){
		return this.I;
	}
	/**
	 * 
	 * Sets the Canvas' image to the given image
	 *
	 * @param b the Image to be used
	 */
	public void setImage(BufferedImage b){
		this.I=b;
	}
	/**
	 * 
	 * TODO sets the current brush to b
	 *
	 * @param b
	 */
	public void setBrush(Brush b){
		this.b = b;
	}
	/**
	 * allows a brush to be grabbed
	 *
	 * @return the selected brush
	 */
	public Brush getBrush(){
		return this.b;
	}
	/**
	 * 
	 * TODO return whether or not the pixel is selected
	 *
	 * @param x
	 * @param y
	 * @return selected?
	 */
	public boolean isSelected(int x,int y){
		try{
		return this.selected[x][y];
		}
		catch(ArrayIndexOutOfBoundsException e){
			return false;
		}
		
		
	}
	/**
	 * This selects the pixel at coordinates at (x,y)
	 *
	 * @param x X-Coordinate
	 * @param y Y-Coordinate
	 */
	public void select(int x,int y){
		try{
			this.selected[x][y]=true;
		}
		catch(ArrayIndexOutOfBoundsException e){
			//
		}
	}
	/**
	 * This deselects the pixel at coordinates at (x,y)
	 * 
	 * @param x X-Coordinate
	 * @param y	Y-Coordinate
	 */
	public void deselect(int x,int y){
		try{
			this.selected[x][y]=false;
		}
		catch(ArrayIndexOutOfBoundsException e){
			//
		}
	}
	/**
	 * 
	 * this sets the shape that drawn on top of the image but not on the canvas
	 * i.e. Preview of drawn shape
	 *
	 * @param s the shape for the preview
	 */
	public void setShape(Shape s){
		this.s=s;
	}
	/**
	 * This is an accessor for the preview shape
	 *
	 * @return the shape
	 */
	public Shape getShape(){
		return this.s;
	}
	public void setForward(){
		try {
		this.I=this.undo.get(this.undo.indexOf(this.I)+1);
		}
		catch(IndexOutOfBoundsException e){
			//
		}
	}
	/**
	 * 
	 * returns the canvas' image to one state previous
	 * 
	 *
	 */
	public void setBack(){
		System.out.println(this.undo.size());
		
		try {
			this.I = this.undo.get(this.undo.indexOf(this.I) - 1);
			if (this.first) {
				while (this.undo.size() - 2 != this.undo.indexOf(this.I)) {
					this.undo.remove(this.undo.indexOf(this.I) + 1);
				}
			}
		} catch (ArrayIndexOutOfBoundsException e) {
			//
		}
	}
	public void setZoom(float scale){
		this.zoom.setToScale(scale,scale);
		this.currentscale=scale;
		this.setPreferredSize(new Dimension(Integer.parseInt(this.getPreferredSize().width*scale+""),Integer.parseInt(this.getPreferredSize().height*scale+"")));
	}
}
