import java.awt.*;
import java.util.*;

public class a7 extends BufferedApplet
{

    Font font = new Font("Helvetica", Font.PLAIN, 12);
    double G[] = {0,1,0,1}; // dummy vals to create raw splines
    Vector points = new Vector();    
    int h; int w;
    int selectedPoint = -1; // sentinel: what did we click
    int tolerance = 20; // this is how close the mouse has to be to select
    int ovalsize = 10; // how large are the ovals denoting control points
    double epsilon = 0.01; // how fine are we drawing the curves
    String name[] = {"BEZIER", "BSPLINE", "CATMULL-ROM", "HERMITE"};
    String onoff[] = {"off","on"};
    int type = 0;
    int drawControlPolygon = 1; // flags
    int drawControlPoints = 1;  // to turn off editing tools



    //Cubic xSpline;
    //Cubic ySpline;

    public void init(){
        super.init();
    }

    public void render(Graphics g) {
	w = bounds().width;
	h = bounds().height;

        if (damage) {
            g.setColor(Color.white);
            g.fillRect(0, 0, bounds().width, bounds().height);
	    //createSpline();
            double[] xvals = new double[4];
            double[] yvals = new double[4];
	    // the geometry values for whatever cubic spline
	    // we're currently drawing
	    Cubic xSpline;
	    Cubic ySpline;

            g.setColor(Color.blue);

            for (int i=0;
		 i < points.size() && points.size()-i >= 4;
		 i = i+3)
		// step through each curve by jumping groups of 3 points
		// from our list
		{                
                xvals[0] = ((double[])points.elementAt(i+0))[0];
                xvals[1] = ((double[])points.elementAt(i+1))[0];
                xvals[2] = ((double[])points.elementAt(i+2))[0];
                xvals[3] = ((double[])points.elementAt(i+3))[0];
                yvals[0] = ((double[])points.elementAt(i+0))[1];
                yvals[1] = ((double[])points.elementAt(i+1))[1];
                yvals[2] = ((double[])points.elementAt(i+2))[1];
                yvals[3] = ((double[])points.elementAt(i+3))[1];

		xSpline = new Cubic(Cubic.BEZIER, xvals);
		ySpline = new Cubic(Cubic.BEZIER, yvals);
		//switch(type) {
                //case 0:
		//    xSpline = new Cubic(Cubic.BEZIER, xvals);
		//	    ySpline = new Cubic(Cubic.BEZIER, yvals);
		//    break;
		//case 1:
		//    xSpline = new Cubic(Cubic.BSPLINE, xvals);
		//    ySpline = new Cubic(Cubic.BSPLINE, yvals);
		//    break;
                //case 2:
		//    xSpline = new Cubic(Cubic.CATMULL_ROM, xvals);
		//    ySpline = new Cubic(Cubic.CATMULL_ROM, yvals);
		//    break;
		//case 3:
		//	    xSpline = new Cubic(Cubic.HERMITE, xvals);
		//    ySpline = new Cubic(Cubic.HERMITE, yvals);
		//    break;
		//}
		//double epsilon = 0.02; // step
		// draw the curve as a series of straight segments
		// stepping by epsilon
                for (double t = 0 ; t < 1 ; t = t+epsilon)
                    g.drawLine((int)xSpline.eval(t), 
                               (int)ySpline.eval(t), 
                               (int)xSpline.eval(t+epsilon), 
                               (int)ySpline.eval(t+epsilon));
		}
            g.setColor(Color.red);
	    // draw the control polygon
	    if (drawControlPolygon == 1) {
		for(int i=1; i<points.size(); i++){                
		    double[] p = (double[])points.elementAt(i);
		    double[] q = (double[])points.elementAt(i-1);
		    g.drawLine((int)(p[0]),(int)(p[1]),
			       (int)(q[0]),(int)(q[1]));
		}
	    }

	    // loop through the list of points
	    // draw each of them, with different colors for interp points
	    
	    if (drawControlPoints == 1) {
		for(int i=0;i<points.size();i++){
		    double[] p = (double[])points.elementAt(i);
		    if (i % 3 == 0) g.setColor(Color.black);
		    if (i % 3 != 0) g.setColor(Color.blue);
		    g.fillOval((int)(p[0]- 0.5*ovalsize),
			       (int)(p[1]- 0.5*ovalsize),
			       ovalsize,ovalsize);
		}
		// draw the new point
		if(selectedPoint != -1){
		    //g.setColor(Color.black);
		    double[] p = (double[])points.elementAt(selectedPoint);
		    g.fillOval((int)(p[0]-0.5*ovalsize),
			       (int)(p[1]-0.5*ovalsize),
			       ovalsize,ovalsize);
		}
	    }

	    g.setColor(Color.black);
	    g.setFont(font);
	    g.drawString("Fineness: "+epsilon + "\n" +
			 " Polygon: "+ onoff[drawControlPolygon] +
			 " Points: "+ onoff[drawControlPoints] +
			 " click tolerance: " + tolerance +
			 " point size: " + ovalsize,
			 h/5, 5*h/6);
	}
    }

