/* features:

-pen mode
-erase mode
-box mode
-rectangle mode
-ring mode
-circle mode
-line

-colour change
-clear
-save??
-undo
-real-time viewing of box/circle draw

canvas class:
-stores image in buffer
-contains listener
-draws
-draws partially made drawings
-sets undo when change
-contains undo method
-contains clear method
-save method??

paint program class:
-contains drawing variables
-buttons to edit variables
-colour changer
*/

// Java core packages
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;

// Java extension packages
import javax.swing.*;
import javax.swing.colorchooser.*;

public class Painter extends JFrame
{
	static final int	ERASE=0,
						PEN=1,
						LINE=2,
						BOX=3,
						RECT=4,
						RING=5,
						OVAL=6;

	int mode=PEN, size=5;

	Container cForm;
	JPanel pOptions;
	JColorChooser ccChooser;
	JButton bClear, bUndo, bRedo, bEraser, bPen, bBrush, bLine, bBox, bRect, bRing, bOval;
	JLabel lSpacer;

	JMenuBar mBar;
	JMenu mMenu;
	JMenuItem mLoad, mSave, mExit;

	CCanvas canvas;


	public Painter()
	{
		super( "Adam's Painter" );
		canvas = new CCanvas(350, 350);

/*<<<<<<<<<<<<<<<<<<< Begin Menu Section >>>>>>>>>>>>>>>>>>>>>>*/
		mBar = new JMenuBar();
		setJMenuBar( mBar );

		mMenu = new JMenu("File");
		mMenu.setMnemonic( 'F' );

		mLoad = new JMenuItem("Load");	mSave = new JMenuItem("Save");	mExit = new JMenuItem("Exit");
		mLoad.setMnemonic('L');			mSave.setMnemonic('S');			mExit.setMnemonic('x');

/*||||||||||||||||||| Load Menu Item |||||||||||||||||||||*/
		mLoad.addActionListener(
			new ActionListener()
			{
				public void actionPerformed(ActionEvent event)
				{
					//Add code here
				}
			}
		);

/*||||||||||||||||||| Save Menu Item |||||||||||||||||||||*/
		mSave.addActionListener(
			new ActionListener()
			{
				public void actionPerformed(ActionEvent event)
				{
					//Add code here
				}
			}
		);

/*||||||||||||||||||| Exit Menu Item |||||||||||||||||||||*/
		mExit.addActionListener(
			new ActionListener()
			{
				public void actionPerformed(ActionEvent event)
				{
					System.exit(0);
				}
			}
		);

		mMenu.add(mLoad);		mMenu.add(mSave);		mMenu.add(mExit);
		mBar.add(mMenu);
/*<<<<<<<<<<<<<<<<<<< End Menu Section >>>>>>>>>>>>>>>>>>>>>>*/

/*<<<<<<<<<<<<<<<<<<< Start Options Section >>>>>>>>>>>>>>>>>>>>>>*/

/*||||||||||||||||||| Undo Button |||||||||||||||||||||*/
		bUndo = new JButton("Undo");
		bUndo.addActionListener(
			new ActionListener()
			{
				public void actionPerformed(ActionEvent event)
				{
					boolean result;
					result = canvas.undo();
					if(!result) JOptionPane.showMessageDialog(null,"Cannot undo!");
				}
			}
		);

/*||||||||||||||||||| Undo Button |||||||||||||||||||||*/
		bRedo = new JButton("Redo");
		bRedo.addActionListener(
			new ActionListener()
			{
				public void actionPerformed(ActionEvent event)
				{
					boolean result;
					result = canvas.redo();
					if(!result) JOptionPane.showMessageDialog(null,"Cannot redo!");
				}
			}
		);

/*||||||||||||||||||| Clear Button |||||||||||||||||||||*/
		bClear = new JButton("Clear");
		bClear.addActionListener(
			new ActionListener()
			{
				public void actionPerformed(ActionEvent event)
				{
					canvas.clear();
				}
			}
		);

/*||||||||||||||||||| Pen Button |||||||||||||||||||||*/
		bPen = new JButton("Pen Draw Mode");
		bPen.addActionListener(
			new ActionListener()
			{
				public void actionPerformed(ActionEvent event)
				{
					mode=PEN;
				}
			}
		);

/*||||||||||||||||||| Eraser Button |||||||||||||||||||||*/
		bEraser = new JButton("Eraser Mode");
		bEraser.addActionListener(
			new ActionListener()
			{
				public void actionPerformed(ActionEvent event)
				{
					mode=ERASE;
				}
			}
		);

/*||||||||||||||||||| Change Brush Size Button |||||||||||||||||||||*/
		bBrush = new JButton("Change Brush Size");
		bBrush.addActionListener(
			new ActionListener()
			{
				public void actionPerformed(ActionEvent event)
				{
					boolean valid;
					String input;
					
					valid = false;
					while(!valid)
					{
						try
						{
							input = JOptionPane.showInputDialog(null,"Please enter a new brush size:  ");
							size = Integer.parseInt(input);
							valid = true;
						}
						catch(Exception e)
						{
							valid = false;
						}
					}
				}
			}
		);
		
/*||||||||||||||||||| Line Button |||||||||||||||||||||*/
		bLine = new JButton("Line Draw Mode");
		bLine.addActionListener(
			new ActionListener()
			{
				public void actionPerformed(ActionEvent event)
				{
					mode=LINE;
				}
			}
		);

/*||||||||||||||||||| Box Button |||||||||||||||||||||*/
		bBox = new JButton("Box Draw Mode");
		bBox.addActionListener(
			new ActionListener()
			{
				public void actionPerformed(ActionEvent event)
				{
					mode=BOX;
				}
			}
		);

/*||||||||||||||||||| Rectangle Button |||||||||||||||||||||*/
		bRect = new JButton("Rectangle Draw Mode");
		bRect.addActionListener(
			new ActionListener()
			{
				public void actionPerformed(ActionEvent event)
				{
					mode=RECT;
				}
			}
		);

/*||||||||||||||||||| Ring Button |||||||||||||||||||||*/
		bRing = new JButton("Ring Draw Mode");
		bRing.addActionListener(
			new ActionListener()
			{
				public void actionPerformed(ActionEvent event)
				{
					mode=RING;
				}
			}
		);

/*||||||||||||||||||| Oval Button |||||||||||||||||||||*/
		bOval = new JButton("Circle Draw Mode");
		bOval.addActionListener(
			new ActionListener()
			{
				public void actionPerformed(ActionEvent event)
				{
					mode=OVAL;
				}
			}
		);

/*||||||||||||||||||| Color Chooser |||||||||||||||||||||*/
		ccChooser = new JColorChooser();
/*<<<<<<<<<<<<<<<<<<< End Options Section >>>>>>>>>>>>>>>>>>>>>>*/

/*<<<<<<<<<<<<<<<<<<< Start Setup Section >>>>>>>>>>>>>>>>>>>>>>*/
		lSpacer = new JLabel("Color Selector");

		pOptions = new JPanel();
		pOptions.setLayout( new GridLayout(12,1) );

		pOptions.add(bUndo);
		pOptions.add(bRedo);
		pOptions.add(bClear);
		pOptions.add(bPen);
		pOptions.add(bEraser);
		pOptions.add(bBrush);
		pOptions.add(bLine);
		pOptions.add(bBox);
		pOptions.add(bRect);
		pOptions.add(bRing);
		pOptions.add(bOval);
		pOptions.add(lSpacer);

		cForm = getContentPane();

		cForm.add(pOptions, BorderLayout.WEST );
		cForm.add(canvas, BorderLayout.CENTER );
		cForm.add(ccChooser, BorderLayout.SOUTH );


		setSize( 1200, 1000 );
		setVisible( true );
/*<<<<<<<<<<<<<<<<<<< End Setup Section >>>>>>>>>>>>>>>>>>>>>>*/
	}

