/*

The Mandelbrot Set
Copyright (c) 1996 Simon Arthur

For more information about this program, contact
chroma@mindspring.com.

*/

import java.awt.*;
import java.applet.*;

public class MandelTest extends Applet implements Runnable {
  MandelControls controls;
  MandelCanvas c; // TimeThread T;

    public void init() {
      String s; // parameter string
      float pa,pb,pc,pd; // temp vars for parameters

      s = getParameter("xstart");
      pa = (s==null) ?  -2 : Float.valueOf(s).floatValue();
      s = getParameter("ystart");
      pb = (s==null) ?  -2 : Float.valueOf(s).floatValue();
      s = getParameter("xend");
      pc = (s==null) ?  2 : Float.valueOf(s).floatValue();
      s = getParameter("yend");
      pd = (s==null) ?  2 : Float.valueOf(s).floatValue();
      MandelCanvas c = new MandelCanvas(pa,pb,pc,pd);        
      BorderLayout l = new BorderLayout();
      setLayout(l);
      add("Center",c);
      c.resize(100,100);
      controls = new MandelControls(c);
      add("South",controls);
      controls.resize(200,200);
      c.controls = controls;
      layout();
      c.repaint();
      // start new thread so that the other one will multitask properly
    //  T=new TimeThread();
     // T.start();
     // T.setPriority(Thread.MIN_PRIORITY);
    }
    
    public void start() {
        controls.enable();
    }
    
    public void run() {
        controls.enable();
    }
    
    public void stop() {
        controls.disable();
    }
}

/*****************************************
             MandelCanvas
******************************************/
class MandelCanvas extends Canvas {
  boolean     filled = false;
  Font        font;
  float       Xmin,Ymin,Xmax,Ymax;
  Thread      mythread=null; 
  boolean     DrawMandel=true;
  boolean     DrawMarqueeMode;  
  boolean     DrawThread=false; 
                // Keep track of whether the drawing 
                // thread has been created
  MandelArtist mymandel=null;
  Marquee mymarquee=new Marquee();
  String artistName = "artthread";
  Rectangle r;
  MandelControls controls;                        
      
  public MandelCanvas (float a, float b, float c, float d) {
    Xmin=a; Ymin=b; Xmax=c; Ymax=d;
  }

  public void paint(Graphics g) {
    r=bounds();
    mymarquee.boundx = r.width;
    mymarquee.boundy = r.height;
    if (DrawMandel) {
      if (!DrawThread) {  // we know that the Drawing thread hasn't been made  
        //first give mymandel the information it needs to draw
        mymandel = new MandelArtist(g,artistName);
        //We'll lower the priority a little so it will pre-empt properly 
        mymandel.setPriority(Thread.MIN_PRIORITY);
        mymandel.maxx = r.width;
        mymandel.maxy = r.height;
        mymandel.xstart = Xmin;
        mymandel.ystart = Ymin;
        mymandel.xend = Xmax;
        mymandel.yend = Ymax;
//System.out.println(Xmin);System.out.println(Xmax);System.out.println(Ymin);System.out.println(Ymax);
        mymandel.needsRepaint=true;
        //  now we'll start a new thread of mymandel so it draws in the background
        mymandel.start();
        DrawThread=true;
      }
      else {
        mymandel.needsRepaint=true;
      }
    }
    mymarquee.draw(g);
  }

  public void redraw(float Xmin, float Ymin,float Xmax, float Ymax) {  
    mymandel.stop();                             
    // make a new thread the next time we need to repaint
    DrawThread=false; DrawMandel=true;
    this.Xmin=Xmin;  this.Ymin=Ymin;
    this.Xmax=Xmax;  this.Ymax=Ymax;
    repaint();
  }
  
  public boolean mouseUp(Event evt, int x, int y) {
    mymarquee.resize(x,y);
    controls.f3.setText(String.valueOf(Xmin + (x * ((Xmax-Xmin)/r.width)  ) ) );
    controls.f4.setText(String.valueOf(Ymin + (y * ((Ymax-Ymin)/r.height) ) ) ); 
    repaint();
    return true;
  }

  public boolean mouseDown(Event evt, int x, int y) {
    mymarquee.reset();
    // stop the MandelArtist drawing thread
    mymandel.stop();
    // make a new thread the next time we need to repaint
    DrawThread=false;
    // stop drawing the Mandelbrot for now
    DrawMandel=false;
    // Set new anchor point for marquee
    mymarquee.setAnchorPoint(x,y);
    controls.f1.setText(String.valueOf(Xmin + (x * ((Xmax-Xmin)/r.width)  ) ) );       
    controls.f2.setText(String.valueOf(Ymin + (y * ((Ymax-Ymin)/r.height) ) ) );       
 
    mymarquee.show();

    return true;
  }
  
