/*----------------------------------------------------------------------+
|   Title:  PoincareDisk.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:   April, 1987.  Pascal version for tektronix terminal         |
|           December, 2002.  Java applet version                        |
+----------------------------------------------------------------------*/

import java.awt.*;

public class PoincareDisk extends Canvas{

  TileParameters par;// parameters
  boolean alternating;

  Polygon P[];  // the list of polygons
  int rule[];   // previously created neighbors for the polygons
  int totalPolygons; // the total number of polygons in all the layers
  int innerPolygons; // the number through one less layer
  Color C[]; // this list of colors for the polygons

  public PoincareDisk(TileParameters par) {
    this.par = par;
  } // Poincare constructor

  public void init() {
    alternating = par.alternating && (par.k%2 == 0);
    countPolygons(par.layers) ;
    determinePolygons();
  } // init

  private void countPolygons(int layer) {
    // Determine
    //   totalPolygons:  the number of polygons there are through that many layers
    //   innerPolygons:  the number through one less layer
    totalPolygons = 1;    // count the central polygon
    innerPolygons = 0;
    int a = par.n*(par.k-3) ;  // polygons in first layer joined by a vertex
    int b = par.n ;        // polygons in first layer joined by an edge
    int next_a, next_b;
    if (par.k == 3) {
      for (int l=1; l<=layer; ++l) {
        innerPolygons = totalPolygons;
        next_a = a + b;
        next_b = (par.n-6)*a + (par.n-5)*b;
        totalPolygons +=  a + b;
        a = next_a;
        b = next_b;
      } // for
    } else { // k >= 4
      for (int l=1; l<=layer; ++l) {
        innerPolygons = totalPolygons;
        next_a = ((par.n-2)*(par.k-3) - 1) * a
               + ((par.n-3)*(par.k-3) - 1) * b;
        next_b = (par.n-2)*a + (par.n-3)*b;
        totalPolygons +=  a + b;
        a = next_a;
        b = next_b;
      } // for
    } // if/else
  } // countPolygons

 /* rule codes
  *   0:  initial polygon.  Needs neighbors on all n sides
  *   1:  polygon already has 2 neighbors, but one less around corner needed
  *   2:  polygon already has 2 neighbors
  *   3:  polygon already has 3 neighbors
  *   4:  polygon already has 4 neighbors
  */

  private void determinePolygons() {
    P = new Polygon[totalPolygons];
    rule = new int[totalPolygons];
    C = new Color[totalPolygons];
    P[0] = Polygon.constructCenterPolygon(par.n,par.k,par.quasiregular);
    rule[0] = 0;
    C[0] = randomColor();
    int j = 1; // index of the next polygon to create
    for (int i=0; i<innerPolygons; ++i)
      j = applyRule(i,j);
  } // determinePolygons

  private int applyRule(int i, int j) {
    int r = rule[i];
    boolean special = (r==1);
    if (special) r=2;
    int start = (r==4)? 3 : 2;
    int quantity = (par.k==3 && r!=0)? par.n-r-1 : par.n-r;
    for (int s=start; s<start+quantity; ++s) {
      // Create a polygon adjacent to P[i]
      P[j] = createNextPolygon(P[i],s%par.n);
      rule[j] = (par.k==3 && s==start && r!=0)? 4 : 3;
      if (alternating && j>1)
        C[j] = (C[i] == C[0])? C[1] : C[0];
      else
        C[j] = randomColor();
      j++;
      int m;
      if (special) m=2;
      else if (s==2 && r!=0) m=1;
      else m=0;
      for ( ; m<par.k-3; ++m) {
        // Create a polygon adjacent to P[j-1]
        P[j] = createNextPolygon(P[j-1],1);
        rule[j] = (par.n==3 && m==par.k-4)? 1 : 2;
        if (alternating)
          C[j] = (C[j-1] == C[0])? C[1] : C[0];
        else
          C[j] = randomColor();
        j++;
      } // for m
    } // for r
    return j;
  } // applyRule
  

  // reflect P thru the point or the side indicated by the side s
  //  to produce the resulting polygon Q
  private Polygon createNextPolygon (Polygon P, int s) {
    Polygon Q = new Polygon(par.n);
    if (par.quasiregular) {
      Point V = P.getVertex(s);
      for (int i=0; i<par.n; ++i) { // reflect P[i] thru P[s] to get Q[j]
        int j = (par.n+i-s) % par.n;
        Q.setVertex(j, V.reflect(P.getVertex(i)));
      } // for
    } else { // regular
      Line C = new Line(P.getVertex(s), P.getVertex((s+1)%par.n)) ;
      for (int i=0; i<par.n; ++i) { // reflect P[i] thru C to get Q[j]}
        int j = (par.n+s-i+1) % par.n;
        Q.setVertex(j, C.reflect(P.getVertex(i)));
      } // for
    } // if/else
    return Q;
  } // computeNextPolygon

  private Color randomColor() {
    int c;
    if (par.grayScale)
      c = Color.HSBtoRGB (0.0f,	// hue irrelevant
          0.0f,   // no satuation
          (float)(Math.random()));   // brightness
    else
      c = Color.HSBtoRGB ((float)(Math.random()*1.0),	// hue
          (float)(Math.random()),	// saturation
          1.0f);			// full brightness
    return new Color(c);
  }
  
  private int gcd(int m, int n) {  // greatest common divisor
    if (m < 0) m = -m;   // Make sure m and n
    if (n < 0) n = -n ;  // are nonnegative.
	if (m > n) {         // Make sure m <= n. }
	  int temp = n;
	  n = m;
	  m = temp;
	} // if
	while (m != 0) {
	  int temp = n;
	  n = m;
	  m = temp % m;
	} // while
	return n;
  } // gcd

  public void update(Graphics g) {
    Dimension d = getSize();
    int x_center = d.width/2;
    int y_center = d.height/2;
    int radius = Math.min(x_center,y_center);
    int diameter = 2*radius;
    g.setColor(par.bgColor);
    g.fillRect(0,0,d.width,d.height);
    g.setColor(par.diskColor);
    g.fillOval(x_center-radius,y_center-radius,diameter,diameter);
    int stars = gcd(par.skipNumber,par.n);
    int pointsPerStar = par.n/stars;
    for (int i=0; i<totalPolygons; ++i) {
      if (par.fill) {
        g.setColor(C[i]);
        for (int s=0; s<stars; ++s) {
          Polygon Q = new Polygon(pointsPerStar);
          for (int j=0; j<pointsPerStar; ++j)
            Q.setVertex (j, P[i].getVertex(j*par.skipNumber%par.n + s));
          Q.fill(g,d);
        } // for s
      } // if
      if (par.outline || !par.fill) {
        if (par.outline) 
          g.setColor(Color.black);
        else 
          g.setColor(C[i]);
        for (int s=0; s<stars; ++s) {
          Polygon Q = new Polygon(pointsPerStar);
          for (int j=0; j<pointsPerStar; ++j)
            Q.setVertex (j, P[i].getVertex(j*par.skipNumber%par.n + s));
          Q.draw(g,d);        
        } // for s
      } // if
    } // for i
  } // draw

  public void paint(Graphics g) {
    repaint();
  }

} // PoincareDisk