/*
 * This is the CurvePanel class for the Famous Curves applet.
 * Author: Ben Soares < bs@st-and.ac.uk >
 *
 */

import java.awt.*;
import java.applet.*;

public class CurvePanel extends Panel {

 // class variable

 static Color darkGreen;

 // member variables

 int pointstage;		//stage at which a chosen point is (0=no point chosen, 1=point chosen)
 int pointX, pointY;		//chosen point position
 int circlestage;		//stage at which a chosen circle is (0=no circle chosen, 1=choosing radius, 2=dragging circle)
 int circleX, circleY, circleCX, circleCY, circleR;
				//chosen circle centre position, circumference point, and radius
 int associatedcurveindex;	//which associated curve option is selected
				/* (0=None, 1=Evolute, 2=Involute, 3=Inverse, 4=Pedal,
				 *  5=Negative Pedal, 6=Caustic, 7=Caustic (parallel lines)) */
 int associatedcurve[][];	//the pixel points of the associated curve
 Curve curve;			//the curve being dealt with
 boolean setaxis;		//have Curves class variables been set?
 boolean clear;			//do you want to clear the curvepanel picture?
 double a, b, c, d, e, f, h, k, m, n, p;// the curves original parameter values
 int steps = 1;				// how many steps to take at each line plot of curve
  // CurvePanel constructor methods
  
public CurvePanel(Curve curve, int associatedcurveindex) {
  
  setBackground(Color.white);
  this.setaxis = false;
  this.curve = curve;
  Font font = new Font("Helvetica", Font.PLAIN, 12);
  setFont(font);
  //  Label label = new Label(curve.name, Label.CENTER);
  //  add(label);
  
  this.pointstage = 0;
  this.circlestage = 0;
  this.pointX = 0;
  this.pointY = 0;
  this.circleX = 0;
  this.circleY = 0;
  this.circleCX = 0;
  this.circleCY = 0;
  this.circleR = 0;
  this.associatedcurveindex = associatedcurveindex;
  this.clear = false;
  darkGreen = new Color(0,140,0);
  
}
  
public CurvePanel(Curve curve){
  
  setBackground(Color.white);
  this.setaxis = false;
  int xsc = (int)(size().width/(curve.xrange*(3/2)));
  int ysc = (int)(size().height/(curve.yrange*(3/2)));
  int sc = Math.min(xsc, ysc);
  Curve.xsc = sc;
  Curve.ysc = sc;
  Curve.Ox = (int)(curve.xb*Curve.xsc*(-3/2));
  Curve.Oy = (int)(curve.yt*Curve.xsc*(3/2));
  Curve.xsize = size().width;
  Curve.ysize = size().height;
  this.curve = curve;
  
  Font font = new Font("Helvetica", Font.PLAIN, 14);
  setFont(font);
  //  Label label = new Label(curve.name, Label.CENTER);
  //  add(label);
  
  this.pointstage = 0;
  this.circlestage = 0;
  this.pointX = 0;
  this.pointY = 0;
  this.circleX = 0;
  this.circleY = 0;
  this.circleCX = 0;
  this.circleCY = 0;
  this.circleR = 0;
  this.associatedcurveindex = 0;
  this.clear = false;
  darkGreen = new Color(0,140,0);
  
} /* end of constructor methods */
  
  // recalculate associated curve method (for scales and translations)
  
public void recalculate() {
  
  switch (associatedcurveindex) {
    
  case 0:			// "None"
    break;
    
  case 1:			// "Evolute"
    this.associatedcurve = curve.evolutePixels();
    break;
    
  case 2:			// "Involute"
    this.associatedcurve = curve.involutePixels(pointX, pointY);
    break;
    
  case 3:			// "Inverse"
    this.associatedcurve = curve.inversePixels(circleX, circleY, circleCX, circleCY);
    break;
    
  case 4:			// Pedal
    this.associatedcurve = curve.pedalPixels(pointX, pointY);
    break;
    
  case 5:			// Negative pedal
    this.associatedcurve = curve.negativePedalPixels(pointX, pointY);
    break;
    
  case 6:			// Caustic
    this.associatedcurve = curve.causticPixels(pointX, pointY);
    break;
    
  case 7:			// Caustic (parallel lines)
    this.associatedcurve = curve.causticPixels(pointX, pointY, new String("parallel"));
    break;
    
  }
  
  this.pointX = Curve.xPointToPixel(Curve.xPixelToPoint(pointX));
  this.pointY = Curve.yPointToPixel(Curve.yPixelToPoint(pointY));
  this.circleX = Curve.xPointToPixel(Curve.xPixelToPoint(circleX));
  this.circleY = Curve.yPointToPixel(Curve.yPixelToPoint(circleY));
  this.circleCX = Curve.xPointToPixel(Curve.xPixelToPoint(circleCX));
  this.circleCY = Curve.yPointToPixel(Curve.yPixelToPoint(circleCY));
  this.circleR = (int)(Math.sqrt((circleCX-circleX)*(circleCX-circleX) + (circleCY-circleY)*(circleCY-circleY)));
  
} /* end of recalculate method */
  
  
  // beginning of scale method
  
public void scale (double f) {
  
  this.pointX = Curve.xPointToPixel(f*Curve.xPixelToPoint(this.pointX));
  this.pointY = Curve.yPointToPixel(f*Curve.yPixelToPoint(this.pointY));
  this.circleX = Curve.xPointToPixel(f*Curve.xPixelToPoint(this.circleX));
  this.circleY = Curve.yPointToPixel(f*Curve.yPixelToPoint(this.circleY));
  this.circleCX = Curve.xPointToPixel(f*Curve.xPixelToPoint(this.circleCX));
  this.circleCY = Curve.yPointToPixel(f*Curve.yPixelToPoint(this.circleCY));
  this.circleR = (int)(Math.sqrt((circleCX-circleX)*(circleCX-circleX)+(circleCY-circleY)*(circleCY-circleY)));
  
} /* end of scale method */
  