  public boolean mouseDrag(Event evt, int x, int y) {
    mymarquee.resize(x,y);
    controls.f3.setText(String.valueOf(Xmin + (x * ((Xmax-Xmin)/r.width)  ) ) );       
    controls.f4.setText(String.valueOf(Ymin + (y * ((Ymax-Ymin)/r.height) ) ) );       
    repaint();
    return true;
  }
  
  public void update(Graphics g) {
    mymarquee.draw(g);
    
    if (DrawMandel) {
       paint(g);
    }
  }
}

/*****************************************
             MandelControls
******************************************/


class MandelControls extends Panel {
    TextField f1, f2, f3, f4;
    Label l1,l2,l3,l4,ltest;
    Button b1,b2,b3,b4,breset;
    Choice ch;
    Dimension d;

    MandelCanvas canvas;
 
 
    // set up our buttons, edit fields, etc.
    public MandelControls(MandelCanvas canvas) {
       this.canvas = canvas;
       GridLayout l=new GridLayout(0,4);
       setLayout(l);
       this.setBackground(Color.gray);
       this.setForeground(Color.blue);
       
//       add("z0", new Label(""));  add("z1", new Label(""));
//       add("z2", new Label(""));  add("z3", new Label(""));
       
       add("l1",l1 = new Label(" Xmin="+canvas.Xmin));
       add("l2",l2 = new Label(" Ymin="+canvas.Ymin));
       add("l3",l3 = new Label(" Xmax="+canvas.Xmax));
       add("l4",l4 = new Label(" Ymax="+canvas.Ymax));
       add("f1",f1 = new TextField(String.valueOf(canvas.Xmin),4));
       add("f2",f2 = new TextField(String.valueOf(canvas.Ymin),4));
       add("f3",f3 = new TextField(String.valueOf(canvas.Xmax),4));
       add("f4",f4 = new TextField(String.valueOf(canvas.Ymax),4)); 
       add("b1",b1 = new Button("Zoom In"));
       add("b2",b2 = new Button("Zoom Out"));
       add("b3",b3 = new Button("Julia Set")); b3.disable(); //don't have this working yet
       add("b4",b4 = new Button("Redraw"));
       add("breset",breset = new Button("Reset Values"));
       ch = new Choice();
       ch.addItem("Small"); 
       ch.addItem("Medium");
       ch.addItem("Big");
       ch.select("Big");
       add("ch1",ch);
      

    }
    
    public void setNewValues() {  //update text fields with info about
                                 // new Mandelbrot set
      l1.setText(" Xmin="+canvas.Xmin);
      l2.setText(" Ymin="+canvas.Ymin);
      l3.setText(" Xmax="+canvas.Xmax); 
      l4.setText(" Ymax="+canvas.Ymax);
      f1.setText(String.valueOf(canvas.Xmin));
      f2.setText(String.valueOf(canvas.Ymin));       
      f3.setText(String.valueOf(canvas.Xmax));       
      f4.setText(String.valueOf(canvas.Ymax)); 
    }

