/*************************************************************
*                                                            *
*   Julia.java    By Mark McClure  -  Feb 1996               *
*                         revised  -  July 1996              *
*   mcclure@stolaf.edu                                       *
*   http://www.stolaf.edu/people/mcclure/java/Julia/         *
*                                                            *
*   Copyright (c) 1996  Mark McClure, All Rights Reserved.   *
*                                                            *
*************************************************************/


/*******************************************************************
*                                                                  *
*   This applet generates julia sets for the quadratic family of   *
*   functions f(z) = z^2 + c.  The complex parameter c may be      *
*   chosen from am image of the Mandelbrot set or entered into     *
*   a TextField.                                                   *
*                                                                  *
*   The Julia set is constructed using the inverse iteration       *
*   algorithm as follows:  Note that f(z) has two inverses         *
*   f1(z) = sqrt(z-c) and f2(z) = -sqrt(z-c).  Start with an       *
*   arbitrary initial point z0.  Construct a binary tree with      *
*   z0 at the top.  Any node z should have two children f1(z)      *
*   and f2(z).  Stop constructing a branch when the pixel it       *
*   determines has been plotted.                                   *
*                                                                  *
*   Note: In order to see the displayed Mandelbrot set, a gif      *
*   image of the Mandelbrot set of the appropriate dimensions      *
*   must reside in the same directory as the .class files          *
*   generated by this file.  Such a gif image currently lives      *
*   here:                                                          *
*   http://www.stolaf.edu/people/mcclure/java/Julia/mandel.gif     *
*                                                                  *
*******************************************************************/

// Need some tools.
import java.awt.*;
import java.util.*;



// -------------------> The Julia class  <------------------------
// This is the main class which extends the Applet class and 
// implements the Runnable interface to run smoothly.

public class Julia extends java.applet.Applet implements Runnable {        

    // ************ Important ************
    // It would be very difficult to fiddle with the size of this
    // applet.  The displayed image of the Mandelbrot set is a gif
    // image 300x300 pixels.  A new image would have to be generated
    // (by an external program).  Furthermore, the size of the
    // treeNodeQueue would have to be increased.
    static final int H_SIZE = 600;    // Horizontal size of the applet
    static final int V_SIZE = 330;    // Vertical size of the applet

    static Thread initRunner;         // Initialization Thread
    JuliaCanvas juliaCanvas;          // Place to draw the Julia Set
    MandelCanvas mandelCanvas;        // Place to draw the Madelbrot Set
    ControlPanel p;                   // Holds The controls 
    Image mandel_img;    // The Mandelbrot set displayed by the applet
                         // is a gif rather than a generated image.

    public void init() {
    
        // Initialization
        resize(H_SIZE, V_SIZE);
        setFont(new Font("TimesRoman", Font.PLAIN, 12));
        setBackground(Color.white);
        setForeground(Color.black);
        setLayout(new BorderLayout());  
                        
        // The two main canvases - one for the Julia set and one
        // for the Mandelbrot set.
        juliaCanvas = new JuliaCanvas(H_SIZE/2, this);
        mandel_img = getImage(getCodeBase(), "mandel.gif");
        mandelCanvas = 
        new MandelCanvas(mandel_img, juliaCanvas, p, H_SIZE/2);

        // A panel to hold the two main canvases.
        Panel innerPanel = new Panel();
        add("Center", innerPanel);
        innerPanel.setLayout(new GridLayout(1, 2, 2, 2));
        innerPanel.add(mandelCanvas);
        innerPanel.add(juliaCanvas);

        // The control panel.
        p = new ControlPanel(juliaCanvas, mandelCanvas, this);
        p.resize(400, 30);
        add("South", p);

        show();
    } //end init()

    // Clean up when applet stops.
    public synchronized void stop() {
        if( juliaCanvas.redrawRunner != null ) {
            juliaCanvas.redrawRunner.stop();
        }
        juliaCanvas.redrawRunner = null;

        if(mandelCanvas.juliaRunner != null ) {
            mandelCanvas.juliaRunner.stop();
        }
        mandelCanvas.juliaRunner = null;
        
        if( initRunner != null ) {
            initRunner.stop();
        }
        initRunner = null;
        if(mandelCanvas.theSet != null) {
            mandelCanvas.theSet.top = null;
        }
    } //end stop()
    
    public void run() {}
}//end Julia class.



// -----------------> The Mandel Canvas class <----------------
// This class holds all the information relevant to the displayed
// Mandelbrot set.

