import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
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.awt.geom.RoundRectangle2D;

import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.util.ArrayList;

import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javax.swing.JMenuItem;
import javax.swing.JPanel;


/**
 * This is where most of the bulk of the paint program is handled.
 *
 * @author Jared Mathis.
 *         Created Mar 29, 2008.
 */
public class DrawSpace extends JPanel implements MouseListener, MouseMotionListener, ComponentListener, ActionListener {
	
	private static final int 
	PADDING = 5,
	MAX_UNDOS = 10;
	
	/**
	 * The BufferedImage of this drawSpace.  It's pixels can be manipulated individually.
	 */
	protected BufferedImage img;
	private Graphics2D g2img;
	private Paint paint;
	private Color currentColor;
	private Point previousPoint;
	private Point firstPoint;
	
	private ArrayList<Shape> 
	drawOverlay = new ArrayList<Shape>(0),
	drawOverlay2 = new ArrayList<Shape>(0),
	drawFinal = new ArrayList<Shape>(0),
	
	fillOverlay = new ArrayList<Shape>(0),
	fillFinal = new ArrayList<Shape>(0);  
	
	private ArrayList<BufferedImage> 
	undos = new ArrayList<BufferedImage>(0),
	redos = new ArrayList<BufferedImage>(0);

	
	/**
	 * This is protected so that ToolPanel can turn off the current tool if the user changes tools before finishing the current one.
	 */
	protected boolean isUsingATool = false;
	private boolean hasNotBeenInit = true;
	private MouseEvent firstClick;
	

	/**
	 * Creates a manipulatable drawSpace.
	 * 
	 * @param background Default color for all the pixels of the image
	 * @param paint The Paint object to which this drawSpace belongs.
	 */
	public DrawSpace(Color background, Paint paint) {
		this.paint = paint;
		int width = paint.window.getBounds().width;
		int height = width = paint.window.getBounds().height;
		
		this.updatePreferredSize(width, height);
		
		this.img = new BufferedImage(
				(int)this.getPreferredSize().getWidth() - 2*PADDING, 
				(int)this.getPreferredSize().getHeight() - 2*PADDING, 
				BufferedImage.TYPE_INT_ARGB);
		this.g2img = this.img.createGraphics();
		
		
		this.setColor(background);
		this.setBackground(Color.GRAY);
		this.setLayout(new FlowLayout());
	}
	
	/**
	 * Used by the Paint of this drawspace when it's done setting up everything.
	 * 
	 * @param value True or False
	 */
	protected void setInitiated(boolean value) { this.hasNotBeenInit = !value; }
	
	/**
	 * @param c A color
	 * @return A new int[] the Color in RBGA form.
	 */
	protected static int[] getColorArray(Color c) {
		return new int [] {
			c.getRed(), 
    		c.getGreen(), 
    		c.getBlue(),
    		c.getAlpha()};
	}
	
	private void setColor(Color c) {
		WritableRaster raster = this.img.getRaster();
		int[] color = getColorArray(c);
		
		for (int i = 0; i < this.img.getWidth(); i++) {
			for (int j = 0; j < this.img.getHeight(); j++) {
				raster.setPixel(i, j, color);
			}
		}
	}
	
	@Override
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		
		this.g2img.setColor(this.currentColor);
		
		for (Shape shape : this.drawFinal) this.g2img.draw(shape);
		for (Shape shape : this.fillFinal) this.g2img.fill(shape);
		
		Graphics2D g2 = (Graphics2D)g;
		g2.drawImage(this.img, PADDING, PADDING, null);
		
		g2.setClip(PADDING, PADDING, this.img.getWidth(), this.img.getHeight());
		g2.setColor(this.currentColor);
		for (Shape shape : this.drawOverlay) g2.draw(shape);
		for (Shape shape : this.drawOverlay2) g2.draw(shape);
		for (Shape shape : this.fillOverlay) g2.fill(shape);
		
