/*
 * Copyright (C) 1998  Ralf Wiebicke
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package de.rw7;

import java.awt.BorderLayout;
import java.awt.Choice;
import java.awt.Color;
import java.awt.Event;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Scrollbar;
import java.awt.image.ColorModel;
import java.awt.image.MemoryImageSource;
import java.awt.image.PixelGrabber;

/**
   Poincare'sche Wiederkehr.
   Fuer eine ausfuehrliche Erklaerung siehe
   DOS (Compterzeitschrift, Franzis Verlag) 4/94 Seite 240.
*/
public class Poincare extends java.applet.Applet implements Runnable
{
	static String[] Sources = { "Kreuz", "Kreis", "Garten", "Kompass" };

	static final int M00 = 1, M10 = 3, M01 = 1, M11 = 2, Mmod = 60;
	final int N = 200;
	final int[] Src = new int[N * N];
	final int[] Dst = new int[N * N];

	int Exponent = 0;
	String Source = Sources[0];
	boolean changed = false;

	Scrollbar ExponentScrollbar = null;
	Choice SrcChoice = null;
	ColorModel CM;
	Image ScreenBuffer;
	Thread running;

	public void init()
	{
		setLayout(new BorderLayout());
		add(
			"South",
			ExponentScrollbar =
				new Scrollbar(Scrollbar.HORIZONTAL, 0, 0, 0, Mmod + 1));
		SrcChoice = new Choice();
		for (int i = 0; i < Sources.length; i++)
			SrcChoice.addItem(Sources[i]);
		add("East", SrcChoice);
		CM = ColorModel.getRGBdefault();
	}

	public void start()
	{
		if (running == null)
		{
			running = new Thread(this);
			running.setPriority(Thread.MIN_PRIORITY);
			running.setDaemon(true);
			running.start();
		}
	}

	public void stop()
	{
		if (running != null)
		{
			running.stop();
			running = null;
		}
	}

	public void run()
	{
		final int N = this.N;
		final int[] Src = this.Src;
		final int[] Dst = this.Dst;
		boolean matrixToPaint = true;
		int Exponent = (-1);
		String Source = null;
		int d00 = 1, d10 = 0, d01 = 0, d11 = 1;

		while (true)
		{
			if (Source != this.Source)
			{
				Source = this.Source;

				Image img = createImage(N, N);
				Rectangle r = new Rectangle(0, 0, N, N);
				Graphics g = img.getGraphics();

				if ("Kreuz".equals(Source))
					drawKreuz(g, r);
				else if ("Kreis".equals(Source))
					drawKreis(g, r);
				else if ("Garten".equals(Source))
					drawGarten(g, r);
				else if ("Kompass".equals(Source))
					drawKompass(g, r);
				else
					throw (new IllegalArgumentException());

				g.dispose();
				PixelGrabber pg = new PixelGrabber(img, 0, 0, N, N, Src, 0, N);
				try
				{
					pg.grabPixels();
				}
				catch (InterruptedException e)
				{
				};
				matrixToPaint = true;
			}

			if (Exponent != this.Exponent)
			{
				Exponent = this.Exponent;

				d00 = 1;
				d10 = 0;
				d01 = 0;
				d11 = 1; // Einheitsmatrix

				if (Exponent == 0)
					for (int i = 0; i < Src.length; i++)
						Dst[i] = Src[i];
				else
				{
					showStatus(
						"erstelle Matrix (" + String.valueOf(Exponent) + ") ...");
					int a00 = M00, a10 = M10, a01 = M01, a11 = M11; // Basis
					int t00, t10, t01;
					int n = Exponent;
					while (n > 0)
					{
						if (n % 2 > 0)
						{
							t00 = (a00 * d00 + a01 * d10) % N;
							t10 = (a10 * d00 + a11 * d10) % N;
							t01 = (a00 * d01 + a01 * d11) % N;
							d11 = (a10 * d01 + a11 * d11) % N;
							d00 = t00;
							d10 = t10;
							d01 = t01;
						};
						t00 = (a00 * a00 + a01 * a10) % N;
						t10 = (a10 * a00 + a11 * a10) % N;
						t01 = (a00 * a01 + a01 * a11) % N;
						a11 = (a10 * a01 + a11 * a11) % N;
						a00 = t00;
						a10 = t10;
						a01 = t01;
						n /= 2;
					};
				};
				matrixToPaint = true;
			};

			if (matrixToPaint)
			{
				matrixToPaint = false;
				showStatus(
					"transformiere Bild ("
						+ String.valueOf(this.Exponent)
						+ ") ...");
				for (int y = 0, i = 0; y < N; y++)
					for (int x = 0; x < N; x++)
						Dst[(d00 * x + d10 * y) % N + N * ((d01 * x + d11 * y) % N)] =
							Src[i++];
			};

			showStatus("generiere Bild...");
			if (ScreenBuffer == null)
				ScreenBuffer =
					createImage(new MemoryImageSource(N, N, CM, Dst, 0, N));
			else
				ScreenBuffer.flush();

			showStatus("fertig.");
			repaint();

			if (!changed)
				running.suspend();
			changed = false;

		}
	}

