import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
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.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JPanel;


/**
 * @author Jon Klein, CM2296
 * Drawing panel class, manages the area to scribble upon, and all of the mouse listeners of the drawing panel.
 */

public class DrawingPanel extends JPanel implements MouseListener,
		MouseMotionListener
{
	private static final Color TEMPCOLOR = Color.red;
	private SidePanel sidep;
	private BottomPanel bottomp;
	private int brush_width = 1;
	// temp X and Y, used for keeping intermediate shapes without drawing them
	private double tempx;
	private double tempy;
	private boolean shiftflag = false;
	private boolean polybool = false;
	private String fontface = "TimesRoman";
	private int fontstyle = Font.PLAIN;
	private int fontsize = 12;
	
	/**
	 * ArrayList for storing drawings.
	 */
	ArrayList<doodle> drawn_objects = new ArrayList<doodle>();
	ArrayList<JComponent> non_fillable = new ArrayList<JComponent>();
	protected boolean fillshapes = false;
	private BufferedImage background_image;

	/**
	 * Gives the drawing panel an idea of the sidep and bottomp
	 * @param sidep A side panel with paint tool buttons.
	 * @param bottomp A bottom panel with color buttons and text fields.
	 */
	public void setPanels(SidePanel sidep, BottomPanel bottomp)
	{
		this.sidep = sidep;
		this.bottomp = bottomp;
	}
	
	/**
	 * Drawing panel constructor, creates drawing area to scribble on.
	 */
	public DrawingPanel()
	{
		this.setBackground(Color.white);
		this.addMouseMotionListener(this);
		this.addMouseListener(this);
	}
	
	/**
	 * Adds the given shape to the drawn_components.
	 * @param s The shape to add.
	 */
	public void addShape(doodle s)
	{
		this.drawn_objects.add(s);
		this.repaint();
		
	}
	
	/**
	 * Sets a background image to the screen.
	 * @param image The image to load (should be 600x600 or nasty things might happen...)
	 */
	public void setBackgroundImage(BufferedImage image)
	{
		this.background_image = image;
		this.clearAll();
	}
	
	@Override
	public void paintComponent(Graphics g)
	{
		super.paintComponent(g);
		Graphics2D g2 = (Graphics2D) g;
		
		if (this.getBackground_image() != null)
		{
			g2.drawImage(this.getBackground_image(), 0, 0, null);
		}
		
		for (doodle shape : drawn_objects)
		{
			if (shape.text)
			{
				g2.setFont(new Font(shape.getFace(), shape.getStyle(),  shape.getSize()));
				g2.setColor(shape.color);
				g2.drawString(shape.getMessage(), shape.x, shape.y);
				continue;
			}
			g2.setStroke(new BasicStroke(shape.size));
			if (shape.filled)
			{
				g2.setColor(shape.fillcolor);
				g2.fill(shape.shape);
			}
			
			g2.setColor(shape.color);
			g2.draw(shape.shape);
		}
		
		
		
	}
	
	@Override
	public void mouseClicked(MouseEvent arg0)
	{

		this.repaint();
	}

	@Override
	public void mouseEntered(MouseEvent arg0)
	{
		requestFocusInWindow();
	}

	@Override
	public void mouseExited(MouseEvent arg0)
	{
		this.bottomp.setMouseLocation(-1, -1);
	}

	@Override
	public void mousePressed(MouseEvent arg0)
	{
		switch (this.sidep.getSelected().buttonid) 
		{
        	case 0:  
        		// pencil
        		Shape s = new Ellipse2D.Double(arg0.getX(), arg0.getY(), 1, 1);
        		this.drawn_objects.add(new doodle(s,
        				 this.bottomp.getSelected().getColor(), 1));
        		this.repaint();
        		break;
        		
        	case 1:
        		Shape s1 = new Ellipse2D.Double(arg0.getX(), arg0.getY(), 10, 10);
        		this.drawn_objects.add(new doodle(s1,
        				this.bottomp.getFillColor(), 10));
        		this.repaint();
        		break;
        		
        	case 2:
        		// rectangle
        		this.tempx = arg0.getX();
        		this.tempy = arg0.getY();
        		Shape r = new Rectangle2D.Double(arg0.getX(), arg0.getY(), arg0.getX(), arg0.getY());
        		this.drawn_objects.add(new doodle(r, TEMPCOLOR, this.brush_width, this.bottomp.getFillColor(), fillshapes));
        		break;
        	
        	case 3:
        		// ellipse
        		this.tempx = arg0.getX();
        		this.tempy = arg0.getY();
        		Shape e = new Ellipse2D.Double(arg0.getX(), arg0.getY(), arg0.getX(), arg0.getY());
        		this.drawn_objects.add(new doodle(e, TEMPCOLOR, this.brush_width, this.bottomp.getFillColor(), fillshapes));
        		break;
        		
        	case 4:
        		// polygon
        		if (!this.polybool) 
        		{
        			this.polybool = !this.polybool;
        			this.tempx = arg0.getX();
            		this.tempy = arg0.getY();
            		Shape line = new Line2D.Double (this.tempx, this.tempy, this.tempx, this.tempy);
            		this.drawn_objects.add(new doodle(line, this.bottomp.getSelected().getColor(), this.brush_width));
        		}
        		
        		else if (arg0.getButton() == MouseEvent.BUTTON3 || arg0.getButton() == MouseEvent.BUTTON2)
        		{
        			Shape line = new Line2D.Double(
        					((Line2D.Double)this.drawn_objects.get(this.drawn_objects.size()-1).shape).x2,
        					((Line2D.Double)this.drawn_objects.get(this.drawn_objects.size()-1).shape).y2,
        					this.tempx, this.tempy);
        			this.drawn_objects.add(new doodle(line, this.bottomp.getSelected().getColor(), this.brush_width));
        			this.polybool = false;
        		}
        		
        		else 
        		{
        			Shape line = new Line2D.Double(
        					((Line2D.Double)this.drawn_objects.get(this.drawn_objects.size()-1).shape).x2,
        					((Line2D.Double)this.drawn_objects.get(this.drawn_objects.size()-1).shape).y2,
        					arg0.getX(), arg0.getY());
        			this.drawn_objects.add(new doodle(line, this.bottomp.getSelected().getColor(), this.brush_width));
        		}
        		
        		break;
        	
        	case 5:
        		// spray paint
        		break;
        		
        	case 6:
        		
        		// line
        		Shape l = new Line2D.Double(arg0.getX(), arg0.getY(), 1, 1);
        		this.drawn_objects.add(new doodle(l, TEMPCOLOR, this.brush_width));
        		break;
        	case 7:
        		String text = JOptionPane.showInputDialog("enter text");
        		this.drawn_objects.add(new doodle(text, this.bottomp.getSelected().getColor(), arg0.getX(), arg0.getY(),this.fontstyle, this.fontsize, this.fontface));
        		this.repaint();
        		break;
        		
        		
        	default: 
        		break;
		}

	}

	@Override
	public void mouseReleased(MouseEvent arg0)
	{
		double width;
		double height;
		switch (this.sidep.getSelected().buttonid) 
		{
		
			case 2:
				// rectangle
				// rectangle and ellipse code was written while under the influence of caffine
				// luckly, it later got broken up into more than two lines
        		width = this.shiftflag ? 
        				(Math.abs(this.tempx-arg0.getX()) < Math.abs(this.tempy-arg0.getY()) ? this.tempx-arg0.getX() : Math.abs(this.tempy-arg0.getY())) 
        				: Math.abs(this.tempx-arg0.getX());
        				
        		height = this.shiftflag ? width : Math.abs(this.tempy-arg0.getY());
        		
				((Rectangle2D.Double) this.drawn_objects.get
						(this.drawn_objects.size()-1).shape).setRect(
								(!this.shiftflag || this.tempx < arg0.getX()) ? this.tempx < arg0.getX() ? this.tempx : arg0.getX() : this.tempx - width,
								(!this.shiftflag || this.tempy < arg0.getY()) ? this.tempy < arg0.getY() ? this.tempy : arg0.getY() : this.tempy - height, 
								width, height);
				this.drawn_objects.get(this.drawn_objects.size()-1).setColor(this.bottomp.getSelected().getColor());
				
				break;
			
			case 3:
				// ellipse
				width = this.shiftflag ? 
        				(Math.abs(this.tempx-arg0.getX()) < Math.abs(this.tempy-arg0.getY()) ? this.tempx-arg0.getX() : Math.abs(this.tempy-arg0.getY())) 
        				: Math.abs(this.tempx-arg0.getX());
        				
        		height = this.shiftflag ? width : Math.abs(this.tempy-arg0.getY());
        		
        		((Ellipse2D.Double) this.drawn_objects.get
						(this.drawn_objects.size()-1).shape).setFrame(
								(!this.shiftflag || this.tempx < arg0.getX()) ? this.tempx < arg0.getX() ? this.tempx : arg0.getX() : this.tempx - width,
								(!this.shiftflag || this.tempy < arg0.getY()) ? this.tempy < arg0.getY() ? this.tempy : arg0.getY() : this.tempy - height, 
								width, height);
				this.drawn_objects.get(this.drawn_objects.size()-1).setColor(this.bottomp.getSelected().getColor());
				break;
				
				
			
			case 6:
				// line
				((Line2D.Double)this.drawn_objects.get(this.drawn_objects.size()-1).shape).x2 = arg0.getX();
        		((Line2D.Double)this.drawn_objects.get(this.drawn_objects.size()-1).shape).y2 = arg0.getY();
        		this.drawn_objects.get(this.drawn_objects.size()-1).setColor(this.bottomp.getSelected().getColor());
        		break;
				
			default:
				break;
		}
		
		this.repaint();

	}

	@Override
	public void mouseDragged(MouseEvent arg0)
	{
		double width;
		double height;
		
		switch (this.sidep.getSelected().buttonid) 
		{
        	case 0:  
        		Shape e = new Ellipse2D.Double(arg0.getX(), arg0.getY(), .5, .5);
        		this.drawn_objects.add(new doodle(e, this.bottomp.getSelected().getColor(), this.brush_width));
        		
        		
        		if(this.drawn_objects.size() > 4 && this.drawn_objects.get(this.drawn_objects.size()-3).shape instanceof Ellipse2D.Double)
        		{
        					Shape l = new Line2D.Double(
        							((Ellipse2D.Double)this.drawn_objects.get(this.drawn_objects.size() - 3).shape).getCenterX(),
        							((Ellipse2D.Double)this.drawn_objects.get(this.drawn_objects.size() - 3).shape).getCenterY(),
        							arg0.getX(), arg0.getY());
        					this.drawn_objects.add(new doodle(l, this.bottomp.getSelected().getColor(), this.brush_width));
        		}
        		break;
        		
        	case 1:
        		Shape e1 = new Ellipse2D.Double(arg0.getX(), arg0.getY(), 10, 10);
        		this.drawn_objects.add(new doodle(e1, this.bottomp.getFillColor(), 10));
        		
        		
        		if(this.drawn_objects.size() > 4 && this.drawn_objects.get(this.drawn_objects.size()-3).shape instanceof Ellipse2D.Double)
        		{
        					Shape l = new Line2D.Double(
        							((Ellipse2D.Double)this.drawn_objects.get(this.drawn_objects.size() - 3).shape).getCenterX(),
        							((Ellipse2D.Double)this.drawn_objects.get(this.drawn_objects.size() - 3).shape).getCenterY(),
        							arg0.getX(), arg0.getY());
        					this.drawn_objects.add(new doodle(l, this.bottomp.getFillColor(), 15));
        		}
        		break;
        		
        	case 2:
        		// rectangle
        		width = this.shiftflag ? 
        				(Math.abs(this.tempx-arg0.getX()) < Math.abs(this.tempy-arg0.getY()) ? this.tempx-arg0.getX() : Math.abs(this.tempy-arg0.getY())) 
        				: Math.abs(this.tempx-arg0.getX());
        				
        		height = this.shiftflag ? width : Math.abs(this.tempy-arg0.getY());
        		
        		
				((Rectangle2D.Double) this.drawn_objects.get
						(this.drawn_objects.size()-1).shape).setRect(
								(!this.shiftflag || this.tempx < arg0.getX()) ? this.tempx < arg0.getX() ? this.tempx : arg0.getX() : this.tempx - width,
								(!this.shiftflag || this.tempy < arg0.getY()) ? this.tempy < arg0.getY() ? this.tempy : arg0.getY() : this.tempy - height, 
								width, height);
        		break;
        	
        	case 3:
        		// ellipse
        		
        		width = this.shiftflag ? 
        				(Math.abs(this.tempx-arg0.getX()) < Math.abs(this.tempy-arg0.getY()) ? this.tempx-arg0.getX() : Math.abs(this.tempy-arg0.getY())) 
        				: Math.abs(this.tempx-arg0.getX());
        				
        		height = this.shiftflag ? width : Math.abs(this.tempy-arg0.getY());
        		
        		((Ellipse2D.Double) this.drawn_objects.get
						(this.drawn_objects.size()-1).shape).setFrame(
								(!this.shiftflag || this.tempx < arg0.getX()) ? this.tempx < arg0.getX() ? this.tempx : arg0.getX() : this.tempx - width,
								(!this.shiftflag || this.tempy < arg0.getY()) ? this.tempy < arg0.getY() ? this.tempy : arg0.getY() : this.tempy - height, 
								width, height);
        		break;
        		
        	case 4:
        		// polygon
        		((Line2D.Double)this.drawn_objects.get(this.drawn_objects.size()-1).shape).x2 = arg0.getX();
        		((Line2D.Double)this.drawn_objects.get(this.drawn_objects.size()-1).shape).y2 = arg0.getY();
        		break;
        	
        	case 5:
        		Shape sp = new Ellipse2D.Double(arg0.getX(), arg0.getY(), 1, 1);
        		this.drawn_objects.add(new doodle(sp,
        				 this.bottomp.getSelected().getColor(), 7));
        		this.repaint();
        		break;
        		
        	case 6:
        		// line
        		((Line2D.Double)this.drawn_objects.get(this.drawn_objects.size()-1).shape).x2 = arg0.getX();
        		((Line2D.Double)this.drawn_objects.get(this.drawn_objects.size()-1).shape).y2 = arg0.getY();
        		break;
        		
        	default: 
        		break;
		}
		this.bottomp.setMouseLocation(arg0.getX(), arg0.getY());
		this.repaint();
	}

	/**
	 *  Removes the last drawn doodle, removes nothing if no doodles are drawn.
	 */
	public void undo()
	{
		if (this.drawn_objects.size()>=1)
		{
			this.drawn_objects.remove(this.drawn_objects.size()-1);
			this.repaint();
		}
	}

	@Override
	public void mouseMoved(MouseEvent arg0)
	{
		this.bottomp.setMouseLocation(arg0.getX(), arg0.getY());
	}

	/**
	 * @param i The width of the outlines, in pixels.
	 */
	public void setBrushWidth(int i)
	{
		this.brush_width  = i;
		
	}

	/**
	 *  Removes all drawn doodles, then redraws.
	 */
	public void clearAll()
	{
		this.drawn_objects = new ArrayList<doodle>();
		this.repaint();
		
	}
	
	/**
	 * Sets the drawing panels idea if shift is pressed or not.
	 * @param value Is shift pressed? If yes, set to true.
	 */
	public void setShift(boolean value)
	{
		this.shiftflag = value;
	}


	/**
	 * @return The background image of the drawing panel.
	 */
	public BufferedImage getBackground_image()
	{
		return background_image;
	}
	
	/**
	 * @param font String representation of font style.
	 */
	public void setFontFace(String font)
	{
		this.fontface = font;
	}
	
	/**
	 * @param style set bold or italic here, use Font.[style].
	 */
	public void setFontStyle(int style)
	{
		this.fontstyle = style;
	}
	
	/**
	 * @param fontsize 
	 * 	sets font size of drawn text doodles.
	 */
	public void setFontSize(int fontsize)
	{
		this.fontsize = fontsize;
	}
	
	
	
	
}
