package curve;
import java.awt.*;

public class InterpolatingBSplineCurve extends BSplineCurve {

  private Coordinate parametricKnot[];
  private static final double twoThirds=2.0/3.0;
  private static final double oneSixth=1.0/6.0;
  private static final int arrowHeadZero=1000;
  private static final int arrowHeadN=1001;
  private NumericTextField gZeroTextX,gNTextX,gZeroTextY,gNTextY;
  private Coordinate gZero,gN;
  private Matrix inverseMatrix;
  
  // Creates new BSplineCurve
  public InterpolatingBSplineCurve(DrawCurve aDrawCurve) {
    super();
    int ctr,row;
    String s;
    Dimension d;
    double matrixArray[][];
    Matrix matrix;
    
    color=Color.black;
    drawCurve=aDrawCurve;
    if(drawCurve!=null) drawCurve.addCurve(this);
    marker=new Coordinate[numPoints];
    
    GridBagLayout layout=new GridBagLayout();
    setBackground(Color.lightGray);
    setLayout(layout);
    GridBagConstraints constraints=new GridBagConstraints();

    //Set some defaults
    constraints.weightx=constraints.weighty=1.0;
    constraints.insets=new Insets(1,1,1,1);
    constraints.fill=GridBagConstraints.NONE;

    //Title row
    titleLabel=new Label("Interpolating B-Spline");
    titleLabel.setFont(new Font("Dialog",Font.BOLD,12));
    constraints.gridwidth=GridBagConstraints.REMAINDER;
    layout.setConstraints(titleLabel,constraints);
    add(titleLabel);

    //Add listeners
    MyActionListener actionListener=new MyActionListener();
    
    //Numbers to substitute into equation
    xText=new NumericTextField[numPoints];
    yText=new NumericTextField[numPoints];
    gZeroTextX=addNewVariable(layout,"g0=","0.71",actionListener,false);
    gZeroTextY=addNewVariable(layout,null,"0.71",actionListener,true);
    
    for(ctr=0;ctr<numPoints;ctr++) {
      s=new Integer(ctr).toString();
      xText[ctr]=addNewVariable(layout,"P"+s+"=",s,actionListener,false);
      yText[ctr]=addNewVariable(layout,null,s,actionListener,true);
    };

    gNTextX=addNewVariable(layout,"gN=","0.71",actionListener,false);
    gNTextY=addNewVariable(layout,null,"0.71",actionListener,true);

    for(ctr=0;ctr<numPoints;ctr++)
      marker[ctr]=new Coordinate(xText[ctr].asDouble(),yText[ctr].asDouble());
    
    gZero=new Coordinate(gZeroTextX.asDouble(),gZeroTextY.asDouble());
    gN=new Coordinate(gNTextX.asDouble(),gNTextY.asDouble());
    
    //Marker size for parametric knots
    markerSize=new int[numPoints+2];
    for(ctr=0;ctr<numPoints+2;ctr++) markerSize[ctr]=0;

    parametricKnot=new Coordinate[numPoints+2]; //the parametric knots
    
    //Set up matrix
    matrixArray=new double[numPoints+2][numPoints+2];
    
    matrixArray[0][0]=-((double)(numPoints-1))/2;
    matrixArray[0][2]=((double)(numPoints-1))/2;
    matrixArray[numPoints+1][numPoints-1]=-((double)(numPoints-1))/2;
    matrixArray[numPoints+1][numPoints+1]=((double)(numPoints-1))/2;
    for(row=1;row<=numPoints;row++) {
      matrixArray[row][row-1]=oneSixth;
      matrixArray[row][row]=twoThirds;
      matrixArray[row][row+1]=oneSixth;
    };
    
    matrix=new Matrix(numPoints+2,numPoints+2,matrixArray);
    matrix.inverse();
    inverseMatrix=new Matrix(matrix);
    setParametricKnots();
  }

  public void setExercise() {
//    int ctr;
//    for(ctr=0;ctr<numPoints;ctr++) {
//      marker[ctr].x=Math.random()*5;
//      marker[ctr].y=Math.random()*5;
//    };
    super.setExercise();
    setParametricKnots();
  };    
    
  //function to get a marker
  public int getMarker(double x,double y) {
    int ctr,returnValue;
    double closest,distance;
    Coordinate arrowHead;
    
    //Look for values within 1/25 of width of graph pixels radius
    closest=((xMax-xMin)/25)*((xMax-xMin)/25); 
    returnValue=-1; //default, no marker found

    if(marker!=null) {//Array initialised
      for(ctr=0;ctr<marker.length;ctr++) {//for every marker in array
        distance=Math.pow(x-marker[ctr].x,2)+Math.pow(y-marker[ctr].y,2);
        if(distance<closest) {//closer than close
          returnValue=ctr;  //change return value
          closest=distance;
        };
      };
      
      //How about those arrows?
      if(drawCurve!=null) {
        arrowHead=drawCurve.getArrowCoordinate(marker[0],gZero);
        distance=Math.pow(x-arrowHead.x,2)+Math.pow(y-arrowHead.y,2);
        if(distance<closest) {//closer than close
          returnValue=arrowHeadZero;  //change return value
          closest=distance;
        };
      }
      if(drawCurve!=null) {
        arrowHead=drawCurve.getArrowCoordinate(marker[numPoints-1],gN);
        distance=Math.pow(x-arrowHead.x,2)+Math.pow(y-arrowHead.y,2);
        if(distance<closest) {//closer than close
          returnValue=arrowHeadN;  //change return value
          closest=distance;
        }
      };
    };
    return returnValue; //Defaults to no marker found
  };
  