class MandelCanvas extends Canvas {
    
    Image mandel_img;                  // Holds the Mandelbrot gif
    JuliaCanvas juliaCanvas;           // Link to the JuliaCanvas
    ControlPanel p;                    // Link to the ControlPanel
    Thread juliaRunner;                // A thread for the JuliaCanvas
    boolean update_positionQ = true;   // Only want to update the position
                                       // display until a point is selected.
    JuliaSet theSet;                   // Link to the Julia Set
    
    // Bounds for the Mandelbrot Set in the complex plane
    static final double z_left = -2;   
    static final double z_right = .6;
    static final double z_top = 1.3;
    static final double z_bot = -1.3;
    
    int pixel_size; // Size of the Mandelbrot image in pixels
    

    // The constructor method
    MandelCanvas(Image mandel_passed, JuliaCanvas juliaCanvas_passed,
    ControlPanel p_passed, int pixel_size_passed) {
                
        // Set these variables to the values passed
        mandel_img = mandel_passed;
        juliaCanvas = juliaCanvas_passed;
        p = p_passed;
        pixel_size = pixel_size_passed;
        
        // Set some stuff
        setBackground(Color.white);
        show();
    }// end constructor method
    
    // Update the postion display when the mouse moves.
    public boolean mouseMove(Event evt, int x, int y) {
        if(update_positionQ) {
            ComplexNumber c = complexConvert(x,y);
            p.update_position(c);
        }
        return true;
    }

    // Clear the postion display when the mouse leaves.
    public boolean mouseExit(Event evt, int x, int y) {
        if(update_positionQ) {
            p.positionDisplay.setText("");
        }
        return true;
    }
    
    // The action starts when the user clicks on the Mandelbrot set.
    public boolean mouseUp(Event evt, int x, int y)  {
        ComplexNumber c = complexConvert(x,y);   // Need a complex
                                                 // number from (x,y)
        theSet = new JuliaSet(c, juliaCanvas);
        
        // Update the position display for the last time.
        p.update_position(c);
        update_positionQ = false;

        // Set the Julia parameter and begin the computation thread.
        if( juliaRunner == null ) {
            juliaRunner = new Thread(theSet);
            //juliaRunner.setPriority(Thread.MIN_PRIORITY);
            juliaRunner.start();
        }
        if( !juliaRunner.isAlive() ) {
            juliaRunner.stop();
            juliaRunner = new Thread(theSet);
            //juliaRunner.setPriority(Thread.MIN_PRIORITY);
            juliaRunner.start();
        }
    
        return true;
    } //end mouseUp()
    
    // Called by mouseUp() to converty (x,y) to a complex number.
    ComplexNumber complexConvert(int x, int y) {
        double a, b;
        a = (double) ( ((z_right - z_left)/pixel_size)*x + z_left );
        b = (double) ( ((z_bot - z_top)/pixel_size)*y + z_top );
    
        return new ComplexNumber(a,b);
    }

    public void paint(Graphics g) {
        g.drawImage(mandel_img, 0, 0, this);
    }
}//end MandelCanvas class



// ------------------> The JuliaCanvas class <-----------------
// This class holds the information needed to draw the Julia Set.
// The set itself is an object of class JuliaSet.
// Implents runnable since drawing is time consuming.

class JuliaCanvas extends Canvas  implements Runnable {
    
    static ComplexNumber c;             // Parameter defining the Julia Set
    static final double z_size = 4.0;   // Size of the set in complex plane
    static int pixel_size;              // Size of the set in pixels
    static Thread redrawRunner;          // Thread to start() theSet in
    Julia theApplet;
    
    // An array to hold the complex numbers in the Julia Set
    
    // The constructor method
    JuliaCanvas(int pixel_size_passed, Julia theApplet_passed) {

        // Set these variables to the values passed
        pixel_size = pixel_size_passed;
        theApplet = theApplet_passed;
        
        // Set some stuff
        setBackground(Color.white);
        setForeground(Color.black);
        setFont(new Font("TimesRoman", Font.PLAIN, 12));
        resize(pixel_size, pixel_size);
        show();
    }// end constructor method
    
    public void run() {
        if(theApplet.mandelCanvas.theSet != null) {
            theApplet.mandelCanvas.theSet.redraw();
        }
    }


    // Called in response to a button press to clear the canvas.
    public void clear() {
        if(redrawRunner != null) {
            redrawRunner.stop();
        }
        if(theApplet.mandelCanvas.juliaRunner != null) {
            theApplet.mandelCanvas.juliaRunner.stop();
        }
        theApplet.mandelCanvas.theSet.top = null;
        repaint();
    }