  // beginning of translate method
  
public void translate (int xt, int yt) {
  
  this.pointX = this.pointX + xt;
  this.pointY = this.pointY + yt;
  this.circleX = this.circleX + xt;
  this.circleY = this.circleY + yt;
  this.circleCX = this.circleCX + xt;
  this.circleCY = this.circleCY + yt;
  
} /* end of translate method */
  
  // beginning of setParameters method
  
public void setParameters(double a, double b, double c, double d, double e, double f, double h, double k, double m, double n, double p) {
  this.a = a;
  this.b = b;
  this.c = c;
  this.d = d;
  this.e = e;
  this.f = f;
  this.h = h;
  this.k = k;
  this.m = m;
  this.n = n;
  this.p = p;
} /* end of setParameters method */
  
  // beginning of resetCurveParameters method
  
public void resetCurveParameters() {
  this.curve.a = this.a;
  this.curve.b = this.b;
  this.curve.c = this.c;
  this.curve.d = this.d;
  this.curve.e = this.e;
  this.curve.f = this.f;
  this.curve.h = this.h;
  this.curve.k = this.k;
  this.curve.m = this.m;
  this.curve.n = this.n;
  this.curve.p = this.p;
} /* end of resetCurveParameters method */
  
  // mousedown method
  
public boolean mouseDown(Event e, int mx, int my) {
  
  switch (associatedcurveindex) {
    
  case 0:			// "None"
    break;
    
  case 1:			// "Evolute"
    // note this should be drawn from when it is chosen
    break;
    
  case 2:			// "Involute"
    pointX = mx;
    pointY = my;
    pointstage = 1;
    associatedcurve = curve.involutePixels(pointX, pointY);
    repaint();
    break;
    
  case 3:			// "Inverse"
    boolean redraw = true;
    double mR = Math.sqrt( (mx-circleX)*(mx-circleX) + (my-circleY)*(my-circleY) );	// mouse distance from centre
    switch (circlestage) {
    case 0:		// no circle yet
      circleX = mx;
      circleY = my;
      circleCX = mx;
      circleCY = my;
      circleR = 2;
      circlestage = 1;
      break;
    case 1:		// after choosing radius
      if ( circleR-1 < mR && mR < circleR+2 ) {		// if mouse is "on" circumference
	circleCX = mx;
	circleCY = my;
	circleR = (int)(mR);
	circlestage = 1;
	redraw = false;
      } else if ( mR < 4 ) {				// if mouse is "on" centre
	circleX = mx;
	circleY = my;
	circlestage = 2;
	redraw = false;
      } else {						// otherwise start new circle
	circleX = mx;
	circleY = my;
	circleCX = mx;
	circleCY = my;
	circleR = 2;
	circlestage = 1;
      }
      break;
    case 2:		// after dragging circle (same as after choosing radius)
      if ( circleR-2 < mR && mR < circleR+3 ) {		// if mouse is "on" circumference
	circleCX = mx;
	circleCY = my;
	circleR = (int)(mR);
	circlestage = 1;
	redraw = false;
      } else if ( mR < 3 ) {				// if mouse is "on" centre
	circleX = mx;
	circleY = my;
	circlestage = 2;
	redraw = false;
      } else {						// otherwise start new circle
	circleX = mx;
	circleY = my;
	circleCX = mx;
	circleCY = my;
	circleR = 2;
	circlestage = 1;
      }
      break;
    }
    associatedcurve = curve.inversePixels(circleX, circleY, circleCX, circleCY);
    if (redraw) { repaint(); }
    break;
    
  case 4:			// Pedal
    pointX = mx;
    pointY = my;
    pointstage = 1;
    associatedcurve = curve.pedalPixels(pointX, pointY);
    repaint();
    break;
    
  case 5:			// Negative pedal
    pointX = mx;
    pointY = my;
    pointstage = 1;
    associatedcurve = curve.negativePedalPixels(pointX, pointY);
    repaint();
    break;
    
  case 6:			// Caustic
    pointX = mx;
    pointY = my;
    pointstage = 1;
    associatedcurve = curve.causticPixels(pointX, pointY);
    repaint();
    break;
    
  case 7:			// Caustic (parallel lines)
    pointX = mx;
    pointY = my;
    pointstage = 1;
    associatedcurve = curve.causticPixels(pointX, pointY, new String("parallel"));
    repaint();
    break;
    
  }
  
  return true;
  
} /* end of mousedown method */
  
  
  // mousedrag method
  
public boolean mouseDrag(Event e, int mx, int my) {
  
  switch (associatedcurveindex) {
    
  case 0:			// "None"
    break;
    
  case 1:			// "Evolute"
    // note that this should be drawn from when it is chosen
    break;
    
  case 2:			// "Involute"
    pointX = mx;
    pointY = my;
    pointstage = 1;
    associatedcurve = curve.involutePixels(pointX, pointY);
    repaint();
    break;
    
  case 3:			// "Inverse"
    switch (circlestage) {
    case 0:		// no circle yet (this should never happen)
      circleX = mx;
      circleY = my;
      circleCX = mx;
      circleCY = my;
      circleR = 2;
      circlestage = 1;
      break;
    case 1:		// choosing radius
      double mR = Math.sqrt( (mx-circleX)*(mx-circleX) + (my-circleY)*(my-circleY) );	// mouse distance from centre
      circleCX = mx;
      circleCY = my;
      circleR = Math.max(2,(int)(mR));
      circlestage = 1;
      break;
    case 2:		// dragging circle
      circleX = mx;
      circleY = my;
      circleCX = mx + circleR;
      circleCY = my;
      circlestage = 2;
      break;
    }
    associatedcurve = curve.inversePixels(circleX, circleY, circleCX, circleCY);
    repaint();
    break;
    
  case 4:			// Pedal
    pointX = mx;
    pointY = my;
    pointstage = 1;
    associatedcurve = curve.pedalPixels(pointX, pointY);
    repaint();
    break;
    
  case 5:			// Negative pedal
    pointX = mx;
    pointY = my;
    pointstage = 1;
    associatedcurve = curve.negativePedalPixels(pointX, pointY);
    repaint();
    break;
    
  case 6:			// Caustic
    pointX = mx;
    pointY = my;
    pointstage = 1;
    associatedcurve = curve.causticPixels(pointX, pointY);
    repaint();
    break;
    
  case 7:			// Caustic (parallel lines)
    pointX = mx;
    pointY = my;
    pointstage = 1;
    associatedcurve = curve.causticPixels(pointX, pointY, new String("parallel"));
    repaint();
    break;
    
  }
  
  return true;
  
} /* end of mousedrag method */
  