		this.clearDrawFinal();
		this.clearFillFinal();

// Used for debugging
//		System.out.println(
//				"drawfinal " + this.drawFinal.size() + 
//				" drawoverlay " + this.drawOverlay.size() + 
//				" drawoverlay2 " + this.drawOverlay2.size() + 
//				"fillfinal " + this.fillFinal.size() + 
//				" filloverlay " + this.fillOverlay.size());
		
	}
	
	private Color getCurrentColorFrom(MouseEvent e) {
		int button = e.getButton();
		if (button == MouseEvent.BUTTON1) return this.paint.primaryColor;
		if (button == MouseEvent.BUTTON3) return this.paint.secondaryColor;
		return null;
	}
	
	private void setPixel(Point point, Color primaryColor) {
		this.setPixel(point, getColorArray(primaryColor));
	}
	
	private void setPixel(Point point, int[] color) {
		WritableRaster raster = this.img.getRaster();     
        raster.setPixel(point.x - PADDING, point.y - PADDING, color); 
        this.repaint();
	}
	
	private static Point movePoint(Point p, int dx, int dy) {
		return new Point(p.x + dx, p.y + dy);
	}
	
	private void drawLine(Point p1, Point p2, ArrayList<Shape> arr, int size) {
		arr.add(new Line2D.Double(p1, p2));
		
		if (size > ToolOptions.SIZE_1) {
			arr.add(new Line2D.Double(movePoint(p1, 1, 0), movePoint(p2, 1, 0)));
			arr.add(new Line2D.Double(movePoint(p1, 1, 1), movePoint(p2, 1, 1)));
			arr.add(new Line2D.Double(movePoint(p1, 0, 1), movePoint(p2, 0, 1)));
			
			if (size > ToolOptions.SIZE_2) {
				arr.add(new Line2D.Double(movePoint(p1, -1, -1), movePoint(p2, -1, -1)));
				arr.add(new Line2D.Double(movePoint(p1, -1, 1), movePoint(p2, -1, 1)));
				arr.add(new Line2D.Double(movePoint(p1, 1, -1), movePoint(p2, 1, -1)));
				arr.add(new Line2D.Double(movePoint(p1, -1, 0), movePoint(p2, -1, 0)));
				arr.add(new Line2D.Double(movePoint(p1, 0, -1), movePoint(p2, 0, -1)));
				
				if (size > ToolOptions.SIZE_3) {
					arr.add(new Line2D.Double(movePoint(p1, -1, 2), movePoint(p2, -1, 2)));
					arr.add(new Line2D.Double(movePoint(p1, 0, 2), movePoint(p2, 0, 2)));
					arr.add(new Line2D.Double(movePoint(p1, 1, 2), movePoint(p2, 1, 2)));
					
					arr.add(new Line2D.Double(movePoint(p1, -1, -2), movePoint(p2, -1, -2)));
					arr.add(new Line2D.Double(movePoint(p1, 0, -2), movePoint(p2, 0, -2)));
					arr.add(new Line2D.Double(movePoint(p1, 1, -2), movePoint(p2, 1, -2)));
					
					arr.add(new Line2D.Double(movePoint(p1, 2, -1), movePoint(p2, 2, -1)));
					arr.add(new Line2D.Double(movePoint(p1, 2, 0), movePoint(p2, 2, 0)));
					arr.add(new Line2D.Double(movePoint(p1, 2, 1), movePoint(p2, 2, 1)));
					
					arr.add(new Line2D.Double(movePoint(p1, -2, -1), movePoint(p2, -2, -1)));
					arr.add(new Line2D.Double(movePoint(p1, -2, 0), movePoint(p2, -2, 0)));
					arr.add(new Line2D.Double(movePoint(p1, -2, 1), movePoint(p2, -2, 1)));
					
				}
			}
		}
		
		this.repaint();
	}
	
	private void drawRectangle(Point p1, Point p2, ArrayList<Shape> arr) {
		arr.add(new Rectangle2D.Double(
				Math.min(p1.x, p2.x), 
				Math.min(p1.y, p2.y), 
				Math.abs(p1.x - p2.x), 
				Math.abs(p1.y - p2.y)
				));
		this.repaint();
	}
	
	private void drawRoundRectangle(Point p1, Point p2, ArrayList<Shape> arr) {
		arr.add(new RoundRectangle2D.Double(
				Math.min(p1.x, p2.x),
				Math.min(p1.y, p2.y),
				Math.abs(p1.x - p2.x),
				Math.abs(p1.y - p2.y),
				20,
				20
				));
		this.repaint();
	}
	
	private void drawOval(Point p1, Point p2, ArrayList<Shape> arr) {
		arr.add(new Ellipse2D.Double(
				Math.min(p1.x, p2.x),
				Math.min(p1.y, p2.y),
				Math.abs(p1.x - p2.x),
				Math.abs(p1.y - p2.y)
				));
		this.repaint();
	}
	
	private void copyDrawOverlayToFinal() {
		this.drawFinal = this.drawOverlay;
		this.moveDrawFinal();
	}
	
	private void copyDrawOverlay2ToFinal() {
		this.drawFinal = this.drawOverlay2;
		this.moveDrawFinal();
	}
	
	private void copyFillOverlayToFinal() {
		this.fillFinal = this.fillOverlay;
		this.moveFillFinal();
	}
	
	private void updateUndos() {
		
		this.paint.menu.undo.setEnabled(true);
		
		BufferedImage currentImageCopy = new BufferedImage(this.img.getWidth(), this.img.getHeight(), BufferedImage.TYPE_INT_ARGB);
		currentImageCopy.setData(this.img.getData());
		this.undos.add(currentImageCopy);
		
		int size = this.undos.size();

		if (size > MAX_UNDOS) this.undos.remove(0);

		this.redos = new ArrayList<BufferedImage>(0);
		this.paint.menu.redo.setEnabled(false);

	}
	
	private void moveDrawFinal() {		
		this.moveFinal(PADDING, PADDING, this.drawFinal);
	}
	
	private void moveFillFinal() {		
		this.moveFinal(PADDING, PADDING, this.fillFinal);
	}
	
	private void moveFinal(int x, int y,  ArrayList<Shape> arr) {
		for (Shape shape : arr) {
			if (shape instanceof Ellipse2D.Double) {
				((Ellipse2D.Double)shape).x -= x;
				((Ellipse2D.Double)shape).y -= y;
			}
			else if (shape instanceof Line2D) {
				Line2D.Double line = (Line2D.Double)shape;
				line.x1 -= x;
				line.x2 -= x;
				line.y1 -= y;
				line.y2 -= y;
			}
			else if (shape instanceof Point) {
				((Point)shape).move(-x, -y);
			}
			else if (shape instanceof Rectangle2D) {
				((Rectangle2D.Double)shape).x -=x;
				((Rectangle2D.Double)shape).y -=y;
			}
			else if(shape instanceof RoundRectangle2D) {
				((RoundRectangle2D.Double)shape).x -=x;
				((RoundRectangle2D.Double)shape).y -=y;
			}
			else if(shape instanceof Ellipse2D) {
				((Ellipse2D.Double)shape).x -=x;
				((Ellipse2D.Double)shape).y -=y;
			}
		}
	}
	
	private static boolean theseColorsAreEqual(int[] c1, int[] c2) {
		if (c1.length != 4 || c2.length != 4) return false;
		int i = 0;
		for (; i < 4; i++) {
			if (c1[i] != c2[i]) break;
		}
		if (i == 4) return true;
		return false;
	}

	
	
	