  //Overriding FreeBSplineCurve
  public void setMarker(int i,double x,double y) {
    if(i==arrowHeadZero) {
      gZero=new Coordinate(x-marker[0].x,y-marker[0].y);
      gZero.normalise();
      gZeroTextX.setText(new PrintableDouble(gZero.x).asStringSF(3));
      gZeroTextY.setText(new PrintableDouble(gZero.y).asStringSF(3));
    } else if(i==arrowHeadN) {
      gN=new Coordinate(x-marker[numPoints-1].x,y-marker[numPoints-1].y);
      gN.normalise();
      gNTextX.setText(new PrintableDouble(gN.x).asStringSF(3));
      gNTextY.setText(new PrintableDouble(gN.y).asStringSF(3));
    } else {
      super.setMarker(i,x,y);
    };
    setParametricKnots();
  };

  //Get maximum and minimum values of x,y on curve including all knots
  public DoubleRect getMinMax() {
    int ctr;
    double yMin,yMax;
    DoubleRect result;
    Coordinate currentMarker;
    
    //Parametrics
    xMin=xMax=getMarkerNumber(-1).x;
    yMin=yMax=getMarkerNumber(-1).y;
    for(ctr=0;ctr<=numPoints;ctr++) {
      currentMarker=getMarkerNumber(ctr);
      if(currentMarker.x<xMin) xMin=currentMarker.x;
      if(currentMarker.x>xMax) xMax=currentMarker.x;
      if(currentMarker.y<yMin) yMin=currentMarker.y;
      if(currentMarker.y>yMax) yMax=currentMarker.y;
    };
    
    //Geometrics
    for(ctr=0;ctr<numPoints;ctr++) {
      currentMarker=marker[ctr];
      if(currentMarker.x<xMin) xMin=currentMarker.x;
      if(currentMarker.x>xMax) xMax=currentMarker.x;
      if(currentMarker.y<yMin) yMin=currentMarker.y;
      if(currentMarker.y>yMax) yMax=currentMarker.y;
    };

    result=new DoubleRect(xMin,yMin,xMax-xMin,yMax-yMin);
    return result;
  };
  
  //fuction to draw markers and construction lines when called
  public void drawConstructs() {
    int ctr;
    Color lightColor; //Light Colour for phantoms
    if(drawCurve!=null) {
      lightColor=new Color((int)(255-0.5*(255-color.getRed())),
        (int)(255-0.5*(255-color.getGreen())),
        (int)(255-0.5*(255-color.getBlue())));
      //Parametric
      for(ctr=-1;ctr<=numPoints;ctr++) {
        if(ctr==0)
          drawCurve.drawMarkerP0(this,getMarkerNumber(ctr),lightColor);
        else
          drawCurve.drawMarker(this,getMarkerNumber(ctr),lightColor);
        if(ctr!=-1)
          drawCurve.drawLine(this,getMarkerNumber(ctr-1),getMarkerNumber(ctr),
            lightColor);
      };
      //Geometric
      for(ctr=0;ctr<numPoints;ctr++) {
        if(ctr==0)
          drawCurve.drawMarkerP0(this,marker[ctr]);
        else
          drawCurve.drawMarker(this,marker[ctr]);
      };
      drawCurve.drawArrow(this,marker[0],gZero);
      drawCurve.drawArrow(this,marker[numPoints-1],gN);
    };
  };
  
  //Sets up equation ready for drawing
  private void setParametricKnots() {
    double pVector[][],aVector[][];
    int ctr;
        
    //Prepare g,p vector and a vector
    pVector=new double[2][numPoints+2];
    aVector=new double[2][numPoints+2];
    //x
    pVector[0][0]=gZero.x;
    pVector[0][numPoints+1]=gN.x;
    for(ctr=0;ctr<numPoints;ctr++)
      pVector[0][ctr+1]=marker[ctr].x;
    //y
    pVector[1][0]=gZero.y;
    pVector[1][numPoints+1]=gN.y;
    for(ctr=0;ctr<numPoints;ctr++)
      pVector[1][ctr+1]=marker[ctr].y;
    
    aVector[0]=inverseMatrix.mulAB(pVector[0],numPoints+2);
    aVector[1]=inverseMatrix.mulAB(pVector[1],numPoints+2);
    
    for(ctr=0;ctr<numPoints+2;ctr++) {
      parametricKnot[ctr]=new Coordinate(aVector[0][ctr],aVector[1][ctr]);
    };
  };
  
  //Retrieves marker from a number, includes phantom knots
  //Returns parametric knots
  protected Coordinate getMarkerNumber(int markerNumber) {
    return new Coordinate(parametricKnot[markerNumber+1]);
  };  
  
  //function dealing with changes in numbers
  protected void onActionPerformed(java.awt.event.ActionEvent event) {
    int ctr;
    for(ctr=0;ctr<numPoints;ctr++)
      marker[ctr]=new Coordinate(xText[ctr].asDouble(),yText[ctr].asDouble());
    gZero=new Coordinate(gZeroTextX.asDouble(),gZeroTextY.asDouble());
    gZero.normalise();
    gZeroTextX.setText(new PrintableDouble(gZero.x).asStringSF(3));
    gZeroTextY.setText(new PrintableDouble(gZero.y).asStringSF(3));
    gN=new Coordinate(gNTextX.asDouble(),gNTextY.asDouble());
    gN.normalise();
    gNTextX.setText(new PrintableDouble(gN.x).asStringSF(3));
    gNTextY.setText(new PrintableDouble(gN.y).asStringSF(3));
    setParametricKnots();
    if(drawCurve!=null) drawCurve.repaint();
  };
};  