    // Plots the complex numbers that form the Julia Set.
    void plot(int x, int y) {
        Graphics g = getGraphics();
        g.drawLine(x,y, x,y);
    }
    
    // Drawing the Julia Set can be time consuming and is run in a thread.
    public void paint(Graphics g) {
        if(redrawRunner == null) {
            redrawRunner = new Thread(this);
            //redrawRunner.setPriority(Thread.MIN_PRIORITY);
            redrawRunner.start();
        }
        if( !redrawRunner.isAlive() ) {
            redrawRunner.stop();
            redrawRunner = new Thread(this);
            //redrawRunner.setPriority(Thread.MIN_PRIORITY);
            redrawRunner.start();
        }
    }
}//end JuliaCanvas class



// -------------> The JuliaSet class <----------------
// Holds the information defining the Julia Set itself.

class JuliaSet implements Runnable {
    ComplexNumber c;             // Set by the function JuliaCanvas.run()
    TreeNode top = null;         // top of the JuliaSet             
    JuliaCanvas canvas;          // A place to draw the JuliaSet
    static final int bailout = 1;
    static final int plot_record_hashsize = 12007;
    
    // The constructor method
    JuliaSet(ComplexNumber c_passed, JuliaCanvas canvas_passed) {
        
        // Set these variables to the values passed
        canvas = canvas_passed;
        c = c_passed;
    }// end constructor method
    
    // This is the main computation and is, therfore, run in
    // a thread.
    public void run() {
        int x, y;
        int i, j;
                        // .78 + .52i is an arbitrary initial point
        ComplexNumber z0 = new ComplexNumber(0.78,0.52);
        TreeNode item;
        Integer concat;
        
        // A Queue of treeNodes facilitates traversal of the tree
        Queue treeNodeQueue = new Queue();
        
        // A Hashtable keeps track of what points have been plotted
        Hashtable plot_record = new Hashtable(plot_record_hashsize);
        
        
        // Apply the inverses a few times to get the initial point
        // close to the Julia set.
        for(i=0; i<10; i++){
            z0 = finv_1(z0);
            z0 = finv_2(z0);
        }

        // Set up the tree which will hold the Julia set
        top = new TreeNode(z0);
        treeNodeQueue.put(top);
        
        // This loop is the main computation described at the top
        // of this file.
        while(!treeNodeQueue.empty()) {
            item = treeNodeQueue.get();
            inv(item);
            x = (item.left).x;
            y = (item.left).y;
            concat = new Integer(1000*x + y);
            if(plot_record.get((Object) concat) == null) {
                canvas.plot(x,y);
                treeNodeQueue.put(item.left);
                plot_record.put((Object) concat, (Object) concat);
            }
            x = (item.right).x;
            y = (item.right).y;
            concat = new Integer(1000*x + y);
            if(plot_record.get((Object) concat) == null) {
                canvas.plot(x,y);
                treeNodeQueue.put(item.right);
                plot_record.put((Object) concat, (Object) concat);
            }
        }
    }

    // Constructs the nodes containing the inverses of TreeNode.z.
    void inv(TreeNode item) {
        TreeNode left_child = new TreeNode(finv_1(item.z));
        TreeNode right_child = new TreeNode(finv_2(item.z));
        item.left = left_child;
        item.right = right_child;
    }
    
    // Traverses the tree to redraw the Julia set.
    void redraw() {
        TreeNode item;
        Queue redrawQueue = new Queue();
        
        if(top != null) {
            redrawQueue.put(top);
            while(!redrawQueue.empty()) {
                item = redrawQueue.get();
                canvas.plot(item.x, item.y);
                if(item.left != null) {
                    redrawQueue.put(item.left);
                }
                if(item.right != null) {
                    redrawQueue.put(item.right);
                }
            }
        }
    }

    // The two inverses.
    ComplexNumber finv_1(ComplexNumber z) {
        return z.minus(c).sqrt();
    }
    ComplexNumber finv_2(ComplexNumber z) {
            return z.minus(c).sqrt().ominus();
    }
}//end JuliaSet class


//--------------->The TreeNode class<-----------------------------
// The Julia Set is constructed as a binary tree.  Each point is 
// held in TreeNode.

class TreeNode {
    ComplexNumber z;
    TreeNode left, right;
    int x, y;
    