    public boolean action(Event ev, Object arg) {
      if (ev.target.equals(b1)) {   //zooom in -- min and max fix it if the user has 
                                    //    switched Xmin & Xmax or Ymin and Ymax
        canvas.redraw(Math.min(Float.valueOf(f1.getText().trim()).floatValue(),Float.valueOf(f3.getText().trim()).floatValue()) ,
                      Math.min(Float.valueOf(f2.getText().trim()).floatValue(),Float.valueOf(f4.getText().trim()).floatValue()) ,
                      Math.max(Float.valueOf(f1.getText().trim()).floatValue(),Float.valueOf(f3.getText().trim()).floatValue()) ,
                      Math.max(Float.valueOf(f2.getText().trim()).floatValue(),Float.valueOf(f4.getText().trim()).floatValue())                      
                      );
      } else if (ev.target.equals(b2)) { // zoom out
        canvas.redraw(canvas.Xmin-(canvas.Xmax-canvas.Xmin)/2,
                      canvas.Ymin-(canvas.Ymax-canvas.Ymin)/2,
                      canvas.Xmax+(canvas.Xmax-canvas.Xmin)/2,
                      canvas.Ymax+(canvas.Ymax-canvas.Ymin)/2);
      } else if (ev.target.equals(b3)) {         // Julia set
     /*   canvas.redraw(canvas.Xmin,
                      canvas.Ymin,
                      canvas.Xmax,
                      canvas.Ymax); */
      } else if (ev.target.equals(b4)) {         // redraw
        canvas.redraw(canvas.Xmin,
                      canvas.Ymin,
                      canvas.Xmax,
                      canvas.Ymax); 
      } else if (ev.target.equals(breset)) { // reset values
        canvas.redraw(-2,-2,2,2); 
      } else if (ev.target.equals(ch)) { // change size of picture
        if (ch.getSelectedItem()=="Small") {
          canvas.mymarquee.hide();
          canvas.resize(50,50); canvas.redraw(canvas.Xmin,canvas.Ymin,canvas.Xmax,canvas.Ymax);
          canvas.move(1,1);         
        } else if (ch.getSelectedItem()=="Medium") {
          canvas.mymarquee.hide();
          canvas.resize(100,100); canvas.redraw(canvas.Xmin,canvas.Ymin,canvas.Xmax,canvas.Ymax);
          canvas.move(1,1);
        } else if (ch.getSelectedItem()=="Big") {
          canvas.mymarquee.hide();
          canvas.resize(300,300); canvas.redraw(canvas.Xmin,canvas.Ymin,canvas.Xmax,canvas.Ymax);
          canvas.move(1,1);
        }
      }                  
      setNewValues();
      return true;
    }
}

/*****************************************
             MandelArtist
******************************************/

class MandelArtist extends Thread {
  boolean needsRepaint = false;
  Graphics gcontext;
  
  int maxx, maxy; // these are the upper limits of the pixels we will be
                  // plotting to 
  float xstart, ystart, xend, yend;  // these are the coordinates
  // of the area of the Mandelbrot set that we will be drawing
  
  public MandelArtist (Graphics g, String s) {
 
//    System.out.println(" - MandelArtist.constructor");
    // copy the graphics context for use in this object
    gcontext=g.create();
    setName(s);  // set the new thread's name
    // gcontext.drawString("constructor", 10, 80); 
  }
  
  public void run() {
    gcontext.drawString("run", 10, 10); 
//    System.out.println(" - MandelArtist.run");
    while (true) {
      try { sleep(500);
      } catch (Exception E) {
        stop();
      }       
       
      if (needsRepaint) {
        doDraw();       
      }
    }
  }
  
//  public void stop() {
//    needsRepaint=false;
//  }

  private int iterate (double x, double y) {
 //   long aold, zsquared=0, xi=(long)(x*10000), yi=(long)(y*10000), ai=0, bi=0;
     /* Iterate over the complex value with real component x 
      * and imaginary component y
      */
     
    int iter=0,maxiter=500;
    double aold, bold, a2old=Double.MAX_VALUE, 
           b2old=Double.MAX_VALUE, zsquared,a=0,b=0,asquared=0,bsquared=0;
           
    aold=0; bold=0;
    asquared=a*a; 
    a=x;
    bsquared=b*b;
    b=y;    
    zsquared=asquared+bsquared;
    for (iter=0; iter<maxiter; iter++) { // calculate value for each point
      a=asquared-bsquared+x;
      asquared=a*a; 
      b=2*aold*b+y;
      if (bold==b && aold==a) { // we have been repeating the same calculation
        iter=maxiter-1;           // so we'll break out on the next loop
    //    yield();
      }
      bsquared=b*b;
      zsquared=asquared+bsquared;
      if ( zsquared>4 )
        break;
      bold=b; aold=a; 
    }
   /*  // an attempt at using integer math to do calculations
   for (ai=0,bi=0,iter=0; iter<500; iter++) { // calculate value for each point
      aold=ai; 
      ai=ai*ai-bi*bi+xi; 
      bi=2*aold*bi+yi; 
      zsquared=ai*ai+bi*bi;
      if ( zsquared>35919520)
        break;
    }*/      
//    System.out.println(zsquared);
    return iter;    
  }


