import java.awt.*;
import java.util.*;
import java.lang.Math;

public class Bezier extends BufferedApplet
{
	private final int MAX_POINTS = 1000;
	
	private final int MODE_IDLE = 0;
	private final int MODE_DRAG = 1;
	private final int MODE_DOWN = 2;
	
	private final int RADIUS_POINT = 8;
	private final int RADIUS_POINT_CONTROL = 16;
	
	private final double EPSILON = 1.0 / 20.0;
	
	private int iActive = -1;
	private int iNumPoints = 0;
	private int iMode = MODE_IDLE;
	
	private int iPoints[][] = new int[MAX_POINTS][2];
	private int iMatrixBezier[][] = { {-1, 3, -3, 1}, {3, -6, 3, 0},
									{-3, 3, 0, 0}, {1, 0, 0, 0}};
	private int iRadius, iWidth, iHeight;
	private boolean bIsInited = false;
	
	private Color clrPoints = Color.gray;
	private Color clrLines = new Color(0,255,0, 64);
	private Color clrSplines = Color.magenta;
	
	public void render(Graphics g, double dTime)
		//will draw the budgers, it will be easy
	{
		if (bIsInited == false)
		{
			iWidth = bounds().width;
			iHeight = bounds().height;
			bIsInited = true;
		}
		
		g.setColor(Color.white);
		g.fillRect(0,0, iWidth, iHeight);
		
		if (iNumPoints > 0)
			//draw the bastards
		{
			if (iNumPoints > 1)
				//draws the lines
			{
				g.setColor(clrLines);
				int x1, y1, x2, y2;
				for(int i=0; i<iNumPoints-1; i++)
				{
					x1 = iPoints[i][0];
					y1 = iPoints[i][1];
					x2 = iPoints[i+1][0];
					y2 = iPoints[i+1][1];
										
					g.drawLine(x1, y1, x2, y2);
				}
			}
			
			if (iNumPoints > 3)
				//draw the splines
			{
				g.setColor(clrSplines);
				for (int i=0; i<iNumPoints; i+= 3)
					drawSplines(g, i);
			}
				
			
			for(int i=0; i<iNumPoints; i++)
				//draws the points
			{
				if (i == iActive)
					g.setColor(clrPoints.darker());
				else
					g.setColor(clrPoints.brighter());
				
				if (i == iActive)
					iRadius = (int)(3*Math.sin(dTime*6));
				else
					iRadius = 0;
				
				if (i%3 == 0)
					g.fillOval(iPoints[i][0]-RADIUS_POINT_CONTROL/2, iPoints[i][1]-RADIUS_POINT_CONTROL/2,
						 RADIUS_POINT_CONTROL+iRadius, RADIUS_POINT_CONTROL+iRadius);
				else
					g.fillOval(iPoints[i][0]-RADIUS_POINT/2, iPoints[i][1]-RADIUS_POINT/2, 
						RADIUS_POINT+iRadius, RADIUS_POINT+iRadius);
			}
			
		}
		
		//else, sit and scratch butt.  no really, im sure its itchy		
	}
	
	public void drawSplines(Graphics g, int i)
	{
		int iXcoords[] = new int[4];	
		int iYcoords[] = new int[4];
		
		if (i+3 < iNumPoints)
		{
			for (int j=0; j<4; j++)
			{
				iXcoords[j] = iPoints[i+j][0];
				iYcoords[j] = iPoints[i+j][1];
			}
			
			//transform it
			iXcoords = MMultiply(iXcoords);
			iYcoords = MMultiply(iYcoords);
			
			for (double t = 0; t < 1; t+= EPSILON)
				g.drawLine((int)X(t, iXcoords), (int)Y(t, iYcoords), 
					(int)X(t+EPSILON, iXcoords), (int)Y(t+EPSILON, iYcoords));
		}
	}
	
	public int[] MMultiply(int[] iXcoords)
	{
		int iTemp[] = new int[4];
		for (int i=0; i<4; i++)
			iTemp[i] = iXcoords[i];
			
		for (int j=0; j<4; j++)
		{
			iXcoords[j] = (	(iMatrixBezier[j][0] * iTemp[0]) +
							(iMatrixBezier[j][1] * iTemp[1]) +
							(iMatrixBezier[j][2] * iTemp[2]) +
							(iMatrixBezier[j][3] * iTemp[3]) );
		}		
		return iXcoords;	
	}
	
	public double X(double t, int[] iCoords)
	{
		return iCoords[0]*(t*t*t) + iCoords[1]*(t*t) + iCoords[2]*(t) + iCoords[3];
	}
	
