/*----------------------------------------------------------------------+
|   Title:  MandelbrotColorPlane.java                                   |
|                                                                       |
|   Author: David E. Joyce                                              |
|           Department of Mathematics and Computer Science              |
|           Clark University                                            |
|           Worcester, MA 01610-1477                                    |
|           U.S.A.                                                      |                                                                       |
|           http://aleph0.clarku.edu/~djoyce/                           |
|                                                                       |
|   Date:   January, 2003.                                              |
+----------------------------------------------------------------------*/

import java.awt.Color;

public class MandelbrotColorPlane extends ColorPlane {

  private int resolution;
  private int parPlane;
  private int wrap;
  private int escape;
  private int pattern;
  private double bound, bound2;

  public static final double MYERBERG = 1.401155189;

  public static final int MU = 0;
  public static final int LAMBDA = 1;              //  mu = lambda^2/4 - lambda/2
  public static final int RECIPMU = 2;             //   1/mu
  public static final int RECIPMUPLUSFOURTH = 3;   //   1/(mu+.25)
  public static final int RECIPLAMBDA = 4;         //   1/lambda
  public static final int RECIPLAMBDAMINUSONE = 5; //   1/(lambda-1)
  public static final int RECIPMUMINUSMYER = 6; //   1/(mu-1.401)

  public static final int CIRCLE_ESCAPE = 0;
  public static final int SQUARE_ESCAPE = 1;
  public static final int STRIP_ESCAPE = 2;
  public static final int HALFPLANE_ESCAPE = 3;

  public static final int PLAIN_PATTERN = 0;
  public static final int FEATHERED_PATTERN = 1;
  public static final int BINARY_PATTERN = 2;
  public static final int GRAYSCALE_PATTERN = 3;
  public static final int ZEBRA_PATTERN = 4;
  private static final double[] BOUND_VALUE = {2.0, 20.0, 5.0, 2.0, 5.0};

  public MandelbrotColorPlane (int resolution, int parPlane, int wrap, int escape, int pattern) {
    setResolution(resolution);
    setParPlane(parPlane);
    setWrap(wrap);
    setEscape(escape);
    setPattern(pattern);
  }

  public void setResolution (int resolution) { this.resolution = resolution; }

  public void setParPlane (int parPlane) { this.parPlane = parPlane; }

  public void setWrap (int wrap) { this.wrap = wrap; }

  public void setEscape (int escape) { this.escape = escape; }

  public void setPattern (int pattern) {
    this.pattern = pattern;
    bound = BOUND_VALUE[pattern];
    bound2 = bound*bound;
 }

  public static Complex convert (Complex z, int oldPlane, int newPlane) {
    if (oldPlane == newPlane)
       return new Complex(z);
    // first convert from the old plane to either the lambda or the mu plane
    if (oldPlane != MU && oldPlane != LAMBDA) {
      z = z.reciprocal();
      if (oldPlane == RECIPMUPLUSFOURTH)
        z = z.minus(0.25);
      else if (oldPlane == RECIPLAMBDAMINUSONE)
        z = z.plus(1.0);
      else if (oldPlane == RECIPMUMINUSMYER)
        z = z.plus(MYERBERG);
    } // if
    // next, convert to mu or lambda as necessary
    if (oldPlane==LAMBDA || oldPlane==RECIPLAMBDA || oldPlane==RECIPLAMBDAMINUSONE) {
      if (newPlane!=LAMBDA && newPlane!=RECIPLAMBDA && newPlane!=RECIPLAMBDAMINUSONE) {
        // convert lambda to mu.  mu = (lambda/2)^2 - (lambda/2)
        z = z.over(2.0);
        z = z.times(z).minus(z);
      } // if
    } else {
      if (newPlane==LAMBDA || newPlane==RECIPLAMBDA || newPlane==RECIPLAMBDAMINUSONE) {
        // convert mu to lambda.  lambda = 1 + sqrt(1+4mu)
        z = z.times(4.0).plus(1.0);
        z = z.sqrt().plus(1.0);
      } // if
    } // if/else
    // finally, convert to the new plane
    switch (newPlane) {
      case MU: return z;
      case LAMBDA: return z;
      case RECIPMU: return z.reciprocal();
      case RECIPMUPLUSFOURTH: return z.plus(0.25).reciprocal();
      case RECIPLAMBDA: return z.reciprocal();
      case RECIPLAMBDAMINUSONE: return z.minus(1.0).reciprocal();
      case RECIPMUMINUSMYER: return z.minus(MYERBERG).reciprocal();
    } // switch
    return z;  // never used, but makes compiler happy
  } // convert

  private boolean escaped (double s, double t) {
    switch (escape) {
      case CIRCLE_ESCAPE:
        return s*s + t*t >= bound2;
      case SQUARE_ESCAPE:
        return Math.abs(s) >= bound || Math.abs(t) >= bound;
      case STRIP_ESCAPE:
        return Math.abs(s) >= bound;
      case HALFPLANE_ESCAPE:
        return s >= bound;
    } // switch
    return true; // never used, but makes compiler happy
  } // escaped

  // Determine whether the point z=x+iy lies in the Mandelbrot set or not.
  public Color valueAt (double x, double y) {
    Complex z = new Complex(x,y);
    // First, change (x,y) to be a point in the mu-plane, if it isn't in it yet.
    z = convert(z,parPlane,MU);
    x = z.x;
    y = z.y;

    // Next check to see if z + 1/4 lies inside the cardioid
    // given by the polar inequality  r < (1+cos theta)/2.  In rectangular
    // coordinates this becomes  (s^2 + t^2 - s/2)^2 < (s^2 + t^2)/4 where
    // (s,t) is (x + 1/4, y).  If so, z lies inside the set
    double s = x + 0.25;
    double t = y;
    double ss = s*s;
    double tt = y*y;
    double left = (ss + tt - s*0.5);
    if (left*left < (ss + tt)*0.25)
      return Color.black;

    // Now see how many iterates of 0 it takes to reach the escape criterion
    // These computations do not use the Complex class for sake of speed
    s = 0.0;
    t = 0.0;
    int k = 0;
    for (k = 0; !escaped(s,t) && k < resolution; k++) {
      ss = s*s - t*t;
      tt = 2.0 * s * t;
      s = ss - x;
      t = tt - y;
    } // for

    if (k == resolution)
      return Color.black;

    if (pattern == ZEBRA_PATTERN)
      return (k%2 == 0)? Color.black : Color.white;

    if (pattern == FEATHERED_PATTERN) {
      if (Math.abs(s)<bound || Math.abs(t)<bound)
        return Color.black;
    }

    if (pattern == GRAYSCALE_PATTERN) {
      double bright = (wrap*k%resolution)/(0.0+resolution);
      if (bright < 0.5)
        bright = 1.0 - 1.2*bright;
      else
        bright = -0.2 + 1.2*bright;
      int cidx = Color.HSBtoRGB (0.0f, 0.0f, (float)(bright));
      return new Color(cidx);
    }

    double hue = (wrap*k%resolution)/(0.0+resolution);
    if (pattern == BINARY_PATTERN && t > 0)
      hue = (hue<0.8)? hue+0.2 : hue-0.8;
    int cidx = Color.HSBtoRGB ((float)(hue), 1.0f, 1.0f);
    return new Color(cidx);
  }

  public String toString() {
    return "["+resolution+","+parPlane+","+wrap+","+escape+","+pattern+"]" ;
  } // toString

} // MandelbrotColorPlane