	//canvas class
	class CCanvas extends JComponent implements MouseListener, MouseMotionListener
	{
		boolean bDrawStarted;
		int xSize=0, ySize=0, x1=0, y1=0, x2=0, y2=0;
		Color cTemp;

		BufferedImage bPicture, bTemp, bUndo[], bRedo[];
		Graphics gPicture, gTemp, gUndo[], gRedo[];

		int Undos, Redos;



		//constructor
		public CCanvas(int xSize, int ySize)
		{
			this.xSize = xSize;
			this.ySize = ySize;
			bPicture	= new BufferedImage(xSize, ySize, BufferedImage.TYPE_3BYTE_BGR );
			bTemp	= new BufferedImage(xSize, ySize, BufferedImage.TYPE_3BYTE_BGR );

			gPicture = bPicture.createGraphics();
			gTemp = bTemp.createGraphics();
			
			gUndo = new Graphics[5];
			bUndo = new BufferedImage[5];
			gRedo = new Graphics[5];
			bRedo = new BufferedImage[5];
			
			for(int i = 0; i != 5; i++)
			{
				bUndo[i] = new BufferedImage(xSize, ySize, BufferedImage.TYPE_3BYTE_BGR );
				gUndo[i] = bUndo[i].createGraphics();
				
				bRedo[i] = new BufferedImage(xSize, ySize, BufferedImage.TYPE_3BYTE_BGR );
				gRedo[i] = bRedo[i].createGraphics();
			}
			
			Undos=0;	Redos=0;

			this.clear();
			this.setDrawColor(Color.black);
			bDrawStarted = false;
			
			addMouseListener( this );
			addMouseMotionListener( this );
		}

