//------------------------------------------------------------------------------
// MandelbrotPanel.java (Mandelbrot Applet 1.05)
// Copyright (C) 2001, 2002 by Alexander Adam
// Tested with Java 1.2.2
// No Warranty of any kind.
//------------------------------------------------------------------------------
import java.awt.*;
import java.awt.event.*;

/** 
  * MandelbrotPanel class<br>
  * handles a Panel that calculates and paints mandelbrot sets
  */
public class MandelbrotPanel extends Panel {
  private Image image;
  private Graphics graphics;
  private Mandelbrot mandelbrot;

  private boolean calculating = true;
  private boolean keepProportions = true;
  private boolean areaSelected = false;
  private boolean areaSelectionEnabled = false;

  //
  // mandelbrot set (initial) parameters
  //
  private double x0 = -0.5f;            // c0: real part (x)
  private double y0 = 0.0f;             // c0: imaginary part (y)

  private int size_x;                   // (image) size - width
  private int size_y;                   // (image) size - height
  private int size_x2;                  // for size_x / 2
  private int size_y2;                  // for size_y / 2

  private int colors = 64;              // max calculation depth
  private float super_hue = 0.0f;
  private float hue_offset = 0.0f;
  private float hue_range  = 1.0f;

  private double range_x = 3.2f;        // calculation x-Range
  private double range_y = 2.4f;        // calculation y-Range

  private double prec_x;                // calculation steps ( precision_x )
  private double prec_y;                // calculation steps ( precision_y )

  //
  // selected area varaiables
  //
  private int selX0;
  private int selY0;
  private int selX1;
  private int selY1;

  MandelbrotPanel( Mandelbrot parent ) {
    super();
    mandelbrot = parent;
    if (mandelbrot != null) {
      addMouseListener( new MandelbrotMouseListener() );
      addMouseMotionListener( new MandelbrotMouseMotionListener() );
    }
  }

  MandelbrotPanel( Mandelbrot parent, double c0x, double c0y,
                   double rangeX, double rangeY, int maxCol,
                   boolean prop ) {
    super();

    mandelbrot = parent;
    x0 = c0x;
    y0 = c0y;
    range_x = rangeX;
    range_y = rangeY;
    colors = maxCol;
    keepProportions = prop;

    if (mandelbrot != null) {
      addMouseListener( new MandelbrotMouseListener() );
      addMouseMotionListener( new MandelbrotMouseMotionListener() );
    }
  }

  //
  // public getter functions
  //
  public double getX0() {
    return x0;
  }

  public double getY0() {
    return y0;
  }

  public double getRangeX() {
    return range_x;
  }

  public double getRangeY() {
    return range_y;      
  }

  public int getColors() {
    return colors;      
  }

  public boolean getProp() {
    return keepProportions;      
  }

  public float getHueOffset() {
    return hue_offset;
  }

  public float getHueRange() {
    return hue_range;
  }

  public float getSuperHue() {
    return super_hue;
  }

  //
  // public setter functions
  //
  public void setX0( double x ) {
    x0 = x;
  }

  public void setY0( double y ) {
    y0 = y;
  }

  public void setRangeX( double x ) {
    range_x = x;
  }

  public void setRangeY( double y ) {
    range_y = y;      
  }

  public void setColors( int c ) {
    colors = c;      
  }

  public void setProp( boolean prop ) {
    keepProportions = prop;
  }

  public void setHueOffset( float hue ) {
    hue_offset = hue;
  }

  public void setHueRange( float hue ) {
    hue_range = hue;
  }

  public void setSuperHue( float hue ) {
    super_hue = hue;
  }

  /**
   * calculate mandelbrot set
   *
   * see James Gleick - CHAOS, page 231ff
   *
   * z' = z^2 + c
   *  c = x + yi ( complex number )
   *  z0 = 0
   *
   * -> z'  = x + yi
   * -> z'' = ( x' + y'i )^2 + x + yi
   *        = x'^2 + 2x'y'i + (y'i)^2 + x + yi
   *        = x'^2 + 2x'y'i - y'^2 + x + yi    ( i^2 := -1 )
   *        = x+x'^2-y'^2 + (y+2x'y')i
   *
   *        := x'' + y'' i
   */
  public void calculate() {
    int sx = getSize().width;          // get width
    int sy = getSize().height;         // get height

    calculate(sx,sy);
  }

