// Bifurcator.java: An applet for demoing the bifurcation behavior of
// various equations.
// Suzanne Britton, completed 8-11-97
// Public domain

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

public class Bifurcator extends Applet {
  public BifDisplay display;
  public BifControls controls;
  
  public void init() {
    display = new BifDisplay(this);
    controls = new BifControls(this);
    setLayout(new BorderLayout());
    add("South", controls);
    add("Center", display);
  }
}

class BifCanvas extends Canvas {
  static final int initIter = 50;
  static final int plottedIter = 50;

  private Image source = null;

  public BifCanvas() {
    setBackground(Color.lightGray);
  }
   
  public void update(Graphics g) {
    paint(g);
  }

  public void paint(Graphics g) {
    Graphics gr;
    int width = size().width;
    int height = size().height;

    if (source == null) {
      source = createImage(width, height);
      gr = source.getGraphics();
      gr.setColor(Color.white);
      gr.fillRect(0, 0, width, height);
      gr.setColor(Color.black);
      gr.drawLine(0, 0, 0, height-1);
      gr.drawLine(0, height-1, width-1, height-1);
    }
    g.drawImage(source, 0, 0, this);
  }

  public void plot(Func f, double xstart, double ystart, double xend,
                   double yend) {
    Graphics gr;
    int width = size().width;
    int height = size().height;
    int x, y;
    short iter;
    double a, nextVal;
    double xscale, yscale;

    source = createImage(width, height);
    gr = source.getGraphics();
    gr.setColor(Color.white);
    gr.fillRect(0, 0, width, height);
    gr.setColor(Color.black);
    gr.drawLine(0, 0, 0, height-1);
    gr.drawLine(0, height-1, width-1, height-1);
    gr.setColor(Color.blue);
    xscale = (double)width / (xend-xstart);
    yscale = (double)height / (yend-ystart);
    for (x=0; x < width; x++) {
      a = (double)x/xscale + xstart;
      nextVal = f.critPoint();
      for (iter=0; iter < initIter; iter++)
        nextVal = f.compute(a, nextVal);
      for (iter=0; iter < plottedIter; iter++) {
        nextVal = f.compute(a, nextVal);
        y = (height-1) - (int)((nextVal - ystart) * yscale);
        if (y >=0 && y < height)
          gr.fillRect(x, y, 1, 1);
      }
    }
    repaint();
  }
}

class CurvesCanvas extends Canvas {
  private Image source = null;

  public CurvesCanvas() {
    setBackground(Color.lightGray);
  }

  public void update(Graphics g) {
    paint(g);
  }
   
  public void paint(Graphics g) {
    Graphics gr;
    int width = size().width;
    int height = size().height;
    
    if (source == null) {
      source = createImage(width, height);
      gr = source.getGraphics();
      gr.setColor(Color.white);
      gr.fillRect(0, 0, width, height);
      gr.setColor(Color.black);
      gr.drawLine(0, 0, 0, height-1);
      gr.drawLine(0, height-1, width-1, height-1);
    }
    g.drawImage(source, 0, 0, this);
  }

  public void plot(Func f, short numIterations, double xstart, double ystart,
                   double xend, double yend) {
    Graphics gr;
    int width = size().width;
    int height = size().height;
    int x, y;
    short iter;
    double a, nextVal;
    int[] prevPoints = new int[numIterations];
    Color[] colors = {Color.red, Color.blue, Color.green, Color.magenta,
      Color.cyan, Color.black, Color.orange, Color.pink
    };
    int numColors = colors.length;
    double xscale, yscale;

    source = createImage(width, height);
    gr = source.getGraphics();
    gr.setColor(Color.white);
    gr.fillRect(0, 0, width, height);    
    for (iter=0; iter < numIterations; iter++)
      prevPoints[iter] = -1;
    gr.setColor(Color.black);
    gr.drawLine(0, 0, 0, height-1);
    gr.drawLine(0, height-1, width-1, height-1);
    xscale = (double)width / (xend-xstart);
    yscale = (double)height / (yend-ystart);
    for (x=0; x < width; x++) {
      a = (double)x/xscale + xstart;
      nextVal = f.critPoint();
      for (iter=0; iter < numIterations; iter++) {
        gr.setColor(colors[iter % numColors]);
        nextVal = f.compute(a, nextVal);
        y = (height-1) - (int)((nextVal - ystart) * yscale);
        if (y >=0 && y < height) {
          if (prevPoints[iter] == -1 || prevPoints[iter] == y)
            gr.fillRect(x, y, 1, 1);
          else
            gr.drawLine(x-1, prevPoints[iter], x, y);
          prevPoints[iter] = y;
        }
        else
          prevPoints[iter] = -1;
      }
    }
    repaint();
  }
}

class BifDisplay extends Panel {
  private Bifurcator app;
  private BifCanvas bifCanvas;
  private CurvesCanvas curvesCanvas;