		//set color
		public void setDrawColor()
		{
			cTemp = ccChooser.getColor();
			setDrawColor(cTemp);
		}
		
		public void setDrawColor(Color color)
		{
			gPicture.setColor(color);
			gTemp.setColor(color);
		}

		//blank image
		public void clear()
		{
			backup();

			gPicture.setColor( Color.white);
			gPicture.fillRect(0,0,xSize,ySize);
			gPicture.setColor(cTemp);
			
			repaint();
		}

		private void backup()
		{
			//draw current image to unfo buffer
			int i;
			if(Undos<5) Undos++;
			
			for(i = Undos; i!=0; i--);
			{
				gUndo[i].drawImage(bUndo[i-1], 0, 0, Color.white, this);
			}
			gUndo[0].drawImage(bPicture, 0, 0, Color.white, this);
			
			Redos=0;
		}
		
		private void rebackup()
		{
			//draw current image to unfo buffer
			int i;
			if(Redos<5) Redos++;
						
			for(i = Redos; i!=0; i--);
			{
				gRedo[i].drawImage(bRedo[i-1], 0, 0, Color.white, this);
			}
			gRedo[0].drawImage(bPicture, 0, 0, Color.white, this);
		}

		public boolean undo()
		{
			int i;
			//if it can undo, do so
			if(Undos>0)
			{
				rebackup();
				gPicture.drawImage(bUndo[0], 0, 0, Color.white, this);
				
				for(i=0; i!=Undos; i++);
				{
					gUndo[i].drawImage(bUndo[i+1], 0, 0, Color.white, this);
				}				
				
				Undos--;
				repaint();
				return true;
			}
			return false;
		}
		
		public boolean redo()
		{
			int i;
			//if it can undo, do so
			if(Redos>0)
			{
				backup();
				gPicture.drawImage(bRedo[0], 0, 0, Color.white, this);
				
				for(i=0; i!=Redos; i++);
				{
					gRedo[i].drawImage(bRedo[i+1], 0, 0, Color.white, this);
				}				
				
				Redos--;
				repaint();
				return true;
			}
			return false;
		}

		//draw alias
		private void draw(int x1, int y1)
		{
			draw(x1,y1,gPicture);
		}

		private void draw(int x1, int y1, Graphics target)
		{
			//prepare undo
			backup();
			setDrawColor();

			//draw right 2-integer mode thingy
			switch(mode)
			{
				case PEN:
					target.fillOval(x1-size, y1-size, size*2, size*2);
					break;
				case ERASE:
					target.setColor(Color.white);
					target.fillRect(x1-size, y1-size, size*2, size*2);
					target.setColor(cTemp);
					break;
				default:
			}
			repaint();
		}

		//draw alias
		private void draw(int x1, int y1, int x2, int y2)
		{
			if(mode<2) draw(x2, y2);
			else draw(x1,y1,x2,y2,gPicture);
		}
		