    public double distance(double x1, double y1,
			   double x2, double y2) {
	double dx = x1-x2; double dy = y1-y2;
	return Math.sqrt((dx*dx) + (dy*dy));
    }

    public int selectPoint(int x,int y) {
        for(int i = 0; i < points.size(); i++) {
            if(distance(((double[])points.elementAt(i))[0],
			((double[])points.elementAt(i))[1],
			x, y) <= tolerance)
                return i;
        }
        return -1; // no point clicked, return sentinel
    }
    
    public boolean mouseDown(Event evt, int x, int y){
	// if we're adding a new point
	// damage
	// draw it
	if ((selectedPoint = selectPoint(x,y)) == -1) {
	    // this adds the next control point when we've just
	    // finished a single cubic bezier
	    if (points.size() % 3 == 0) {
		points.addElement(new double[]{x,y});
		selectedPoint = points.size() - 1;
		double dx =
		    ((double[])points.elementAt(selectedPoint-1))[0]-x;
		double dy =
		    ((double[])points.elementAt(selectedPoint-1))[1]-y;
		points.addElement(new double[]{x-dx,y-dy});
		selectedPoint = points.size() - 1;
	    }
	    else {
		points.addElement(new double[]{x,y});
		selectedPoint = points.size() - 1;
	    }
	}
        damage = true; return true;
    }
    
    public boolean mouseUp(Event evt, int x, int y){
        damage = true;
        return true;
    }
	
    public boolean mouseDrag(Event evt, int x, int y){
        if(selectedPoint != -1){
            double[] p = (double[])points.elementAt(selectedPoint);

	    // if we're dragging a start/end point
	    // then we need to drag the associated control points
            if(selectedPoint % 3 == 0 &&
	       selectedPoint     != 0 && 
               selectedPoint+1 < points.size()) {
                double[] mid1 =
		    (double[])points.elementAt(selectedPoint-1);
                double[] mid2 =
		    (double[])points.elementAt(selectedPoint+1);
                mid1[0] = mid1[0] + (x - p[0]);
                mid1[1] = mid1[1] + (y - p[1]);
                mid2[0] = mid2[0] + (x - p[0]);
                mid2[1] = mid2[1] + (y - p[1]);
            }
	    // to keep the control points in line
	    // for smoothness along junctions between cubic beziers
	    if (selectedPoint % 3 == 1 &&
		selectedPoint     != 0 &&
		selectedPoint-2 > 0) {
		double[] back2 =
		    (double[])points.elementAt(selectedPoint-2);
		back2[0] = back2[0] - (x - p[0]);
		back2[1] = back2[1] - (y - p[1]);
	    }
	    // to keep the control points in line
	    // for smoothness along junctions between cubic beziers
	    if (selectedPoint % 3 == 2 &&
		selectedPoint     != 0 &&
		selectedPoint+2 < points.size()) {
		double[] forw2 =
		    (double[])points.elementAt(selectedPoint+2);
		forw2[0] = forw2[0] - (x - p[0]);
		forw2[1] = forw2[1] - (y - p[1]);
	    }

	    // move the point to the new x,y
            p[0] = x; p[1] = y;
        }
        damage = true;
        return true;
    }

    public boolean mouseMove(Event evt, int x, int y){
        damage = true;
        return true;
    }

    public boolean keyUp(Event evt, int key){
        damage = true;
        return true;
    }

    public boolean keyDown(Event evt, int key){
	if (key == 'q') drawControlPolygon = 0;
	if (key == 'w') drawControlPolygon = 1;
	if (key == 'e') drawControlPoints = 0;
	if (key == 'r') drawControlPoints = 1;
	if (key == 'i') ovalsize++;
	if (key == 'o') ovalsize--;
	if (key == 'm') {if (tolerance>0) tolerance--;};
	if (key == 'n') tolerance++;
	if (key == 'j') epsilon = epsilon / 2;
	if (key == 'k') epsilon = epsilon * 2;
	if (key == 'z') {
	    //    points.addElement(new double[]{x,y});

	    System.out.println("Outputting current control points");
	    //for(int i=0;i<points.size();i++){
	    //System.out.println("points["+i+"][0]=" +
	    //		   ((double[])points.elementAt(i))[0]+
	    //		   "; points["+i+"][1]=" +
	    //		   ((double[])points.elementAt(i))[1]+";");
	    //}
	    for(int i=0;i<points.size();i++){
		System.out.println("points.addElement(new double[]{" +
				   ((double[])points.elementAt(i))[0]+
				   ", " +
				   ((double[])points.elementAt(i))[1]+"});");
	    }
	    System.out.println("---------------------------------");
	}
        damage = true;
        return true;
    }
    

}