  private Func f;
  private short numIterations;
  private double xstart, ystart, xend, yend;
  private boolean plotting = false;

  public BifDisplay(Bifurcator app) {
    this.app = app;
    setLayout(new GridLayout(2, 1));
    bifCanvas = new BifCanvas();
    curvesCanvas = new CurvesCanvas();
    add(bifCanvas);
    add(curvesCanvas);
  }

  public void plot(Func f, short numIterations, double xstart, double ystart,
                   double xend, double yend) {
    if (plotting)
      return;
    else
      plotting = true;

    this.f = f;
    this.numIterations = numIterations;
    if (xstart < xend) {
      this.xstart = xstart;
      this.xend = xend;
    }
    else {
      this.xstart = xend;
      this.xend = xstart;
    }
    if (ystart < yend) {
      this.ystart = ystart;
      this.yend = yend;
    }
    else {
      this.ystart = yend;
      this.yend = ystart;
    }
    curvesCanvas.plot(f, numIterations, xstart, ystart, xend, yend);
    bifCanvas.plot(f, xstart, ystart, xend, yend);

    plotting = false;
  }

  public void update(Graphics g) {
    paint(g);
  }

  public void paint(Graphics g) {
    curvesCanvas.repaint();
    bifCanvas.repaint();
  }
}

class BifControls extends Panel {
  private Bifurcator app;
  private Panel topRow, bottomRow;
  private TextField xstart, ystart, xend, yend;
  private Choice funcList;
  private TextField numIterations;
  private Func1 f1;
  private Func2 f2;
  private Func3 f3;

  public BifControls(Bifurcator app) {
    f1 = new Func1();
    f2 = new Func2();
    f3 = new Func3();
    
    this.app = app;
    topRow = new Panel();
    bottomRow = new Panel();
    xstart = new TextField("1", 6);
    xend = new TextField("4", 6);
    ystart = new TextField("0", 6);
    yend = new TextField("1", 6);
    funcList = new Choice();
    funcList.addItem("ax(1-x)");
    funcList.addItem("asin(x)");
    funcList.addItem("acos(x)");
    numIterations = new TextField("6", 2);
  
    topRow.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 5));
    topRow.add(new Label("HSpan:", Label.RIGHT));
    topRow.add(xstart);
    topRow.add(new Label("--", Label.CENTER));
    topRow.add(xend);
    topRow.add(new Label("VSpan:", Label.RIGHT));
    topRow.add(ystart);
    topRow.add(new Label("--", Label.CENTER));
    topRow.add(yend);

    bottomRow.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 5));
    bottomRow.add(new Button("Start"));
    bottomRow.add(new Label("Function:", Label.RIGHT));
    bottomRow.add(funcList);
    bottomRow.add(new Label("Num Iterations:", Label.RIGHT));
    bottomRow.add(numIterations);

    setLayout(new GridLayout(2, 1));
    setBackground(Color.lightGray);
    add(topRow);
    add(bottomRow);
  }

  public boolean action(Event evt, Object what) {
    if (evt.target instanceof Button) {
      Func f;
      switch (funcList.getSelectedIndex()) {
        case 0:  f = f1; break;
        case 1:  f = f2; break;
        default: f = f3; break;
      }
      app.display.plot(
        f,
        (short)Integer.parseInt(numIterations.getText()),
        Double.valueOf(xstart.getText()).doubleValue(),
        Double.valueOf(ystart.getText()).doubleValue(),
        Double.valueOf(xend.getText()).doubleValue(),
        Double.valueOf(yend.getText()).doubleValue()
      );
      return true;
    }
    else if (evt.target instanceof Choice) {
      switch (funcList.getSelectedIndex()) {
        case 0:
	  xstart.setText("1");
	  xend.setText("4");
	  ystart.setText("0");
	  yend.setText("1");
	  numIterations.setText("6");
	  break;
        case 1:
	  xstart.setText("1");
	  xend.setText("4");
	  ystart.setText("-4");
	  yend.setText("4");
	  numIterations.setText("6");
	  break;	
        default:
	  xstart.setText("1");
	  xend.setText("4");
	  ystart.setText("-4");
	  yend.setText("4");
	  numIterations.setText("6");
      }
      repaint();
      return true;
    }
    else
      return false;
  }
}

abstract class Func {
  abstract double compute(double a, double x);
  abstract public double critPoint();
}

class Func1 extends Func {
  public double compute(double a, double x) {
    return a*x*(1-x);
  }

  public double critPoint() {return 0.5;}
}

class Func2 extends Func {
  public double compute(double a, double x) {
    return a*Math.sin(x);
  }

  public double critPoint() {return Math.PI/2;}
}

class Func3 extends Func {
  public double compute(double a, double x) {
    return a*Math.cos(x);
  }
  
  public double critPoint() {return 0;}
}
