import java.awt.*;

    
    
/**
 * FlakeDrawer does the real work.
 */
public class FlakeDrawer extends Panel {
    TwoVector points[];		// Equivalent to TwoVector [] points;
    TwoVector subvectors[];
    int depth = 5, maxdepth;
    boolean docolor = true;
    boolean dographics = true;
    boolean doublebuffered = true;
    Image offscreen;


    public FlakeDrawer(float [][] points, float [][] subdivision, int maxdepth) {
	// Convert data to convenient form: arrays of TwoVectors

	try { depth = Integer.parseInt( System.getProperty("FLAKEDEPTH") );
	} catch(Exception e) {;}
	try { dographics = System.getProperty("FLAKEGRAPHICS") != null;
	} catch(Exception e) {;}
	try { doublebuffered = System.getProperty("FLAKEDOUBLE") != null;
	} catch(Exception e) {;}
	try { docolor = System.getProperty("FLAKECOLOR") != null;
	} catch(Exception e) {;}

	this.points = new TwoVector[points.length];

	for(int i = 0; i < points.length; i++)
		this.points[i] = new TwoVector(points[i]);

	TwoVector prev = new TwoVector();
	this.subvectors = new TwoVector[subdivision.length];
	this.subvectors[0] = new TwoVector( subdivision[0] );
	for(int i = 1; i < subdivision.length; i++) {
		subvectors[i] = new TwoVector( subdivision[i] );
		prev.setData( subdivision[i-1] );
		subvectors[i].subtract( prev );
	}

	this.maxdepth = maxdepth;
    }

    public void drawFlake(Graphics g, Rectangle box, int depth) {
	TwoVector vec = new TwoVector();
	TwoVector base = new TwoVector();
	// Fit as well as we can into the given drawing area.
	TwoVector center = new TwoVector( box.x + box.width/2,
					  box.y + box.height/2 );
	float scale = Math.min(box.width, box.height) / 2f;
	for(int i = 0; i < points.length; i++) {
	    vec.lincomb( scale, points[ (i+1) % points.length ],
			-scale, points[i] );
	    base.lincomb( scale, points[i], 1, center );
	    drawSeg(g, depth, base, vec);
	}
    }

    // Respond to just one event.  Refine our snowflake.
    public boolean mouseDown( Event e, int x, int y ) {
	depth = (depth+1) % maxdepth;
	repaint();
	return true;	// We've handled it.
    }

    // Well, OK, two events.
    public boolean keyDown( Event e, int key ) {
	if(key == 'q')
	    System.exit(0);
	else if(key >= '0' && key <= '9')
	   depth = key - '0';
	else {
	   docolor = (key & 1) != 0;
	   dographics = (key & 2) != 0;
	   doublebuffered = (key & 4) != 0;
	}
	repaint();
	return false;
    }
	   

    public synchronized void update(Graphics screeng) {
	paint(screeng);
    }

    public void paint(Graphics screeng) {
	Rectangle area = bounds();
	Graphics g;

	if(doublebuffered) {
	    /* If we're double-buffering, we need an (off-screen) image to
	     * draw into.  Keep one lying around in "offscreen".
	     * If that hasn't yet been initialized, or if our size has
	     * changed since then, then create a new one.
	     */
	    if(offscreen == null
			    || offscreen.getWidth(null) != area.width
			    || offscreen.getHeight(null) != area.height) {
		offscreen = createImage(area.width, area.height);
System.out.println("image " + area.width + "x" + area.height);
	    }
	    /* We'll render into the off-screen image's graphics area.
	     * Start by clearing it to the background color.
	     */
	    g = offscreen.getGraphics();
System.out.println("off-screen " + g);
	} else {
	    /* Otherwise, we're single-buffered; render directly to the screen.  */
	    g = screeng;
System.out.println("on-screen " + g);
	}

	g.setColor(getBackground());
	g.clearRect(0, 0, area.width, area.height);
	g.setColor(getForeground());
	dopaint(g);

	/*
	 * If we were double-buffering, finish by copying the image we just
	 * drew into the relevant piece of the screen.
	 */
	if(doublebuffered) {
System.out.println("drawImage");
	    screeng.drawImage(offscreen, 0, 0, this);
	}
    }

    public void dopaint(Graphics g) {

	setForeground( Color.black );
	long before = System.currentTimeMillis();
	drawFlake( g, bounds(), depth );
	long delay = System.currentTimeMillis() - before;
	float rate = 1000*((float)(3 << (2*depth))) / delay;
	System.out.println( "depth " + depth + " took " +
		+ delay + " ms for "
		+ (3<<(2*depth)) + " pts (" + rate + "/sec) "
		+ (docolor ? "" : "no ") + "color, "
		+ (dographics ? "" : "no ") + "getGraphics(), "
		+ (doublebuffered ? "" : "no ") + "double-buffer, into " + g);
	g.drawString("Depth " + depth,  5, size().height - 5);
    }