  public void doDraw() {
    
/* Draw the mandelbrot into graphics context gcontext
maxx and maxy are the max numbers of pixels
xstart, ystart, xend and yend marks off the area of the Mandelbrot
set that we will draw.
*/     

    int iter,selcolor,plotx, ploty=0, iterold, plotyold;
    float oldx=xstart, oldy=ystart; 
    float zsquared, xlen=xend-xstart, ylen=yend-ystart;
    float xincr=xlen/maxx, yincr=ylen/maxy;
    Color c[]=new Color[255]; 
    String s;

//System.out.println(" - MandelArtist.doDraw");
    needsRepaint=false; // we won't need to repaint unless we get interrupted
                        // while drawing in which case whis will be set to
                        // false by some other method.
    for(iter=0;iter<255; iter++) {
      c[iter]=new Color(iter*26,iter*2,iter*35);
    }
     
    for (double x = xstart ; x < xend ; x+=xincr) {
      plotx = (int)( (x-xstart) * (maxx/xlen) );
      for (double y = ystart ; y < yend ; y+=yincr) { // Loop over each point on M-set
        iter=0; 
        iter=iterate(x,y);
        gcontext.setColor(c[iter%255]);
        ploty=(int)( (y-ystart) * (maxy/ylen) );        
        gcontext.drawLine( plotx , ploty , plotx , ploty );
      }
    }
  }         
}


/*************************************************************************
                     Marquee
 *************************************************************************/

class Marquee {
// stuff for drawing our Marquee (selection rectangle)
  int rectAnchorX=0, rectAnchorY=0, rectTermX=0, rectTermY=0;
// keep track of where we've last put the marquee on the screen  
  int x1, y1, x2, y2;
  int boundx=Integer.MAX_VALUE,
      boundy=Integer.MAX_VALUE;  //don't make it bigger than these values
  boolean hidden=true, needs_hide=false;

public void draw(Graphics g) {
  // draw the Marquee, if we need to 
  if (!hidden) {
    if (x1!=rectAnchorX || y1!=rectAnchorY || rectTermY!=y2 ||rectTermX!=x2 ) {
    // we need to erase the old rectangle
      put_rectangle(g, x1,y1,x2,y2);
      //now draw new one
      put_rectangle(g,rectAnchorX, rectAnchorY, rectTermX, rectTermY);
      x1=rectAnchorX; y1=rectAnchorY;
      x2=rectTermX; y2=rectTermY;
      hidden=false;
    }
  } else {
  // hidden
  //if it has not already been erased, erase it, otherwise do nothing
    if (needs_hide) {
      put_rectangle(g,rectAnchorX, rectAnchorY, rectTermX, rectTermY);
    }
  }
}

public boolean inMarquee(int x, int y) {
   //find out if the specified point is inside the marquee
//      System.out.println("Marquee.inmarquee");
//      System.out.println(x);
//      System.out.println(rectAnchorX);
//      System.out.println(rectTermX);
//      System.out.println(rectAnchorY);
//      System.out.println(rectTermY);

   if ( ( (x>rectAnchorX && x<rectTermX) || (x>rectTermX && x<rectAnchorX) ) &&
        ( (y>rectAnchorY && y<rectTermY) || (y>rectTermY && y<rectAnchorY) ) )
     {
//        System.out.println("inmarquee - true");
        return true;
     } else { 
//        System.out.println("inmarquee - false");        
        return false;
     }
}
 
public void show() {
  hidden=false;
}

public void hide() {
  hidden=true;
  needs_hide=true;
}
 
public void reset() {
 rectAnchorX=0; rectAnchorY=0; rectTermX=0; rectTermY=0;
}
 
public void resize(int x, int y) {
  // rectTermX=Math.max(x,y); rectTermY=Math.max(x,y);  // make square marq.
  rectTermX=Math.min(x,boundx); // check to see it isn't too big
  rectTermY=Math.min(y,boundx);
}

public void setAnchorPoint (int x, int y) {
    rectAnchorX=x;
    rectAnchorY=y;
}

private void put_rectangle(Graphics g, int x_0, int y_0, int x_1, int y_1) {
   int width, height, startx, starty;
   g.setColor(Color.white);
   g.setXORMode(Color.black);
   width=Math.abs(x_1-x_0); height=Math.abs(y_1-y_0);
   //find upper left corner
   starty=Math.min(y_1,y_0);
   startx=Math.min(x_1,x_0);
   g.drawRect(startx, starty, width, height);
   g.setPaintMode();
  }
}

/** waste some time - may fix netscape problem with threading*/

class TimeThread extends Thread {
 
  public void run() {
    while (true) {
       try { 
        sleep(5);
      } catch (Exception E) {
        stop();
      }
  }
 }
}
