import java.awt.event.*;
import java.awt.*;
import java.applet.*;

public class Spiro extends Applet {

    private Color[] colorSequence;
    private int period1;
    private int period2;
    private double radius1;
    private double radius2;
    private int max_iter;
    private boolean flipDir;
    private boolean colorBy;

    private void pickRandomSpiro() {
        period1 = 10 + (int) (Math.random() * 100);
        period2 = 10 + (int) (Math.random() * 100);
        max_iter = period1*period2/gcd(period1,period2);
        double tmp = Math.random();
        radius1 = 1.0 - tmp;
        radius2 = tmp;
        flipDir = Math.random() < 0.5;
        colorBy = Math.random() < 0.5;
        colorSequence = new Color[2+(int)(Math.random()*7)];
        for(int i = 0;i<colorSequence.length;i++) {
            colorSequence[i] = new Color((float)Math.random(), (float)Math.random(), (float)Math.random());
        }
    }

    private final int gcd(int a, int b) {
        if(b == 0) { 
            return a;
        } else {
            return gcd(b, a%b);
        }
    }

    protected void processMouseEvent(MouseEvent me) {
        if(me.getID() == MouseEvent.MOUSE_CLICKED) {
            pickRandomSpiro();
            repaint();
        }
    }

    public void start() {
    }
    public void stop() {
    }
    public void init() {
        pickRandomSpiro();
        enableEvents(AWTEvent.MOUSE_EVENT_MASK);
    }

    private static final int POINTS_PER_CYCLE = 25;
    public void paint(Graphics g) {
	Dimension d = getSize();
        int counter = 0;
        double x = calcX(counter);
        double y = calcY(counter);

        g.setColor(Color.black);
        g.fillRect(0,0,d.width,d.height);
        while(counter < max_iter * POINTS_PER_CYCLE) {
            g.setColor(calcColor(counter));
            counter++;
            double newX = calcX(counter);
            double newY = calcY(counter);
            g.drawLine(
                    (int)((1.0+x)*d.width/2),
                    (int)((1.0+y)*d.height/2),
                    (int)((1.0+newX)*d.width/2),
                    (int)((1.0+newY)*d.height/2)
                    );
            x = newX;
            y = newY;
        }
    }

    public double calcX(int counter) {
        double c = 1.0*counter/POINTS_PER_CYCLE;
        double angle1 = (c/period1)*2*Math.PI;
        double angle2 = (c/period2)*2*Math.PI;
        if(flipDir) angle2 = -angle2;

        return Math.sin(angle1)*radius1+Math.sin(angle2)*radius2;
    }

    public double calcY(int counter) {
        double c = 1.0*counter/POINTS_PER_CYCLE;
        double angle1 = (c/period1)*2*Math.PI;
        double angle2 = (c/period2)*2*Math.PI;
        if(flipDir) angle2 = -angle2;

        return Math.cos(angle1)*radius1+Math.cos(angle2)*radius2;
    }

    public Color calcColor(int counter) {
        double c = 1.0*counter/POINTS_PER_CYCLE;
        double segment = colorSequence.length*c/(colorBy ? period1 : period2);
        int a = (int) Math.floor(segment);
        double per = segment % 1.0;
        Color col1 = colorSequence[a%colorSequence.length];
        Color col2 = colorSequence[(a+1)%colorSequence.length];
        int r = (int) ((1.0-per)*col1.getRed() + per*col2.getRed());
        int g = (int) ((1.0-per)*col1.getGreen() + per*col2.getGreen());
        int b = (int) ((1.0-per)*col1.getBlue() + per*col2.getBlue());
        return new Color(r,g,b);
    }
}