    public void drawSeg(Graphics g, int depth, TwoVector base, TwoVector vec) {
	if(depth > 0) {

	    // Subdivide.  First, make a "basis" for our new coordinate system.
	    // "vec" plays role of X, "perp" plays Y.

	    TwoVector perp = vec.perpTo();
	    TwoVector subbase = (TwoVector)base.clone();
	    TwoVector subvec = new TwoVector();

	    for(int i = 0; i < subvectors.length; i++) {
		subvec.setData( subvectors[i].dot(vec), subvectors[i].dot(perp) );

		// Recurse.
		drawSeg(g, depth-1, subbase, subvec);
		subbase.add( subvec );
	    }

	} else {
	    // depth <= 0.  We've recursed enough -- actually draw something.

	    float p[] = base.getData();
	    float v[] = vec.getData();
//	    g.drawLine( (int)p[0], (int)p[1], (int)(p[0]+v[0]), (int)(p[1]+v[1]) );
	    Graphics gg = g;
	    if(dographics)
		gg = getGraphics();
	    if(docolor)
		gg.setColor( (((int)p[0]) & 1) == 0 ? Color.black : Color.red );
	    gg.drawLine( (int)p[0], (int)p[1], (int)(p[0]), (int)(p[1]) );
	}
    }
}
		

    

/**
 * N-dimensional vector class, with basic linear operations.
 */
class NVector extends Object implements Cloneable {
   // Our sole instance variable.
   float data[];

   /**
    * This isn't very useful, but Java requires that we offer a
    * constructor with no arguments if we have subclasses.
    * See the Nutshell book, p. 65.
    */

   public NVector() {
    /* We could explicitly invoke one of the other constructors here.
     * For example, writing
     *	this(3);
     * would mean that a ``new NVector()'' would be 3-D by default.
     */
   }

   public NVector(int dim) {
	data = new float[dim];
   }

   public NVector(float initialdata[]) {
	copyData( initialdata );
   }

   /**
    * Return a copy of this NVector.
    */
   public Object clone() {
	return new NVector(this.data);
   }

   /**
    * Get vector data.
    */
   float [] getData() {
	return this.data;
   }

   /**
    * Set vector data.
    */
   void setData(float newdata[]) {
	data = newdata;
   }

   /**
    * Set vector data, making a copy.
    */
   void copyData(float newdata[]) {
	data = new float[newdata.length];
	for(int i = 0; i < data.length; i++)
	    data[i] = newdata[i];
   }

   /**
    * Set vector's data to match that of some other vector.
    */
   public NVector copyFrom( NVector v ) {
	copyData( v.data );
	return this;
   }

   /**
    * Return dimension of this vector.
    */
   public int dim() {
	return this.data.length;	// Ask our array for its length
   }

   /**
    * (Euclidean) inner product of this vector with another.
    */
   float dot(NVector v) {
	float sum = 0;
	int i;
	int dim = v.dim();
	if(dim > this.dim())	//  "this.dim()" is synonymous with "dim()"
	    dim = this.dim();
	for(i = 0; i < dim; i++) {
	    sum += data[i] * v.data[i];
	}
	return sum;
   }

   /**
    * (Euclidean) magnitude of this vector.
    */
   float mag() {
	return (float)Math.sqrt(dot(this)); // Take inner product with self.
   }

   /**
    * Linear combination: this = a*av + b*bv
    */
   public void lincomb(float a, NVector av, float b, NVector bv) {
	int dim = Math.max(av.dim(), bv.dim());
	float newdata[];
	newdata = (data != null && data.length == dim) ? data : new float[dim];
	for(int i = 0; i < dim; i++) {
	    newdata[i] = a*av.data[i] + b*bv.data[i];
	}
	data = newdata;
   }

   /**
    * Linear combination: this = a*this + b*bv
    */
   public void lincomb(float a, float b, NVector bv) {
	lincomb(a, this, b, bv);
   }
	
   /**
    * Subtract another vector v from this one.
    */
   public void subtract(NVector v) {
	lincomb(1,this, -1,v);
   }

   /**
    * Add vector v to this one.
    */
   public void add(NVector v) {
	lincomb(1,this, 1,v);
   }
}

class TwoVector extends NVector {


   public TwoVector() {
	data = new float[2];
   }

   public TwoVector(float x, float y) {
	this();
	data[0] = x;
	data[1] = y;
   }

   public Object clone() {
	return new TwoVector(this.data);
   }

   // Let the NVector( float[] data ) code do the work.
   // We could check that the data supplied here really has two components.
   public TwoVector( float[] data ) {
	super( data );
   }

   /**
    * Return new vector perpendicular to this one
    */
   TwoVector perpTo() {
	return new TwoVector(data[1], -data[0]);
   }

   void setPerpTo( TwoVector tv ) {
	float t = tv.data[0];
	data[0] = tv.data[1];
	data[1] = -t;
   }

   void setData(float x, float y) {
	data[0] = x;
	data[1] = y;
   }

   public TwoVector copyFrom( TwoVector v ) {
	copyData( v.data );
	return this;
   }
}

