/******************************************************************************
*													*
*	Class:  Mandelbrot									*
*	Author:  Jay Henniger  -  jlh75@cornell.edu					*
*	Date:  July 10, 2001									*
*													*
*	Purpose:  This application lets the user explore the fascinating and    *
* 		beautiful Mandelbrot set.  The Mandelbrot set is an example of    * 
*		a fractal.  It exhibits self-similarity at any level of           *
*		magnification.  The application allows the user to zoom in and    *
*		explore different areas of the set, and provides a familiar       *
*		user interface for managing a few other options (different        *
*		color palates, etc).                                              *
*													*
*	Mathematical background:  What is the Mandelbrot set?  For a given 	*
*	complex number c , consider the sequence of complex numbers             *
* 													*
*     			Z0, Z1, Z2, Z3, ...						*
* 													*
*     defined recursively by the formula:							*
*													*
*			Z0 = 0 + 0i									*
*			Z(n+1) = (Z(n))^2 + c							*
*													*
*	The Mandelbrot set is the set of all complex numbers c for which the    *
*	sequence of complex numbers above does not "escape to infinity."  We    *
*	say that a sequence escapes to infinity if for every integer N there    *
*	exists a place in that sequence after which all the terms are farther   *
*	away from (0,0) - the origin - than N units.                            *
*													*
*	Fortnuately, we have an easy way to tell if a given sequence has        *
*	started to escape to infinity.  Once the points in a sequence get       *
*	farther than 2 units away from the origin they MUST continue to diverge *
*	away from the origin "to infinity."                                     *                      
*													*
*	Note to advanced users:  If you want to explore very deep into the set, *
*	you'll want to increase MAXITERS.  This will slow down the program but  *
*	improve accuracy close to the boundary of the set.  You may also want   *
*	to consider changing the start-up image directly by editing the values  *
*	in the main() method.                                                   *
*													*
*	This program is provided freely for academic and non-commercial,        *
*	personal use only.  Copyright 2001 - all rights reserved.               *
*													*
******************************************************************************/  

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Mandelbrot extends JFrame implements ActionListener, MouseListener{

	public static final int WIDTH = 450;
	public static final int HEIGHT = 400;
	public static int MAXITERS = 125;
	
	//These values account for the fact that the paintable
	//part of the JFrame is not WIDTH x HEIGHT, but is offset
	//by the following values due to the JFrame border, menuBar, etc.

	public static final int LEFTOFFSET = 4;
	public static final int RIGHTOFFSET = 5;
	public static final int TOPOFFSET = 42;
	public static final int BOTTOMOFFSET = 14;

	public static Complex upLeft, lowRight;
	
	//Array to store data for Mandelbrot set.  
	//HEIGHT + 30 to account for BOTTOMOFFSET

 	private static int[][] escapeNum = new int[WIDTH][HEIGHT + 30];
	
	//Set the initial color scheme to blue

	String colorScheme = new String("blue");

	public Mandelbrot()
	{	   
	   setSize(WIDTH, HEIGHT);
	   setLocation(200,150);
	   Container contentPane = getContentPane();
	   contentPane.setLayout(new FlowLayout());
	   contentPane.setBackground(Color.white);
	   setTitle(" Fractal Explorer - the Mandelbrot set.  Click to zoom in.");
	   addMouseListener(this);

	   //Create new menuBar and menus

	   MenuBar myMenuBar = new MenuBar();
	   
	   Menu fileMenu = new Menu("File");
	   Menu viewMenu = new Menu("View");
	   Menu colorMenu = new Menu("Color");
	   Menu zoomMenu = new Menu("Zoom");
	   Menu accuracyMenu = new Menu("Accuracy"); 

	   MenuItem fileMenuExit = new MenuItem("Exit");
	   fileMenuExit.setActionCommand("Exit");
	   fileMenuExit.addActionListener(this);
	
	   MenuItem viewMenuRefresh = new MenuItem("Refresh");
	   viewMenuRefresh.setActionCommand("Refresh");
	   viewMenuRefresh.addActionListener(this);	
	   
	   CheckboxMenuItem aMenu100 = new CheckboxMenuItem("100",true);
	   CheckboxMenuItem aMenu200 = new CheckboxMenuItem("200");
	   aMenu100.setActionCommand("setIters100");
	   aMenu100.addActionListener(this);
         aMenu100.setState(true);
	   aMenu200.setActionCommand("setIters200");
	   aMenu200.addActionListener(this);
	   
	   accuracyMenu.add(aMenu100);
	   accuracyMenu.add(aMenu200);

	   MenuItem colorMenuRed = new MenuItem("Red");
	   MenuItem colorMenuGreen = new MenuItem("Green");
	   MenuItem colorMenuBlue = new MenuItem("Blue");
	   colorMenuRed.setActionCommand("red");
	   colorMenuGreen.setActionCommand("green");
   	   colorMenuBlue.setActionCommand("blue");
 	   colorMenuRed.addActionListener(this);
	   colorMenuGreen.addActionListener(this);
	   colorMenuBlue.addActionListener(this);	
	
	   colorMenu.add(colorMenuBlue);
	   colorMenu.add(colorMenuRed);
	   colorMenu.add(colorMenuGreen);	
		
	   fileMenu.add(fileMenuExit);
	   viewMenu.add(viewMenuRefresh);

	   MenuItem zoomFull = new MenuItem("Back to full");
	   zoomFull.setActionCommand("zoom full");
	   zoomFull.addActionListener(this);	
	   zoomMenu.add(zoomFull);

	   myMenuBar.add(fileMenu);
         myMenuBar.add(viewMenu);
	   myMenuBar.add(colorMenu);
	   myMenuBar.add(zoomMenu);
	   //myMenuBar.add(accuracyMenu);

	   setMenuBar(myMenuBar);
	   
	}//end Mandelbrot constructor

	//getData method calculates number of iterations (up to MAXITERS)
	//before points escape to infinity (magnitude > 2).  Takes a region
	//in the complex plane as input in the form of two complex numbers
	//giving the upper left and lower right corners of the region.

	public static void getData(Complex upperLeft, Complex lowerRight){
	   double realStep, imagStep;
	   upLeft = upperLeft;
	   lowRight = lowerRight;
	   realStep = (lowerRight.real - upperLeft.real)/(double)(WIDTH - LEFTOFFSET - RIGHTOFFSET - 1);
	   imagStep = (upperLeft.imag - lowerRight.imag)/(double)(HEIGHT - TOPOFFSET + BOTTOMOFFSET - 1);

	   Complex location = upperLeft;
           
	   //Starting at upperLeft, calculate escapeNum
		
           for(int imagLoc = TOPOFFSET; imagLoc <= HEIGHT + BOTTOMOFFSET; imagLoc++){
              for(int realLoc = LEFTOFFSET; realLoc <= WIDTH - RIGHTOFFSET; realLoc++){ 
		
 	           int iter = 1;
		     double mag = 0;
		     Complex z = location;

	           while(mag < 2 && iter < MAXITERS){
		        z = Complex.add(Complex.mult(z,z),location);
		        mag = Complex.mag(z);
		        iter++;	
		     }

		     escapeNum[realLoc][imagLoc] = iter; 
		     location = new Complex(location.real + realStep, location.imag);
	        }
	        location = new Complex(upperLeft.real, location.imag - imagStep);		
	     }
	     System.out.println("Finished calculating escape data.");
	}//end getData
	
	public void paint(Graphics g){
		super.paint(g);	
		g.setColor(Color.black);
		
		//Paint the Mandelbrot set black 
		//using data from escapeNum[][]

		for(int i = LEFTOFFSET; i <= WIDTH - RIGHTOFFSET; i++){
		   for(int j = TOPOFFSET; j <= HEIGHT + BOTTOMOFFSET; j++){
		   
			int iters = escapeNum[i][j];
			
			if(iters == MAXITERS){
			   g.setColor(Color.black);	//Mandelbrot set will be
			   g.fillRect(i,j,1,1);  	//painted black
		 	}
		 	
		 	//Paint the rest of the pixels according to 
		 	//how many iterations that "pixel" took to 
		 	//"escape to infinity."  Three color schemes are provided.
		 	
			else if(colorScheme.equals("red")){
			   float myBlue = (float)Math.sqrt(Math.sqrt((double)iters/MAXITERS));
			   float myRed = (float)1.0 - (float)Math.sqrt(Math.sqrt((double)iters/MAXITERS));
			   float myGreen = (float)iters/MAXITERS;
			   g.setColor(new Color(myRed,myGreen,myBlue));
			   g.fillRect(i,j,1,1);  
			} 
			else if(colorScheme.equals("green")){
			  float myGreen = (float)Math.sqrt(Math.sqrt((double)iters/MAXITERS));
			  float myRed = (float)1.0 - (float)Math.sqrt(Math.sqrt((double)iters/MAXITERS));
			  float myBlue = (float)iters/MAXITERS;
			  g.setColor(new Color(myRed,myGreen,myBlue));
			  g.fillRect(i,j,1,1); 
			}
			else if(colorScheme.equals("blue")){
			  float myRed = (float)Math.sqrt(Math.sqrt((double)iters/MAXITERS));
			  float myBlue = (float)1.0 - (float)Math.sqrt(Math.sqrt((double)iters/MAXITERS));
			  float myGreen = (float)iters/MAXITERS;
			  g.setColor(new Color(myRed,myGreen,myBlue));
			  g.fillRect(i,j,1,1);
			}
			
		   }	
		}
	}//end paint method
	
	
	//This required method handles user events.
	//For this program, only events generated by
	//the menuBar are handled here.
	
	public void actionPerformed(ActionEvent e){		
	   if(e.getActionCommand().equals("Exit")){
		System.exit(0);
	   }
	   if(e.getActionCommand().equals("Refresh")){
            repaint();	
	   }
		
	   //Repaint with new color scheme if user
	   //selects "Red" "Green" or "Blue" from the
	   //Color menu.
		
	   if(e.getActionCommand().equals("red")){
	      colorScheme = "red";
		repaint();	
	   }
         if(e.getActionCommand().equals("green")){
		colorScheme = "green";
		repaint();	
	   }
	   if(e.getActionCommand().equals("blue")){
		colorScheme = "blue";
		repaint();	
	   }
	   if(e.getActionCommand().equals("zoom full")){
		//copied from main() to re-display the full set
		double initialLeft, initialRight, initialTop, initialBottom;
	      initialTop = 1.3;
	      initialBottom = -1.3;
	      initialRight = 1.0;
	      initialLeft = initialRight - (initialTop - initialBottom)*
		  (WIDTH - LEFTOFFSET - RIGHTOFFSET)/(double)(HEIGHT - TOPOFFSET + BOTTOMOFFSET);
		getData(new Complex(initialLeft,initialTop),
		   new Complex(initialRight,initialBottom));
		repaint();
	   }		
	   if(e.getActionCommand().equals("setIters100")){
		MAXITERS = 100;
		//aMenu100.setState(true);
		//aMenu200.setState(false);
		getData(upLeft,lowRight);
		repaint();	
	   }
	   if(e.getActionCommand().equals("setIters200")){
		MAXITERS = 200;
		//aMenu100.setState(false);
		//aMenu200.setState(true);
		getData(upLeft,lowRight);
		repaint();	
	   }
	}//End actionPerformed method
	
	//This method is required since Mandelbrot implements
	//MouseListener.  It gets the location of a mouse click
	//event, and "zooms in" on the region where the click occurred.
	
	public void mouseClicked(MouseEvent e){
	   double top, bottom, left, right, tall, wide;
	   int x = e.getX();
	   int y = e.getY();
	   System.out.println("x = " + x + " y = " + y);	
	   tall = upLeft.imag - lowRight.imag;
	   wide = lowRight.real - upLeft.real;
	   right = upLeft.real + (x - LEFTOFFSET)/(double)
		(WIDTH - LEFTOFFSET - RIGHTOFFSET) * wide + wide/5.0;
	   top = upLeft.imag - (y - TOPOFFSET)/(double)
		(HEIGHT - TOPOFFSET + BOTTOMOFFSET) * tall + tall/5.0;
	   bottom = top - tall/2.5;
	   left = right - wide/2.5;
	   getData(new Complex(left,top), new Complex(right,bottom));
	   repaint();
	}

	//The rest of these mouse methods are required also, since
	//the class Mandelbrot implements MouseListener

	public void mousePressed(MouseEvent e){}
	public void mouseReleased(MouseEvent e){}
	public void mouseEntered(MouseEvent e){}
	public void mouseExited(MouseEvent e){}

    //Finally, the main method.  Generates a new Mandelbrot object and
    //paints the start-up image.
    
	public static void main(String args[]) {
	   double initialLeft, initialRight, initialTop, initialBottom;
	   initialTop = 1.3;
	   initialBottom = -1.3;
	   initialRight = 1.0;
	   initialLeft = initialRight - (initialTop - initialBottom)*
		(WIDTH - LEFTOFFSET - RIGHTOFFSET)/(double)(HEIGHT - TOPOFFSET + BOTTOMOFFSET);
	   Mandelbrot myWindow = new Mandelbrot();
	   getData(new Complex(initialLeft,initialTop),
		new Complex(initialRight,initialBottom));
	   myWindow.setVisible(true);	
	}
	
}//End Mandelbrot class