// This works for small figures; it just makes my Java run out of stack because it's recursive...
	private void fill(int x, int y, int[] clickedColor) {
		
		this.setPixel(new Point(x + PADDING, y + PADDING), this.currentColor);
	
		if (x > 0) {
			int[] west = this.img.getRaster().getPixel(x - 1, y, new int[4]);
			
			if (theseColorsAreEqual(clickedColor, west)) {
				fill(x - 1, y, west);
			}
		}
		if (y > 0) {
			int[] north = this.img.getRaster().getPixel(x, y - 1, new int[4]);
			
			if (theseColorsAreEqual(clickedColor, north)) {
				fill(x, y - 1, north);
			}
		}
		if (x < this.img.getRaster().getWidth() - 1) {
			int[] east = this.img.getRaster().getPixel(x + 1, y, new int[4]);
			
			if (theseColorsAreEqual(clickedColor, east)) {
				fill(x + 1, y, east);
			}
		}
		
		if (y < this.img.getRaster().getHeight() - 1) {
			int[] south = this.img.getRaster().getPixel(x, y + 1, new int[4]);
			
			if (theseColorsAreEqual(clickedColor, south)) {
				fill(x, y + 1, south);
			}
		}

	}
	
	/**
	 * This method is protected so that ToolPanel can clear the overlays when the user changes tools.
	 */
	protected void clearDrawOverlay() { this.drawOverlay = new ArrayList<Shape>(0); }
	
	/**
	 * This method is protected so that ToolPanel can clear the overlays when the user changes tools.
	 */
	protected void clearDrawOverlay2() { this.drawOverlay2 = new ArrayList<Shape>(0); }
	
	/**
	 * This method is protected so that ToolPanel can clear the overlays when the user changes tools.
	 */
	protected void clearFillOverlay() { this.fillOverlay = new ArrayList<Shape>(0); }
	
	private void clearDrawFinal() {	this.drawFinal = new ArrayList<Shape>(0); }

	private void clearFillFinal() {	this.fillFinal = new ArrayList<Shape>(0); }
	
	private void updatePreferredSize() {
		updatePreferredSize(
				this.paint.window.getBounds().width,
				this.paint.window.getBounds().height
				);
		this.repaint();
		
		}

	private void updatePreferredSize(int width, int height) {
		int x = width - ToolPanel.COLS * (ToolPanel.SPACING + ToolButton.BUTTON_SIZE) - 20;
		int y = height - ColorPanel.ROWS * (ColorPanel.SPACING + ColorSwatch.SWATCH_SIZE) - 60;

		if (this.hasNotBeenInit) this.setPreferredSize(new Dimension(x, y));
		else this.setPreferredSize(new Dimension(this.img.getWidth() + 2*PADDING, this.img.getHeight() + 2*PADDING ));
		
		this.paint.scroller.setPreferredSize(new Dimension(x + 3, y + 3));
	}
	
	private boolean containsPoint(Point p) {
		WritableRaster raster = this.img.getRaster();
		Rectangle r = raster.getBounds();
		r.x += PADDING;
		r.y += PADDING;
		if (r.contains(p) && this.contains(p)) return true;
		return false;
	}
	
	public void mouseClicked(MouseEvent e) {
		// End Sequences
		if (this.isUsingATool) {	
			this.isUsingATool = false;
			
			if (
					this.paint.currentTool == ToolPanel.DRAW_TOOL || 
					this.paint.currentTool == ToolPanel.LINE_TOOL || 
					this.paint.currentTool == ToolPanel.RECT_TOOL ||
					this.paint.currentTool == ToolPanel.ROUNDBOX_TOOL  ||
					this.paint.currentTool == ToolPanel.OVAL_TOOL ||
					this.paint.currentTool == ToolPanel.BRUSH_TOOL
					) {
				this.copyDrawOverlayToFinal();
				this.copyFillOverlayToFinal();
				this.updateUndos();
			}
			
			else if (this.paint.currentTool == ToolPanel.POLY_TOOL) {
				this.polyTool(e);
			}
			
			else if (this.paint.currentTool == ToolPanel.EYEDROP_TOOL) { 
				this.paint.currentTool = this.paint.previousTool;
				ToolButton.tools.get(this.paint.currentTool).select();
				this.isUsingATool = false;
			}
			
			this.clearDrawOverlay();
			this.clearFillOverlay();
			this.repaint();
		}
		
		// Start Sequences
		else {	
			this.currentColor = this.getCurrentColorFrom(e);
			this.firstClick = e;
			this.isUsingATool  = true;
			
			if ((this.paint.currentTool == ToolPanel.DRAW_TOOL || this.paint.currentTool == ToolPanel.BRUSH_TOOL) && this.containsPoint(e.getPoint())) {
				this.previousPoint = e.getPoint();

			}
			
			else if (
					this.paint.currentTool == ToolPanel.LINE_TOOL || 
					this.paint.currentTool == ToolPanel.POLY_TOOL ||
					this.paint.currentTool == ToolPanel.RECT_TOOL ||
					this.paint.currentTool == ToolPanel.OVAL_TOOL ||
					this.paint.currentTool == ToolPanel.ROUNDBOX_TOOL
					) {
				
				this.firstPoint = e.getPoint();
			}
			
			else if (this.paint.currentTool == ToolPanel.FILL_TOOL) {
				this.fillTool();
			}
			
			else if (this.paint.currentTool == ToolPanel.EYEDROP_TOOL && this.containsPoint(e.getPoint())){
				try{ this.eyedropTool(e.getPoint()); }
				catch(java.lang.ArrayIndexOutOfBoundsException error) {  /*empty*/  }
			}
			
			
			this.repaint();
		}
	}
	
	private void polyTool(MouseEvent e) {
		this.drawLine(this.firstPoint, e.getPoint(), this.drawOverlay2, this.paint.currentToolSize);
		
		if (e.getButton() == this.firstClick.getButton()) {
			this.isUsingATool = true;
			this.firstPoint = e.getPoint();
		}
		else {
			this.updateUndos();
			this.drawLine(this.firstClick.getPoint(), e.getPoint(), this.drawOverlay2, this.paint.currentToolSize);
			this.copyDrawOverlay2ToFinal();
			this.clearDrawOverlay2();
		}
		
	}

	private void fillTool() {
		int x = this.firstClick.getX() - PADDING;
		int y = this.firstClick.getY() - PADDING;
		
		try {
			this.updateUndos();
			this.fill(x, y, this.img.getRaster().getPixel(x, y, new int[4]));
		}
		catch(java.lang.StackOverflowError error) { 				
			System.out.println("Sorry, you need to increase the stack of your JVM.  Something like -Xss10m will work better."); 
		}
		
		this.isUsingATool = false;
		
	}

	private void eyedropTool(Point p) {
		this.currentColor = getColorFromArray(this.img.getRaster().getPixel(
				(int)(p.getX() - PADDING), 
				(int)(p.getY() - PADDING), 
				new int[4]));

		if (this.firstClick.getButton() == MouseEvent.BUTTON1) this.paint.primaryColor = this.currentColor;
		else if (this.firstClick.getButton() == MouseEvent.BUTTON3) this.paint.secondaryColor = this.currentColor;
		
		this.paint.toolPanel.viewer.repaint();
	}
	
	private static Color getColorFromArray(int[] c) {
		return new Color(c[0], c[1], c[2], c[3]);
	}

	public void mouseEntered(MouseEvent e) { /* empty */ }
	public void mouseExited(MouseEvent e) { /* empty */ }
	public void mousePressed(MouseEvent e) { /* empty */ }
	public void mouseReleased(MouseEvent e) { /* empty */ }
	public void mouseDragged(MouseEvent e) { System.out.println("Don't drag the mouse.  Keep the mouse steady, click once and then move it."); }
	
	public void mouseMoved(MouseEvent e) {
		if (this.isUsingATool) {
			Point point = e.getPoint();
			
			if (this.paint.currentTool == ToolPanel.DRAW_TOOL && this.containsPoint(point)) {
				this.drawLine(point, this.previousPoint, this.drawOverlay, ToolOptions.SIZE_1);
			}
			
			else if (this.paint.currentTool == ToolPanel.LINE_TOOL || this.paint.currentTool == ToolPanel.POLY_TOOL) {
				this.clearDrawOverlay();
				this.drawLine(point, this.firstPoint, this.drawOverlay, this.paint.currentToolSize);
			}
			
			else if (this.paint.currentTool == ToolPanel.RECT_TOOL) {
				this.rectTool(point);			
			}	
			
			else if (this.paint.currentTool == ToolPanel.ROUNDBOX_TOOL){
				this.roundboxTool(point);
			}
			
			else if (this.paint.currentTool == ToolPanel.OVAL_TOOL){
				this.ovalTool(point);
			}
			
			else if (this.paint.currentTool == ToolPanel.EYEDROP_TOOL && this.containsPoint(point)) {
				this.eyedropTool(point);
			}
			
			else if (this.paint.currentTool == ToolPanel.BRUSH_TOOL && this.containsPoint(point)) {
				this.drawLine(point, this.previousPoint, this.drawOverlay, this.paint.currentToolSize);
			}
			
			this.previousPoint = point;
		}
	}

	private void rectTool(Point point) {
		this.clearDrawOverlay();
		this.clearFillOverlay();
		if (this.paint.currentToolStyle == ToolOptions.STYLE_EDGE) {
			this.drawRectangle(point, this.firstPoint, this.drawOverlay); 
		}
		else if (this.paint.currentToolStyle == ToolOptions.STYLE_SOLID) {
			this.drawRectangle(point, this.firstPoint, this.fillOverlay); 
		}	
	}

	private void roundboxTool(Point point) {
		this.clearDrawOverlay();
		this.clearFillOverlay();
		if (this.paint.currentToolStyle == ToolOptions.STYLE_EDGE) {
			this.drawRoundRectangle(point, this.firstPoint, this.drawOverlay); 
		}
		else if (this.paint.currentToolStyle == ToolOptions.STYLE_SOLID) {
			this.drawRoundRectangle(point, this.firstPoint, this.fillOverlay); 
		}
	}

	private void ovalTool(Point point) {
		this.clearDrawOverlay();
		this.clearFillOverlay();
		if (this.paint.currentToolStyle == ToolOptions.STYLE_EDGE) {
			this.drawOval(point, this.firstPoint, this.drawOverlay); 
		}
		else if (this.paint.currentToolStyle == ToolOptions.STYLE_SOLID) {
			this.drawOval(point, this.firstPoint, this.fillOverlay); 
		}
	}

	public void componentHidden(ComponentEvent e) { /* empty */ }
	public void componentShown(ComponentEvent e) { /* empty */ }
	public void componentMoved(ComponentEvent e) { /* empty */ }

	public void componentResized(ComponentEvent e) {
		this.updatePreferredSize();
		this.paint.panel.revalidate(); 	// This "refreshes" the flow-layout it took me HOURS to find this method.
										// The worst part about the swing library is just the syntax and my non knowing
										// all of the method names and such.  
		
		
		//System.out.println(this.paint.window.getBounds());
		
	}
	
	private void invert() {
		this.updateUndos();
		
		WritableRaster raster = this.img.getRaster();
		
		for (int i = 0; i < this.img.getWidth(); i++) {
			for (int j = 0; j < this.img.getHeight(); j++) {
				int[] color = raster.getPixel(i, j, new int[4]);
				raster.setPixel(i, j, invert(color));
			}
		}
		
		this.repaint();
	}
	
	public void actionPerformed(ActionEvent arg0) {
		Object source = arg0.getSource();
		
		if (source instanceof JMenuItem) {
			JMenuItem item = (JMenuItem)source;
			
			String name = item.getText();
			
			if (name == "New") {				
				this.setColor(this.paint.secondaryColor);
				this.repaint();
				this.undos = new ArrayList<BufferedImage>(0);
				this.redos = new ArrayList<BufferedImage>(0);
				this.paint.menu.undo.setEnabled(false);
				this.paint.menu.redo.setEnabled(false);
			}
			
			else if (name == "Quit") {				
				System.exit(0);
			}
		
			
			else if (name == "Invert") {
				this.invert();
			}
			
			
			else if (name == "Save" && this.paint.fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
				try {
					
					ImageIO.write(this.img, "png", this.paint.fileChooser.getSelectedFile());
				} catch (IOException exception) { /* empty */ }
				
			}
			
			else if (name == "Open" && this.paint.fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {

				try {
					this.img = ImageIO.read(this.paint.fileChooser.getSelectedFile());
					this.g2img = this.img.createGraphics();
					this.repaint();
					
				} catch (IOException exception) { /* empty */ }
				
			}
			
			else if (name == "Undo") {
				this.undoLastAction();
			}
			
			else if (name == "Redo") {
				BufferedImage undo = new BufferedImage(this.img.getWidth(), this.img.getHeight(), BufferedImage.TYPE_INT_ARGB);
				undo.setData(this.img.getData());
				this.undos.add(undo);
				
				
				int n = this.redos.size() - 1;
				
				this.img = this.redos.remove(n);
				this.g2img = this.img.createGraphics();
				this.repaint();
			
				if (n == 0) this.paint.menu.redo.setEnabled(false);
				
				this.paint.menu.undo.setEnabled(true);
			}
		}
		
		return;
		
	}

	private void undoLastAction() {
		BufferedImage redo = new BufferedImage(this.img.getWidth(), this.img.getHeight(), BufferedImage.TYPE_INT_ARGB);
		redo.setData(this.img.getData());
		this.redos.add(redo);
		
		
		int n = this.undos.size() - 1;
		
		this.img = this.undos.remove(n);
		this.g2img = this.img.createGraphics();
		this.repaint();
		

		
		if (n == 0) this.paint.menu.undo.setEnabled(false);
		
		this.paint.menu.redo.setEnabled(true);
		
	}

	private static int[] invert(int[] color) {
		if (color.length != 4) return null;
		for(int i = 0; i<3; i++) {
			color[i] = 255 - color[i];
		}
		return color;
	}

	/**
	 * Used by ImageResizer class to set the size of the drawable space of this DrawSpace.
	 *
	 * @param x New width
	 * @param y New height
	 */
	protected void setImageSize(int x, int y) {
		if (!this.hasNotBeenInit) this.updateUndos();
		
		WritableRaster raster = this.img.getRaster();
		this.img = new BufferedImage(x, y, BufferedImage.TYPE_INT_ARGB);
		WritableRaster raster2 = this.img.getRaster();
		this.g2img = this.img.createGraphics();
		this.setColor(this.paint.secondaryColor);
		
		for(int i = 0; i < x && i < raster.getWidth(); i++) {
			for(int j = 0; j < y && j < raster.getHeight(); j++) {
				raster2.setPixel(i, j, raster.getPixel(i, j, new int[4]) );
			}
		}
		
		this.updatePreferredSize();
		this.revalidate();

		this.repaint();
		
	}
}