		private void draw(int x1, int y1, int x2, int y2, Graphics target)
		{
			draw(x1, y1, x2, y2, target, null);	
		}
		private void draw(int x1, int y1, int x2, int y2, Graphics target, Color color)
		{
			//prepare undo
			backup();
			if(color==null) setDrawColor();
			else setDrawColor(color);
			
			int temp;
			
			if(mode>2)
			{
				if(x1 > x2)
				{
					temp=x1;
					x1=x2;
					x2=temp;
				}
				if(y1 > y2)
				{
					temp=y1;
					y1=y2;
					y2=temp;
				}
			}

			//draw right stuff
			switch(mode)
			{
				case LINE:
					target.drawLine(x1, y1, x2, y2);
					break;
				case BOX:
					target.drawRect(x1, y1, x2-x1, y2-y1);
					break;
				case RECT:
					target.fillRect(x1, y1, x2-x1, y2-y1);
					break;
				case RING:
					target.drawOval(x1, y1, x2-x1, y2-y1);
					break;
				case OVAL:
					target.fillOval(x1, y1, x2-x1, y2-y1);
					break;
				default:
			}
			
			if(target == gPicture) repaint();
		}


		public void paint( Graphics g )
		{
			//Draw the permanent buffer to the temp buffer
			gTemp.drawImage(bPicture, 0, 0, Color.white, this);

			//Draw any in-progress drag-draws to the temp buffer
			if(mode > 1 && bDrawStarted) 
			{
				gTemp.setColor(cTemp);
				draw(x1, y1, x2, y2, gTemp);
			}			
			else if(mode<2) 
			{
				int temp = mode;
				mode = LINE*temp + RECT;
				draw(x2-size, y2-size, x2+size, y2+size, gTemp, Color.white);
				
				mode = LINE*temp + BOX;
				draw(x2-size, y2-size, x2+size, y2+size, gTemp);
				
				mode = temp;
			}

			//Draw temp buffer to screen
			g.drawImage(bTemp, 0, 0, Color.white, this);
		}

		//MouseListener event handlers

		//handle event when mouse released immediately after press
		public void mouseClicked( MouseEvent event )
		{
			//draw(event.getX(), event.getY(), event.getX(), event.getY());
		}

		//handle event when mouse pressed
		public void mousePressed( MouseEvent event )
		{
			bDrawStarted = true;
			if(mode < 2) draw(event.getX(), event.getY());
			else
			{
				//Start temp buffer draw info
				//bDrawStarted = true;
				x1 = event.getX();
				y1 = event.getY();
				x2 = event.getX();
				y2 = event.getY();
				repaint();
			}
		}

		//handle event when mouse released after dragging
		public void mouseReleased( MouseEvent event )
		{
			//Temp draw complete, draw to permanent buffer
			x2 = event.getX();
			y2 = event.getY();
			bDrawStarted = false;
			draw(x1, y1, x2, y2);
		}

		//handle event when mouse enters area
		public void mouseEntered( MouseEvent event )
		{
		}

		//handle event when mouse exits area
		public void mouseExited( MouseEvent event )
		{
		}

		// MouseMotionListener event handlers

		// handle event when user drags mouse with button pressed
		public void mouseDragged( MouseEvent event )
		{
			x2 = event.getX();
			y2 = event.getY();
			//draw if in pen or erase mode
			if(mode<2) draw(event.getX(), event.getY());
			//Update info for temp buffer draws
			else
			{
				repaint();
			}
		}

		// handle event when user moves mouse
		public void mouseMoved( MouseEvent event )
		{
			if(mode<2)
			{
				x2 = event.getX();
				y2 = event.getY();
				repaint();
			}
			
			//Update info for temp buffer draws
			else if(bDrawStarted)
			{
				x2 = event.getX();
				y2 = event.getY();
				repaint();
			}
		}
	}
/*||||||||||||||||||||||||||||||| END CANVAS ||||||||||||||||||||||||||||||||||||| */

/*||||||||||||||||||||||||||||||| MAIN |||||||||||||||||||||||||||||||||||||||||||||| */
	// execute application
	public static void main( String args[] )
	{
		Painter application = new Painter();

		application.setDefaultCloseOperation(
			JFrame.EXIT_ON_CLOSE );
	}
}
