/*
 *   Copyright (C) 2001 Bryce Allen
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
 
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.*;
import javax.swing.*;

/**
 * Class to allow user to draw basic shapes and transform them.
 *
 * @version 1.0 2001-1-28
 * @author Bryce Allen
 */
public class DrawPanel2D extends JPanel implements MouseListener, MouseMotionListener
{	
	private Vector shapes = new Vector();
	private Shape shape;
	
	private Shape tempShape = null;	// used to store shape as it is being drawn
	private Point2D startPoint = null;
	private Point2D currentPoint = null;
	
	private AffineTransform trans; // cache transformations independent of device
	private AffineTransform normalTrans; // default trans and logical2device
	private AffineTransform logical2device;
	
	private BufferedImage image;
	private Graphics2D imageGraphics;
	private Color shapeColor = Color.gray;
	private int lastRotate = 0;
	private Color transparent = new Color(255, 255, 255, 0);
	private BasicStroke dashedStroke;

	public static int DRAW_MODE_VECTOR = 1;
	public static int DRAW_MODE_RECT = 2;
	public static int DRAW_MODE_LINE = 3;
	private int drawMode = DRAW_MODE_VECTOR;

	private boolean drawAxes = true;
	private boolean drawGrid = true;
	private boolean drawCoordinates = true;
	private int gridCellWidth = 10;
	private boolean snapToGrid = true;
	private boolean cartesianCoordinates = false;

	public DrawPanel2D()
	{
		trans = new AffineTransform();
		
		float[] dashPattern = { 10F, 5F };
		dashedStroke = new BasicStroke(2, BasicStroke.CAP_SQUARE,
									   BasicStroke.JOIN_MITER, 10,
									   dashPattern, 0);
		
		addMouseListener(this);
		addMouseMotionListener(this);
	}

	public void paintComponent(Graphics g)
	{
		Dimension size = getSize();
		int halfWidth = size.width / 2;
		int halfHeight = size.height / 2;
		if(image == null)
		{
			initImage();
		}
		setBackground(Color.white);
		super.paintComponent(g);
		Graphics2D g2 = (Graphics2D)g;

		if(drawGrid)
		{
			g2.setPaint(new Color(230, 230, 230));
			for(int i=0; i < halfWidth; i += gridCellWidth)
			{
				// verticle line
				g2.drawLine(halfWidth + i, 0, halfWidth + i, size.height);
				g2.drawLine(halfWidth - i, 0, halfWidth - i, size.height);
			}
			for(int i=0; i < halfHeight; i += gridCellWidth)
			{
				// horizontal line
				g2.drawLine(0, halfHeight + i, size.width, halfHeight + i);
				g2.drawLine(0, halfHeight - i, size.width, halfHeight - i);
			}
		}

		if(drawAxes)
		{
			g2.setPaint(Color.black);
			g2.drawLine(0, halfHeight, size.width, halfHeight); 
			g2.drawLine(halfWidth, 0, halfWidth, size.height); 
		}

		g2.drawImage(image, null, 0, 0);
		
		if(tempShape != null)
		{
			if(drawCoordinates)
			{
				String coords = "(" + currentPoint.getX() + "," + currentPoint.getY() + ")"; 
				FontMetrics fm = g2.getFontMetrics();
				int stringWidth = fm.stringWidth(coords);
				int stringHeight = fm.getHeight();
				g2.drawString(coords, size.width - stringWidth - 5, size.height - stringHeight);
			}
			g2.setPaint(Color.gray);
			g2.setStroke(dashedStroke);
			g2.transform(logical2device);
			g2.draw(tempShape);
		}
	}

	private void drawShapes(Graphics2D g)
	{
		for(int i=0; i < shapes.size(); ++i)
		{
			g.draw((Shape)shapes.get(i));
		}
	}

	private void initImage()
	{
		image = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB_PRE);
		imageGraphics = image.createGraphics();
		
		imageGraphics.transform(logical2device);
		
		normalTrans = imageGraphics.getTransform();
		
		imageGraphics.setStroke(new BasicStroke(2));
		imageGraphics.setPaint(shapeColor);
		