    static final int pixel_size = 300;
    static final int z_size = 4;
    
    TreeNode(ComplexNumber z_passed) {
        z = z_passed;
        x = (int) ((pixel_size/z_size)*(z.a + z_size/2));
        y = (int) ((pixel_size/z_size)*(z_size/2 - z.b));
        left = null;
        right = null;
    }
}

//------------------>The Queue class<-------------------------
// A standard array based implementation of a Queue to facilitate
// traversal of the Julia set tree.

class Queue {
    int head, tail;
    TreeNode[] theQueue;
    static final int max = 500;
    
    Queue() {
        head = 0;
        tail = 0;
        theQueue = new TreeNode[max + 1];
    }
    
    void put(TreeNode v) {
        theQueue[tail++] = v;
        if(tail > max) {
            tail = 0;
        }   
    }
    
    TreeNode get() {
        TreeNode x;
    
        x = theQueue[head++];
        if(head > max) {
            head = 0;
        }
        return x;
    }
    
    boolean empty() {
        return head == tail;
    }
}


// -----------> The ComplexNumber class <----------------
// The computations above are in terms of complex numbers
// defined here.

class ComplexNumber {
    double a, b;        // The real and imaginary parts
    
    
    // ---> Several constructor Methods <---
    ComplexNumber(double a_passed, double b_passed) {
        a = a_passed;
        b = b_passed;
    }
    
    ComplexNumber(double a_passed) {
        a = a_passed;
        b = 0.0;
    }
    
    ComplexNumber() {
        a = 0.0;
        b = 0.0;
    }// end constructor methods
    
    
    // ---> Several arithmetic methods <---
    ComplexNumber plus(ComplexNumber z) {
        return new ComplexNumber(a + z.a, b + z.b);
    }
        
    ComplexNumber minus(ComplexNumber z) {
        return new ComplexNumber(a - z.a, b - z.b);
    }
    
    ComplexNumber ominus() {
        return new ComplexNumber(-a, -b);
    }
    
    //The only slightly tricky operation
    ComplexNumber sqrt() {
        
        // The polar components of the number
        double theta;
        double r = Math.sqrt(Math.sqrt(a*a + b*b));
        
        // The cartesian components of the square root
        double a_sqrt, b_sqrt;

        // Compute theta
        if(a >= 0) {
            theta = (Math.atan(b/a)/2);
        }
        else {
            if(b >= 0) {
                theta = (Math.atan(b/a) + Math.PI)/2;
            }
            else {
                theta = (Math.atan(b/a) - Math.PI)/2;
            }
        }// end computation of theta

        // Compute the cartesian components of the square root
        a_sqrt = r*(Math.cos(theta));
        b_sqrt = r*(Math.sin(theta));
        
        return new ComplexNumber(a_sqrt, b_sqrt);
    }// end sqrt()
    
     // end arithmetic methods
     
}// end ComplexNumber class


// ------------> The ControlPanel class <------------------
//This class defines the ControlPanel and responds to events.

class ControlPanel extends Panel {

    Panel mandelPanel;                   // Two subpanels
    Panel juliaPanel;
    Julia theApplet;                     // A link to the applet
    
    Button clear_button;                 // The clear button
    JuliaCanvas juliaCanvas;             // Linked to the JuliaCanvas
    MandelCanvas mandelCanvas;           // Linked to the MandelCanvas
    static TextField positionDisplay;    // Displays the Julia parameter
    
    Thread juliaTextRunner;              // The user may start the 
                                         // computation by entering a
                                         // value in the TextField.  So
                                         // we need a thread for that 
                                         // computation

    // The constructor method.
    ControlPanel(JuliaCanvas juliaCanvas_passed,
    MandelCanvas mandelCanvas_passed, Julia theApplet_passed) {
    
        // Set the layout
        try{ 
            setLayout(new GridLayout(1, 2, 0, 0)); 
        }
        catch(IllegalArgumentException e){/* Ignore exception */}
                                          // I'm not even sure why this
                                          // exception exists.
        
        // Place a panel below the Mandelbrot image and the Julia image. 
        mandelPanel = new Panel();
        juliaPanel  = new Panel();
        add(mandelPanel);
        add(juliaPanel);
        
        // Create and add the position display to the mandelPanel.
        positionDisplay = new TextField(30);
        positionDisplay.setEditable(true);
        mandelPanel.add(positionDisplay);
        
        // Create and add the buttons to the juliaPanel.
        clear_button = new Button("Clear");
        juliaPanel.add(clear_button);
        
        // Set the canvases to the values passed.
        juliaCanvas = juliaCanvas_passed;
        mandelCanvas = mandelCanvas_passed;
        theApplet = theApplet_passed;
        
        // Set the background color
        setBackground(Color.gray);
    }// end constructor method
    