  public void calculate(int sx, int sy) {
    //
    // Initialize Variables
    //
    size_x = sx;
    size_y = sy;
    prec_x = range_x / (double)size_x;  // calculation steps ( precision_x )
    prec_y = range_y / (double)size_y;  // calculation steps ( precision_y )

    if (keepProportions) {
      if (prec_x > prec_y) {
        prec_y = prec_x;
        size_y = (int)(range_y / prec_y);
      } else if (prec_y > prec_x) {
        prec_x = prec_y;
        size_x = (int)(range_x / prec_x);
      }
    }

    size_x2 = size_x / 2;
    size_y2 = size_y / 2;

    //
    // Initialize offscreen Image and Graphics
    //
    image = createImage( size_x, size_y );
    graphics = image.getGraphics();

    //
    // Calculate Mandelbrot Set
    //
    calculating = true;

    float br = (float)1.0;
    if (super_hue == (float)0.0) {
      br = (float)0.0;
    }

    // for percent info
    int percent = 10;

    double px0 = x0 - prec_x * (double)size_x2;
    for ( int x = 0; x < size_x; x++ ) {
      double py0 = y0 - prec_y * (double)size_y2;

      for ( int y = 0; y < size_y ; y++ ) {
        graphics.setColor(Color.getHSBColor(super_hue, (float)1.0, br));

        double px = px0;
        double py = py0;

        for ( int col = 0; col < colors; col++ ) {
          double px1 = px0+px*px-py*py;
          py = py0+2*px*py;
          px = px1;

          if ( Math.abs(px) > 2.0 || Math.abs(py) > 2.0 ) {
            float c = ((float)col / (float)colors)*hue_range + hue_offset;
            if (c > 1.0f) {
              c -= 1.0f;
            }

            graphics.setColor(Color.getHSBColor(c, (float)1.0,(float)1.0));

            break;
          }
        }

        graphics.drawLine(x,y,x,y);

        py0 += prec_y;
      }
      px0 += prec_x;

      if (mandelbrot != null) {
        if (100*x/size_x >= percent) {
          mandelbrot.getAppletContext().showStatus(percent+"%");
          percent += 10;
        }
      }
    }

    calculating = false;
    areaSelected = false;
    areaSelectionEnabled = true;
  }

  /**
   * paint() for MandelbrotPanel
   */
  public void paint(Graphics screen) {
    // paint mandelbrot image
    if ( image != null ) {
      int sx = getSize().width;         // get current width
      int sy = getSize().height;        // get current height
      screen.drawImage( image, (sx-size_x)/2, (sy-size_y)/2, size_x, size_y, this );
    }
  }

  /**
   * destroy() for MandelbrotPanel graphics
   */
  public void destroy() {
    // dispose graphics
    graphics.dispose();
  }

  /**
   * MandelbrotMouseMotionListener
   */
  private class MandelbrotMouseListener implements MouseListener {

    public void mouseClicked( MouseEvent evt ) {
    }

    public void mouseEntered( MouseEvent evt ) {
    }

    public void mouseExited( MouseEvent evt ) {
    }

    public void mousePressed( MouseEvent evt ) {
    }

    public void mouseReleased( MouseEvent evt ) {
      if ( areaSelected ) {
        areaSelected = false;
        areaSelectionEnabled = false;

        // calculate parameters for new set from selected area
        int mx = Math.min(selX0,selX1)+Math.abs(selX1-selX0)/2;
        int my = Math.min(selY0,selY1)+Math.abs(selY1-selY0)/2;
        x0 += (mx - size_x/2) * prec_x;
        y0 += (my - size_y/2) * prec_y;
        range_x *= (double)Math.abs(selX1-selX0)/(double)size_x;
        range_y *= (double)Math.abs(selY1-selY0)/(double)size_y;

        mandelbrot.stop();
        mandelbrot.start();
      }
    }
  }

  /**
   * MandelbrotMouseMotionListener
   */
  private class MandelbrotMouseMotionListener implements MouseMotionListener {

    private int realX1;
    private int realY1;

    public void mouseMoved(MouseEvent evt) {
    }

    public void mouseDragged(MouseEvent evt) {
      if (areaSelectionEnabled) {
        int sx = getSize().width;       // get current width
        int sy = getSize().height;      // get current height
        int x = evt.getX() - (sx-size_x)/2;
        int y = evt.getY() - (sy-size_y)/2;

        if ( !areaSelected ) {
          areaSelected = true;
          realX1 = selX1 = selX0 = x;
          realY1 = selY1 = selY0 = y;
        } else if ( x != selX1 || y != selY1 ) {
          if (keepProportions) {
            if (x!=realX1) {
              // x-position has changed
              realX1 = x;
              realY1 = y;
              // adjust y-value
              if ( selY0 <= selY1 ) {
                y = selY0 + Math.abs( x - selX0 ) * sy / sx;
                if ( y < realY1 ) {
                  if ( ( selX0 <= x && x <= selX1 ) || ( selX0 >= x && x >= selX1 ) ) {
                    return;
                  }
                }
              } else {
                y = selY0 - Math.abs( x - selX0 ) * sy / sx;
                if ( y > realY1 ) {
                  if ( ( selX0 <= x && x <= selX1 ) || ( selX0 >= x && x >= selX1 ) ) {
                    return;
                  }
                }
              }
            } else if (y!=realY1) {
              // y-position has changed
              realX1 = x;
              realY1 = y;
              // adjust x-value
              if ( selX0 <= selX1) {
                x = selX0 + Math.abs( y - selY0 ) * sx / sy;
                if ( x < realX1 ) {
                  if ( ( selY0 <= y && y <= selY1 ) || ( selY0 >= y && y >= selY1 ) ) {
                    return;
                  }
                }
              } else {
                x = selX0 - Math.abs( y - selY0 ) * sx / sy;
                if ( x > realX1 ) {
                  if ( ( selY0 <= y && y <= selY1 ) || ( selY0 >= y && y >= selY1 ) ) {
                    return;
                  }
                }
              }
            }
          }
          graphics.setXORMode(Color.white);
          graphics.drawRect(Math.min(selX0,selX1),Math.min(selY0,selY1),Math.abs(selX1-selX0),Math.abs(selY1-selY0));
          graphics.drawRect(Math.min(selX0,x),Math.min(selY0,y),Math.abs(x-selX0),Math.abs(y-selY0));
          repaint();
          selX1 = x;
          selY1 = y;
        }
      }
    }
  }
}

