import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

public class MyAmoeba extends BufferingFrame implements MouseListener, MouseMotionListener {

	// main method, starts up
	public static void main(String[] args) {
		new MyAmoeba();
	}

	// data 
	protected Point2D[] mPoints;          // the control points
	protected int mSelectedIndex;        // the one selected

	// Use arrays to figure out who a control point's 
	// partner and slaves are. If mSelectedIndex = i,
	// then it's partner is partner[i] and it's slave
	// is slave[i]. Notice that this is a lot easier than
	// computing who the partner and slave is! But less
	// flexible (like if you wanted to add more curves to 
	// the amoeba). 
	int[] slave = {11,2,-1,2,5,-1,5,8,-1,8,11,-1};
	int[] partner = {10,3,-1,1,6,-1,4,9,-1,7,0,-1};


	// share the shapes 
	private GeneralPath blob;
	private Ellipse2D nucleus;

	// Constructor
	public MyAmoeba() {
		super("MyAmoeba");    // ApplicationWindow constructor, name of window
		setSize(500, 400);
		center();           // center on screen

		initShapes();       // set up shapes

		// Listen for mouse events.
		addMouseListener(this);     // Hey AWT! Tell me about mouse clicks!
		addMouseMotionListener(this);   // And about motions!

		setVisible(true);           // Display the Window
	}

	void initShapes() {
		// Set up initial positions of points
		mPoints = new Point2D[12];
		// Cubic curve - upper left
		mPoints[0] = new Point2D.Double(50, 100);
		mPoints[1] = new Point2D.Double(100, 50);
		mPoints[2] = new Point2D.Double(250, 50);
		// Cubic curve - upper right
		mPoints[3] = new Point2D.Double(400,50);
		mPoints[4] = new Point2D.Double(450,100);
		mPoints[5] = new Point2D.Double(450,200);
		// Cubic curve - lower right
		mPoints[6] = new Point2D.Double(450,300);
		mPoints[7] = new Point2D.Double(400,350);
		mPoints[8] = new Point2D.Double(250,350);
		// Cubic curve - lower left
		mPoints[9] = new Point2D.Double(100,350);
		mPoints[10] = new Point2D.Double(50,300);
		mPoints[11] = new Point2D.Double(50,200);

		// Use these to define the initial amoeba
		blob = makeAmoeba();

		// The nucleus of the cell
		nucleus = new Ellipse2D.Double(200,160,100,80);

		mSelectedIndex = -1;
	}

	// Override BufferingFrame's drawPic instead of paint
	public void paint(Graphics g) {
		Graphics2D g2 = (Graphics2D)g;

		// Anti-aliasing 
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);


		Color burntOrange = new Color(230,150,10);
		g2.setPaint(burntOrange);
		g2.fill(blob);
		g2.setStroke(new BasicStroke(5));
		g2.setPaint(Color.black);
		g2.draw(blob);

		GradientPaint paint = new GradientPaint(200,160,Color.pink,
				300,240,Color.yellow,
				false);
		g2.setPaint(paint);
		g2.fill(nucleus);
		g2.setPaint(Color.black);
		g2.draw(nucleus);

		// Draw the control polygon
		GeneralPath control = new 
		GeneralPath(GeneralPath.WIND_EVEN_ODD);
		// Each step in the path is a line segments
		control.moveTo((float)mPoints[11].getX(),(float)mPoints[11].getY());
		for (int i=0; i<12; i++) {
			control.lineTo((float)mPoints[i].getX(),(float)mPoints[i].getY());
		}
		Color seeThru = new Color(80,80,80,100);
		g2.setPaint(seeThru);
		Stroke cStroke = new BasicStroke(4,BasicStroke.CAP_BUTT,
				BasicStroke.JOIN_MITER);
		g2.setStroke(cStroke);
		g2.draw(control);

		// draw the control points
		for (int i = 0; i < mPoints.length; i++) {
			// If the control point is selected, color it red.
			if (i == mSelectedIndex)
				g2.setPaint(Color.red);
			else
				g2.setPaint(Color.blue);
			// Draw the point.
			g2.fill(getControlPoint(mPoints[i]));
		}

	}


	/* Now all the mouse handling code */

	// We'll need this in a minute...
	// notice it returns something that implements the Shape interface, 
	// we don't say what. 
	protected Shape getControlPoint(Point2D p) {
		// Create a small square around the given point.
		int side = 8;
		return new Rectangle2D.Double(
				p.getX() - side / 2, p.getY() - side / 2,
				side, side);
	}

	public void mousePressed(MouseEvent me) {
		mSelectedIndex = -1;
		for (int i = 0; i < mPoints.length; i++) {
			if ((i!=2) && (i!=5) && (i!=8) && (i!=11)) {
				Shape s = getControlPoint(mPoints[i]);
				// me.getPoint() returns the position of the cursor. 
				// s.contains returns true if that point is in Shape s,
				// which is one of the little rectangles around the 
				// control points.
				if (s.contains(me.getPoint())) {
					mSelectedIndex = i;
					break;
				}
			}
		}
		repaint();
	}

	// makes the Amoeba from the control points
	GeneralPath makeAmoeba() 
	{
		// first, redefine the amoeba
		GeneralPath tempBlob = new GeneralPath(GeneralPath.WIND_EVEN_ODD);

		// Start the path at the last point in the list
		tempBlob.moveTo((float)mPoints[11].getX(),(float)mPoints[11].getY());

		// Sadly, the methods of GeneralPath expect float arguements.
		// Each step in the path is a curve, starting at the last point 
		// given, and defined by that point and the three passed as 
		// arguments.
		for (int i=0; i<4; i++) {
			tempBlob.curveTo((float)mPoints[3*i].getX(),(float)mPoints[3*i].getY(),
					(float)mPoints[3*i+1].getX(),(float)mPoints[3*i+1].getY(),
					(float)mPoints[3*i+2].getX(),(float)mPoints[3*i+2].getY());
		}
		return tempBlob;
	}

	// changes the position of the slave point
	void placeSlave(int m,int p,int s) {
		double x = (mPoints[m].getX() + mPoints[p].getX())/2.0;
		double y = (mPoints[m].getY() + mPoints[p].getY())/2.0;
		mPoints[s].setLocation(x,y);
	}


	public void mouseDragged(MouseEvent me) {

		if (mSelectedIndex != -1) {
			Point2D oldPoint = 
				(Point2D)mPoints[mSelectedIndex].clone();
			mPoints[mSelectedIndex].setLocation(me.getPoint());

			int m = mSelectedIndex;
			int p = partner[m];
			int s = slave[m];

			placeSlave(m,p,s);

			GeneralPath tempBlob = makeAmoeba();

			// check for collision - 
			// make both the amoeba and the nucleus into Areas
			// and see if the nucleus area is covered by the blob area 

			Area ablob = new Area(tempBlob);
			Area anucleus = new Area(nucleus);
			Area aintersect = new Area(nucleus);
			aintersect.intersect(ablob);
			if (anucleus.equals(aintersect)) {
				blob = tempBlob;
			} else {
				mPoints[mSelectedIndex].setLocation(oldPoint);
				placeSlave(m,p,s);
			}
			repaint();
		}
	}

	// methods of MouseListener and MouseMotionListener that we
	// have to implement, but shouldn't do anything in this application.
	public void mouseClicked(MouseEvent me) {}
	public void mouseReleased(MouseEvent me) {}
	public void mouseMoved(MouseEvent me) {}
	public void mouseEntered(MouseEvent me) {}
	public void mouseExited(MouseEvent me) {}
}