    //The event handler.
    public boolean action(Event evt, Object arg) {
    
        // Clear Button is pressed.
        if (evt.target instanceof Button) {
            mandelCanvas.update_positionQ = true;
            mandelCanvas.juliaRunner.stop();
            juliaCanvas.clear();
            juliaCanvas.repaint();
        }
        
        // Start computation if user hits return in the TextField
        if( (evt.target instanceof TextField) 
        && (evt.id == Event.ACTION_EVENT) ) {
        
            // Seems that I should have to check to see if this works.
            ComplexNumber c = parseInput(positionDisplay.getText());
            mandelCanvas.theSet = new JuliaSet(c, juliaCanvas);
            
            if( juliaTextRunner == null ) {
                juliaTextRunner = new Thread(mandelCanvas.theSet);
                //juliaTextRunner.setPriority(Thread.MIN_PRIORITY);
                juliaTextRunner.start();
            }
            if( !juliaTextRunner.isAlive() ) {
                juliaTextRunner.stop();
                juliaTextRunner = new Thread(mandelCanvas.theSet);
                //juliaTextRunner.setPriority(Thread.MIN_PRIORITY);
                juliaTextRunner.start();
            }
        }// end if  
        return true;
    }// end action()


    // Called by MandelCanvas.mouseEvents to update the postion.
    public static void update_position(ComplexNumber c) {
        String y;
        String x;
        
        try {
            x = Double.toString(c.a).substring(0,5);
        }
        catch(StringIndexOutOfBoundsException e) {
            x = Double.toString(c.a);
        }
        if(c.b >= 0) {
            try {
                y = Double.toString(c.b).substring(0,5);
            }
            catch(StringIndexOutOfBoundsException e) {
                y = Double.toString(c.b);
            }
            positionDisplay.setText("c = " + x + " + " + y + " i");
        }
        else {
            try {
                y = Double.toString(-c.b).substring(0,5);
            }
            catch(StringIndexOutOfBoundsException e) {
                y = Double.toString(-c.b);
            }
            positionDisplay.setText("c = " + x + " - " + y + " i");
        }
    }// end update_position()
    
    
    // When user presses return in the TextField, we want to compute the
    // JuliaSet.  So we need to parse the TextField to get the complex
    // Juliaset parameter.  The user **must** enter text in the format
    // "c = a + b i" or "c = a - b i"  where 'a' and 'b' are non-negative
    // numbers.
    ComplexNumber parseInput(String str) {
    
        // Use the StringTokenizer to parse the TextField.
        StringTokenizer string_toke = new StringTokenizer(str, " ");
        int num_tokes = string_toke.countTokens();
        
        // User may enter "c = a + b i" or "c = a - b i".  
        // plus_or_minus tells which one it was.
        String plus_or_minus;
        
        double a = 0, b = 0;    // The real and imaginary parts
        int i;
        
        // Check to make sure the text was entered correctly
        if( num_tokes != 6 ) {
            inputError();
        }
        if( !string_toke.nextToken().equalsIgnoreCase("c") ) {
            inputError();
        }
        if( !string_toke.nextToken().equals("=") ) {
            inputError();
        }
        
        // Get the real part.
        try {
            a = new Double(string_toke.nextToken()).doubleValue();
        }
        catch(NumberFormatException e) {
            inputError();
        }
        
        // Get the imaginary part. Check whether it's positve or
        // negative first.
        plus_or_minus = new String(string_toke.nextToken());
        if( !( plus_or_minus.equals("+") || plus_or_minus.equals("-") ) ) {
            inputError();
        }
        try {
            b = new Double(string_toke.nextToken()).doubleValue();
        }
        catch(NumberFormatException e) {
            inputError();
        }
        if(plus_or_minus.equals("-")) {
            b = -b;
        }
        
        // Check for the 'i'.
        if( !string_toke.nextToken().equalsIgnoreCase("i") ) {
            inputError();
        }
        
        // Return the ComplexNumber.
        return new ComplexNumber(a,b);
    }// end parseInput()
    
    // Display message if input is incorrectly formatted.
    static void inputError() {
        positionDisplay.setText("Input Error");
    }   
    
}//end ControlPanel class.

//end file