  // paint method
  
public void paint(Graphics g) {
  
  if (!this.setaxis) {
    int sc;
    sc = (int)(size().height/(curve.yrange));
    Curve.xsc = sc;
    Curve.ysc = sc;
    Curve.Ox = (int)(-curve.xb*sc+(size().width-size().height)/2);
    //   Curve.Ox = (int)(size().width/2 + (curve.xb+curve.xt)/2*sc)
    Curve.Oy = (int)(+curve.yt*sc);
    Curve.xsize = size().width;
    Curve.ysize = size().height;
    this.curve.setCurve();
    this.curve.translate(0, 0);
    this.translate(0, 0);
    this.setaxis = true;
  }
  if (this.clear) {
    g.setColor(Color.white);
    g.fillRect(0, 0, size().width-1, size().height-1);
    this.clear = false;
  }
  
  g.setColor(Color.lightGray);
  //draw axis
  g.drawLine(Curve.Ox, 0, Curve.Ox, size().height);
  g.drawLine(0, Curve.Oy, size().width, Curve.Oy);
  
  g.setColor(Color.blue);
  //draw the main curve
  int acx1, acy1, acx2, acy2, acx0, acy0;
  int j = 0;
  for (int i=0; i<=curve.nop-steps; i+=steps) {
    acx1 = curve.fxpixels[i];
    acy1 = curve.fypixels[i];
    acx2 = curve.fxpixels[i+steps];
    acy2 = curve.fypixels[i+steps];
    // the next "if" checks to see at least one of the endpoints is on screen
    if ((0 < acx2 && acx2 < Curve.xsize && 0 < acy2 && acy2 < Curve.ysize) || (0 < acx1 && acx1 < Curve.xsize && 0 < acy1 && acy1 < Curve.ysize)) {
      // the next "if" checks to see the points aren't too far away
      if (Math.abs(acx1 - acx2) < size().height/2 && Math.abs(acy1 - acy2) < size().width/2) {
	g.drawLine(acx1, acy1, acx2, acy2);
      }
    }
    j = i;
  }
  if (j!=curve.nop-1) {
    j++;
    acx1 = curve.fxpixels[j];
    acy1 = curve.fypixels[j];
    acx2 = curve.fxpixels[curve.fxpixels.length-1];
    acy2 = curve.fypixels[curve.fxpixels.length-1];
    // the next "if" checks to see at least one of the endpoints is on screen
    if ((0 < acx2 && acx2 < Curve.xsize && 0 < acy2 && acy2 < Curve.ysize) || (0 < acx1 && acx1 < Curve.xsize && 0 < acy1 && acy1 < Curve.ysize)) {
      // the next "if" checks to see the points aren't too far away
      if (Math.abs(acx1 - acx2) < size().height/2 && Math.abs(acy1 - acy2) < size().width/2) {
	g.drawLine(acx1, acy1, acx2, acy2);
      }
    }
  }
  
  
  g.setColor(Color.green);
  //draw a point if appropriate
  if (pointstage > 0) {
    g.fillArc(pointX-2, pointY-2, 4, 4, 0, 360);
  }
  //draw circle if appropriate
  if (circlestage > 0) {
    g.fillArc(circleX-2, circleY-2, 4, 4, 0, 360);
    g.setColor(darkGreen);
    g.drawArc(circleX-circleR, circleY-circleR, 2*circleR, 2*circleR, 0, 360);
  }
  //draw a line if appropriate
  if (associatedcurveindex == 7 && pointstage>0) {
    g.setColor(darkGreen);
    if (pointX == Curve.Ox) {
      if (pointY == Curve.Oy) {
	g.drawLine(0, Curve.Oy, size().width, Curve.Oy);
      } else {
	g.drawLine(Curve.Ox, 0, Curve.Ox, size().height);
      }
    } else {
      int linepoints[][] = Curve.lineEnds(Curve.yPixelToPoint(pointY)/Curve.xPixelToPoint(pointX), 0.0);
      g.drawLine(linepoints[0][0], linepoints[1][0], linepoints[0][1], linepoints[1][1]);
    }
  }
  
  g.setColor(Color.red);
  //draw associated curve if appropriate
  if ( (associatedcurveindex>0 && pointstage+circlestage>0) || (associatedcurveindex==1) ) {
    for (int i=0; i<=curve.nop+2-steps; i+=steps) {
      acx1 = associatedcurve[0][i];
      acy1 = associatedcurve[1][i];
      acx2 = associatedcurve[0][i+steps];
      acy2 = associatedcurve[1][i+steps];
      // the next "if" checks to see at least one of the endpoints is on screen
      if ((0 < acx2 && acx2 < Curve.xsize && 0 < acy2 && acy2 < Curve.ysize) || (0 < acx1 && acx1 < Curve.xsize && 0 < acy1 && acy1 < Curve.ysize)) {
	// the next "if" checks to see the points aren't too far away
	if (Math.abs(acx1 - acx2) < size().height/2 && Math.abs(acy1 - acy2) < size().width/2) {
	  g.drawLine(acx1, acy1, acx2, acy2);
	}
      }
      j = i;
    }
    if (j!=curve.nop-1) {
      j++;
      acx1 = associatedcurve[0][j];
      acy1 = associatedcurve[1][j];
      acx2 = associatedcurve[0][associatedcurve[0].length-1];
      acy2 = associatedcurve[1][associatedcurve[1].length-1];
      // the next "if" checks to see at least one of the endpoints is on screen
      if ((0 < acx2 && acx2 < Curve.xsize && 0 < acy2 && acy2 < Curve.ysize) || (0 < acx1 && acx1 < Curve.xsize && 0 < acy1 && acy1 < Curve.ysize)) {
	// the next "if" checks to see the points aren't too far away
	if (Math.abs(acx1 - acx2) < size().height/2 && Math.abs(acy1 - acy2) < size().width/2) {
	  g.drawLine(acx1, acy1, acx2, acy2);
	}
      }
    }
  }
  
  g.setColor(darkGreen);
  // outline point or centre of circle if appropriate
  if (pointstage > 0 && associatedcurveindex != 7) {
    g.drawArc(pointX-2, pointY-2, 4, 4, 0, 360);
  }
  if (circlestage > 0) {
    g.drawArc(circleX-2, circleY-2, 4, 4, 0, 360);
  }

} /* end of paint method */

} /* end of CurvePanel class */