	public void paint(Graphics g)
	{
		if (ScreenBuffer != null)
			g.drawImage(ScreenBuffer, 0, 0, null);
		g.drawString(String.valueOf(Exponent), 0, 20);
	}

	public boolean handleEvent(Event e)
	{
		if (e.target == ExponentScrollbar)
		{
			int n = (((Integer) e.arg).intValue());
			Exponent = n;
			ExponentScrollbar.setValue(n);
			changed = true;
			running.resume();
		}
		else if (e.target == SrcChoice && e.id == 1001)
		{
			Source = (String) e.arg;
			changed = true;
			running.resume();
		}

		return (super.handleEvent(e));
	}

	private static void drawKreuz(Graphics g, Rectangle r)
	{
		g.setColor(Color.black);
		g.fillRect(r.x, r.y, r.width, r.height);
		g.setColor(Color.white);
		g.fillOval(
			r.x + r.width / 8,
			r.y + r.height / 8,
			3 * r.width / 4,
			3 * r.height / 4);
		g.setColor(Color.blue);
		g.fillRect(
			r.x + r.width / 4,
			r.y + 9 * r.height / 20,
			r.width / 2,
			r.height / 10);
		g.fillRect(
			r.x + 9 * r.width / 20,
			r.y + r.height / 4,
			r.width / 10,
			r.height / 2);
	}

	private static void drawKreis(Graphics g, Rectangle r)
	{
		g.setColor(Color.white);
		g.fillRect(r.x, r.y, r.width, r.height);
		g.setColor(Color.black);
		g.drawOval(r.x, r.y, r.width - 1, r.height - 1);
	}

	private static void drawGarten(Graphics g, Rectangle r)
	{
		g.setColor(Color.blue);
		g.fillRect(r.x, r.y, r.width, 3 * r.height / 4);
		g.setColor(Color.green);
		g.fillRect(r.x, r.y + 3 * r.height / 4, r.width, r.height / 4);
		g.setColor(Color.orange);
		g.fillRect(
			r.x + 9 * r.width / 20,
			r.y + r.height / 3,
			r.width / 10,
			r.height / 2);
		g.setColor(Color.green);
		g.fillOval(
			r.x + r.width / 4,
			r.y + r.height / 10,
			r.width / 2,
			r.height / 2);
		g.setColor(Color.yellow);
		g.fillOval(
			r.x + r.width / 20,
			r.y + r.height / 20,
			r.width / 5,
			r.height / 5);
		g.setColor(Color.white);
		g.fillOval(
			r.x + 15 * r.width / 20,
			r.y + r.height / 20,
			r.width / 15,
			r.height / 15);
		g.fillOval(
			r.x + 16 * r.width / 20,
			r.y + r.height / 20,
			r.width / 15,
			r.height / 15);
		g.fillOval(
			r.x + 16 * r.width / 20,
			r.y + 3 * r.height / 20,
			r.width / 15,
			r.height / 15);
		g.fillOval(
			r.x + 17 * r.width / 20,
			r.y + 3 * r.height / 20,
			r.width / 15,
			r.height / 15);
	}

	private static void drawKompass(Graphics g, Rectangle r)
	{
		g.setColor(Color.yellow);
		g.fillRect(r.x, r.y, r.width / 2, r.height / 2);
		g.setColor(Color.green);
		g.fillRect(r.x + r.width / 2, r.y, r.width / 2, r.height / 2);
		g.setColor(Color.blue);
		g.fillRect(r.x, r.y + r.height / 2, r.width / 2, r.height / 2);
		g.setColor(Color.red);
		g.fillRect(
			r.x + r.width / 2,
			r.y + r.height / 2,
			r.width / 2,
			r.height / 2);
		g.setColor(Color.black);
		FontMetrics fm = g.getFontMetrics();
		g.drawString("N", r.width / 2 - fm.charWidth('N') / 2, fm.getAscent());
		g.drawString("S", r.width / 2 - fm.charWidth('S') / 2, r.height);
		g.drawString("W", 0, r.height / 2 + fm.getAscent() / 2);
		g.drawString(
			"E",
			r.width - fm.charWidth('E'),
			r.height / 2 + fm.getAscent() / 2);
	}

} // class Poincare