	public double Y(double t, int[] iCoords)
	{
		return iCoords[0]*(t*t*t) + iCoords[1]*(t*t) + iCoords[2]*(t) + iCoords[3];
	}
	
	public boolean mouseMove(Event e, int x, int y)
	{
		iMode = MODE_IDLE;
		int dx, dy, r;
		for (int i=0; i<iNumPoints; i++)
		{
			dx = iPoints[i][0] - x;
			dy = iPoints[i][1] - y;
			r = (int)Math.sqrt(dx*dx + dy*dy);
				
			if (r < RADIUS_POINT_CONTROL/2)
				if (i%3 == 0)
					iActive = i;
			else if (r < RADIUS_POINT/2)
				iActive = i;	
		}
		return true;	
	}

	public boolean mouseUp(Event e, int x, int y)
	{
		if (iMode != MODE_DRAG)
		{
			//if iNumPoints mod 3 = 0, then calculate the weird
			if (((iNumPoints-1)%3 == 0) && (iNumPoints > 3))
			{
				double dTheta = Math.atan((double)(iPoints[iNumPoints-1][1] - iPoints[iNumPoints-2][1]) / 
										  (double)(iPoints[iNumPoints-1][0] - iPoints[iNumPoints-2][0]));
				int dx = x - iPoints[iNumPoints-1][0];
				int dy = y - iPoints[iNumPoints-1][1];
				
				iPoints[iNumPoints][0] = x;
				iPoints[iNumPoints][1] = (int)((dx * Math.tan(dTheta))+iPoints[iNumPoints-1][1]);
			}
			else 
			{
				iPoints[iNumPoints][0] = x;
				iPoints[iNumPoints][1] = y;
			}
			iNumPoints++;
		}
		return true;	
	}

	public boolean mouseDrag(Event e, int x, int y)
	{
		iMode = MODE_DRAG;
		if ((((iActive + 1)%3)==0) && (iActive+1 <= iNumPoints))
		{
			double dTheta = Math.atan((double)(iPoints[iActive+1][1] - iPoints[iActive][1]) / 
									  (double)(iPoints[iActive+1][0] - iPoints[iActive][0]));
			int dx = iPoints[iActive+2][0] - iPoints[iActive+1][0];
			int dy = iPoints[iActive+2][1] - iPoints[iActive+1][1];
			
			if ((iPoints[iActive+1][0] - iPoints[iActive][0]) > 0)
				dTheta -= Math.PI;

			double dLength = Math.sqrt(dx*dx + dy*dy);
			iPoints[iActive+2][0] = (int)((dLength * Math.sin(-dTheta-(Math.PI/2)))+iPoints[iActive+1][0]);
			iPoints[iActive+2][1] = (int)((dLength * Math.cos(-dTheta-(Math.PI/2)))+iPoints[iActive+1][1]);			
		}
		else if ((((iActive - 1)%3)==0) && ((iActive - 1) != 0))
		{
			double dTheta = Math.atan((double)(iPoints[iActive][1] - iPoints[iActive-1][1]) / 
									  (double)(iPoints[iActive][0] - iPoints[iActive-1][0]));
			int dx = iPoints[iActive-1][0] - iPoints[iActive-2][0];
			int dy = iPoints[iActive-1][1] - iPoints[iActive-2][1];
			
			if ((iPoints[iActive][0] - iPoints[iActive-1][0]) < 0)
				dTheta += Math.PI;
				
			double dLength = Math.sqrt(dx*dx + dy*dy);
			iPoints[iActive-2][0] = (int)((dLength * Math.sin(-dTheta-(Math.PI/2)))+iPoints[iActive-1][0]);
			iPoints[iActive-2][1] = (int)((dLength * Math.cos(-dTheta-(Math.PI/2)))+iPoints[iActive-1][1]);
		}
		else if (iActive%3 == 0)
		{
			if ((iActive != 0) && (iActive != iNumPoints-1))
			{
				int dx = x - iPoints[iActive][0];
				int dy = y - iPoints[iActive][1];
				
				iPoints[iActive-1][0] += dx;
				iPoints[iActive][0] += dx;
				iPoints[iActive+1][0] += dx;
				
				iPoints[iActive-1][1] += dy;
				iPoints[iActive][1] += dy;
				iPoints[iActive+1][1] += dy;
			}
		}

		iPoints[iActive][0] = x;
		iPoints[iActive][1] = y;

		return true;	
	}
}