		drawShapes(imageGraphics);
	}

	private void rotateShapeColor()
	{
		int[] color = new int[3];
		color[0] = (shapeColor.getRed() + 23) % 256;
		color[1] = (shapeColor.getGreen() + 5) % 256;
		color[2] = (shapeColor.getBlue() + 18) % 256;

		shapeColor = new Color(color[0], color[1], color[2]);
		++lastRotate;
		if(lastRotate > 2)
		{
			lastRotate = 0;
		}
		imageGraphics.setPaint(shapeColor);
	}

	private void initLogical2Device()
	{
		logical2device = new AffineTransform();
		logical2device.translate(getWidth()/2, getHeight()/2);
		if(cartesianCoordinates)
		{
			logical2device.concatenate(new AffineTransform(1.0, 0.0, 0.0, -1.0, 0.0, 0.0));
		}
	
		initImage();
	}

	public void setBounds(int x, int y, int width, int height)
	{
		super.setBounds(x, y, width, height);
		
		initLogical2Device();
	}
	
	public void rotate(double theta, double x, double y)
	{
		imageGraphics.rotate(Math.toRadians(theta), x, y);
		trans.rotate(Math.toRadians(theta), x, y);
		rotateShapeColor();
		drawShapes(imageGraphics);
		repaint();
	}

	public void translate(double tx, double ty)
	{
		imageGraphics.translate(tx, ty);
		trans.translate(tx, ty);
		rotateShapeColor();
		drawShapes(imageGraphics);
		repaint();
	}

	public void scale(double sx, double sy)
	{
		imageGraphics.scale(sx, sy);
		trans.scale(sx, sy);
		rotateShapeColor();
		drawShapes(imageGraphics);
		repaint();
	}

	public void shear(double shx, double shy)
	{
		imageGraphics.shear(shx, shy);
		trans.shear(shx, shy);
		rotateShapeColor();
		drawShapes(imageGraphics);
		repaint();
	}

	public void reflectAcrossX()
	{
		imageGraphics.transform(new AffineTransform(1.0, 0.0, 0.0, -1.0, 0.0, 0.0));
		trans.concatenate(new AffineTransform(1.0, 0.0, 0.0, -1.0, 0.0, 0.0));
		rotateShapeColor();
		drawShapes(imageGraphics);
		repaint();
	}
	
	public void reflectAcrossY()
	{
		imageGraphics.transform(new AffineTransform(-1.0, 0.0, 0.0, 1.0, 0.0, 0.0));
		trans.concatenate(new AffineTransform(-1.0, 0.0, 0.0, 1.0, 0.0, 0.0));
		rotateShapeColor();
		drawShapes(imageGraphics);
		repaint();
	}
	
	public void resetTransform()
	{
		imageGraphics.setTransform(normalTrans);
		trans.setToIdentity();
		clearScreenImage();
		drawShapes(imageGraphics);
		repaint();
	}

	public void clearShapes()
	{
		shapes.removeAllElements();
		clearScreenImage();
		repaint();
	}

	public void clearAll()
	{
		shapes.removeAllElements();
		imageGraphics.setTransform(normalTrans);
		trans.setToIdentity();
		clearScreenImage();
		repaint();
	}

	public void setDrawMode(int mode) { drawMode = mode; } 
	public int getDrawMode() { return drawMode; }

	public void setShowAxes(boolean val) { drawAxes = val; repaint(); } 
	public boolean getShowAxes() { return drawAxes; }
	
	public void setShowGrid(boolean val) { drawGrid = val; repaint(); } 
	public boolean getShowGrid() { return drawGrid; }
	
	public void setShowCoordinates(boolean val) { drawCoordinates = val; repaint(); } 
	public boolean getShowCoordinates() { return drawCoordinates; }
	
	public void setSnapToGrid(boolean val) { snapToGrid = val; } 
	public boolean getSnapToGrid() { return snapToGrid; }
	
	public void setCartesianCoordinates(boolean val)
	{
		cartesianCoordinates = val;
		initLogical2Device();
		repaint();
	} 
	public boolean getCartesianCoordinates() { return cartesianCoordinates; }
	
	private void clearScreenImage()
	{
		int halfWidth = getWidth() / 2;
		int halfHeight = getHeight() / 2;
		Color tempColor = imageGraphics.getColor();
		Composite tempComposite = imageGraphics.getComposite();
		imageGraphics.setPaint(transparent);
		imageGraphics.setComposite(AlphaComposite.Src);
		imageGraphics.fillRect(-halfWidth, -halfHeight, getWidth(), getHeight());
		drawShapes(imageGraphics);
		imageGraphics.setPaint(tempColor);
		imageGraphics.setComposite(tempComposite);
	}

	public double[] getMatrix()
	{
		double[] buffer = new double[6];

		trans.getMatrix(buffer);
		
		return buffer;		
	}

	private void snapToGrid(Point2D p)
	{
		long iNumXCells = Math.round(p.getX() / gridCellWidth);
		long iNumYCells = Math.round(p.getY() / gridCellWidth);

		p.setLocation(iNumXCells*gridCellWidth, iNumYCells*gridCellWidth);
	}

	private Point2D device2logical(int x, int y)
	{
		Point2D p = new Point2D.Double(x, y);
		try
		{
			logical2device.inverseTransform(p, p);
		} catch(NoninvertibleTransformException e) {
			e.printStackTrace();
		}
		return p;
	}

	private Rectangle2D getRect2D(Point2D p1, Point2D p2)
	{
		double x = p1.getX();
		double y = p1.getY();
		double width = p2.getX() - x;
		double height = p2.getY() - y;
		if(width < 0)
		{
			// they went left
			x = p2.getX();
			width = -width;
		}
		if(height < 0)
		{
			// they went down
			y = p2.getY();
			height = -height;
		}
		return new Rectangle2D.Double(x, y, width, height);
	}

	public void mouseClicked(MouseEvent e)
	{
		currentPoint = device2logical(e.getX(), e.getY());
		
		if(snapToGrid)
		{
			snapToGrid(currentPoint);
		}
	
		if(drawMode == DRAW_MODE_VECTOR)
		{
			shapes.add(new Line2D.Double(0, 0, currentPoint.getX(), currentPoint.getY()));
			drawShapes(imageGraphics);
			repaint();
		}
		else if(drawMode == DRAW_MODE_LINE)
		{
			if(startPoint != null)
			{
				shapes.add(new Line2D.Double(startPoint, currentPoint));
				startPoint = null;
				tempShape = null;
				drawShapes(imageGraphics);
				repaint();
			}
			else
			{
				startPoint = currentPoint;
			}
		}
		else if(drawMode == DRAW_MODE_RECT)
		{
			if(startPoint != null)
			{
				shapes.add(getRect2D(startPoint, currentPoint));
				startPoint = null;
				tempShape = null;
				drawShapes(imageGraphics);
				repaint();
			}
			else
			{
				startPoint = currentPoint;
			}
		}
	}

	public void mouseEntered(MouseEvent e) {}

	public void mouseExited(MouseEvent e)
	{
		if(tempShape != null)
		{
			tempShape = null;
			repaint();
		}
	}

	public void mousePressed(MouseEvent e) {}

	public void mouseReleased(MouseEvent e) {}

	public void mouseDragged(MouseEvent e) {}

	public void mouseMoved(MouseEvent e)
	{
		currentPoint = device2logical(e.getX(), e.getY());
		
		if(snapToGrid)
		{
			snapToGrid(currentPoint);
		}

		if(drawMode == DRAW_MODE_VECTOR)
		{
			tempShape = new Line2D.Double(0, 0, currentPoint.getX(), currentPoint.getY());
			repaint();
		}
		else if(drawMode == DRAW_MODE_LINE && startPoint != null)
		{
			tempShape = new Line2D.Double(startPoint, currentPoint);
			repaint();
		}
		else if(drawMode == DRAW_MODE_RECT && startPoint != null)
		{
			tempShape = getRect2D(startPoint, currentPoint);
			repaint();
		}

	}
}

