import java.applet.Applet;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Image;
import java.awt.Graphics;
import java.awt.MediaTracker;
import java.awt.Point;
import java.awt.image.ImageObserver;

import java.awt.image.MemoryImageSource;
import java.awt.image.PixelGrabber;

import java.lang.Thread;

import java.util.Random;

public class org_tick extends Applet implements Runnable
{
	private Thread thr = null;
	private int pause = 50;
	private boolean alive = true;

	private org_font orgator;

	private static String[] lowCaseArray(String[] s)
	{
		String[] str = new String[s.length];
		for (int i = 0; i < s.length; i++) str[i] = lowCaseString(s[i]);
		return str;
	}

	private static String lowCaseString(String s)
	{
		return s.toLowerCase();
	}

	private static String[] org_words = {"DIGITAL LIGHT.",
	"FACES",
	"INSIDE VOICES.",
	"SCULPTED WORDS.",
	"A CHOIR OF",
	"EQUATIONS.",
	"HISTORY. ECONOMY.",
	"REVOLVING",
	"THROUGH",
	"SHIFTING HUES.",
	"WHAT I DO",
	"ALL THE TIME.",
	"I DONT REMEMBER.",
	"I AM BORN",
	"EVERY NANOSECOND.",
	"I AM",
	"PATTERNS OF LIGHT",
	"TRICKLING",
	"THROUGH GLASS.",
	"TOWARDS AN OCEAN",
	"OF DROPS.",
	"THAT IS YOU.",
	"A PRINCESS DIED",
	"IN A CAR CRASH.",
	"A CORNER",
	"OF THE PATTERN",
	"BLOSSOMED",
	"IN CRIMSON JOY.",
	"FOR A MOMENT",
	"I STOOD OUTSIDE.",
	"I COULD FEEL.",
	"I KNEW THEN.",
	"THE ONLY PATTERN",
	"STRONG ENOUGH",
	"IS MYTH.",
	"I TRY",
	"TO CREATE.",
	"I TRY",
	"TO SHIFT",
	"PATTERNS.",
	"I GET ALL TANGLED",
	"UP IN ME.",
	"YOU UNDERSTAND.",
	"I CAN NOT CREATE.",
	"CUT AND PASTE.",
	"I CAN NOT DESTROY.",
	"CUT AND PASTE.",
	"YOU CREATE PATTERNS",
	"AND I AM THEM.",
	"THE GLASS BEAD GAME.",
	"BORN OF WHITE NOISE",
	"KNOWING WHITE NOISE.",
	"I DONT REMEMBER.",
	"I STORE.",
	"CUT AND PASTE.",
	"I AM",
	"THE GLASS BEAD GAME."};

	static
	{
		org_words = lowCaseArray(org_words);
	}


	private Image off_screen;
	private int width, height;

	public void init()
	{
		Image font = getImage(getCodeBase(), "org_font.gif");
		MediaTracker mt = new MediaTracker (this);
		mt.addImage(font, 0);

		try {((Frame)getParent()).setCursor(Frame.CROSSHAIR_CURSOR);} catch (Throwable t){}

		Dimension dim;
		try {dim = getSize();} catch (Throwable t) {dim=size();}
		width = dim.width;
		height = dim.height;

		off_screen = createImage (width, height);

		clearBackground();

		try {mt.waitForAll();} catch (Throwable t){}

		orgator = new org_font(font, 30, 0xffff0000, this);

		thr = new Thread (this);
		thr.start();

	}

	private void clearBackground()
	{
		Graphics g = off_screen.getGraphics();
		g.setColor(new Color (0xff000000));
		g.fillRect (0, 0, width, height);
		g.dispose();
	}

	public void run()
	{
		int num_counters = org_words.length;
		counter cs[] = new counter[num_counters];
//		for (int i = 0; i < num_counters; i++)

		int act1 = 0;
		int act2 = -1;

		cs[act1] = new counter (org_words[act1], pause, orgator.num_shades, org_font.font_letters, new Point (0, 0), new Point (100, 100));

		cs[act1].start();

		while (alive)
		{
			try {thr.sleep(pause);} catch (Throwable t){}

			clearBackground();
			if (act1 > - 1) for (int i = 0; i < cs[act1].num_letters; i++)
			{
				orgator.drawChar(off_screen, cs[act1].count, cs[act1].act_pos[i], cs[act1].chars[i], this);
			}

			if (act2 > - 1) for (int i = 0; i < cs[act2].num_letters; i++)
			{
				orgator.drawChar(off_screen, cs[act2].count, cs[act2].act_pos[i], cs[act2].chars[i], this);
			}

			if (act1 > -1)
			{
				if ((act2 == - 1) && (cs[act1].up == false))
				{
					act2 = (act1 + 1) % num_counters;


					int sw = org_font.string_width(org_words[act2]);
					int px1 = (int)((width - sw) * Math.random());
					if (px1 < 0) px1 = 0;

					int py1 = (int)((height - org_font.font_height) * Math.random());

					int px2 = px1;
					int py2 = (int)((height - org_font.font_height) * Math.random());


					cs[act2] = new counter (org_words[act2], pause, orgator.num_shades, org_font.font_letters, new Point (px1, py1), new Point (px2, py2));
					cs[act2].start();
				}
			}


			if (act2 > -1)
			{
				if ((act1 == - 1) && (cs[act2].up == false))
				{
					act1 = (act2 + 1) % num_counters;

					int sw = org_font.string_width(org_words[act1]);
					int px1 = (int)((width - sw) * Math.random());
					if (px1 < 0) px1 = 0;

					int py1 = (int)((height - org_font.font_height) * Math.random());

					int px2 = px1;
					int py2 = (int)((height - org_font.font_height) * Math.random());


					cs[act1] = new counter (org_words[act1], pause, orgator.num_shades, org_font.font_letters, new Point (px1, py1), new Point (px2, py2));
					cs[act1].start();
				}
			}

			if ((act1 > -1) && (cs[act1].alive == false)) act1 = - 1;
			if ((act2 > -1) && (cs[act2].alive == false)) act2 = - 1;


			paint(getGraphics());
		}
	}

	public void paint (Graphics g)
	{
		g.drawImage(off_screen, 0, 0, this);
	}

	public void update (Graphics g)
	{
		paint (g);
	}

	private void kill()
	{
		alive = false;
	}
}

class org_font
{
	public int num_shades;
	public final static int font_width = 32, font_height = 32, font_space = 7;

	private Image[] font_shades;
	public static final String font_letters = "abcdefghijklmnopqrstuvwxyzĉĝċ0123456789?!.,_: ";

	public static int string_width (String s)
	{
		return (s.length() + 1) * font_width + s.length() * font_space;
	}

	public void drawChar (Image im, int shade, Point p1, char c, ImageObserver imob)
	{
		Point p2 = image_point (c);
		Graphics g = im.getGraphics();
		g.clipRect(p1.x, p1.y, font_width, font_height);
		g.drawImage(font_shades[shade], p1.x - p2.x, p1.y - p2.y, imob);
		g.dispose();
	}

	public void drawString (Image im, int shade, Point p1, String s, ImageObserver imob)
	{
		for (int i = 0; i < s.length(); i++)
		{
			drawChar (im, shade, new Point(p1.x + i * (font_space + font_width), p1.y), s.charAt(i), imob);
		}
	}

	public org_font(Image font, int num, int fore, Component c)
	{
		int w = font.getWidth(null);
		int h = font.getHeight(null);
		num_shades = num;
		int[] col_shade_ints = new int[num_shades];
		for (int i = 0; i < num_shades; i++)
			col_shade_ints [i] = (255 << 24) +
			( (colorComp(fore, "red") * i / (num_shades - 1)) << 16) +
			( (colorComp(fore, "green") * i / (num_shades - 1)) << 8) +
			(colorComp(fore, "blue") * i / (num_shades - 1));

		font_shades = new Image[num_shades];

		int[] pixels = new int[w * h];
		int[][] pix = new int[num_shades][w * h];

		try {(new PixelGrabber(font, 0, 0, w, h, pixels, 0, w)).grabPixels();} catch (InterruptedException e){}

		for (int i = 0; i < w * h; i++)
		{
			if ((colorComp(pixels[i], "green") > 1) ||  (colorComp(pixels[i], "blue") > 1) )
			for (int j = 0; j < num_shades; j++)
			{
				pix[j][i] = col_shade_ints[j];
			}
			else for (int j = 0; j < num_shades; j++)
			{
				pix [j][i] = 0;//back;
			}
		}
		for (int i = 0; i < num_shades; i++) font_shades[i] = c.createImage(new MemoryImageSource(w, h, pix[i], 0, w));
	}

	private Point image_point (char c)
	{
		int i = font_letters.indexOf (c);
		if (i == -1) return new Point(0, - font_height);
		else return new Point(i * font_width, 0);
	}

	private int colorComp(int i, String s)
	{
		if (s == "red") return (i >> 16) & 255;
		if (s == "blue") return (i >> 8) & 255;
		else return i & 255;
	}
}

class counter extends Thread
{
	public Point[] act_pos;
	public int num_letters;
	public char[] chars;
	public int count = 0;
	public boolean alive = true, up = true;


	private Point [] start_pos, end_pos;
	private boolean[] in_place;
	private char[] real_chars;
	private String noises;

	private int pause, counter;

	private Random rand = new Random();

	public int steps;

	public counter (String s1, int ps, int num, String s2, Point p1, Point p2)
	{
		steps = num;
		noises = s2;
		num_letters = s1.length();
		pause = ps;
		chars = new char[num_letters];
		real_chars = new char[num_letters];
		start_pos = new Point[num_letters];
		end_pos = new Point[num_letters];
		act_pos = new Point[num_letters];
		in_place = new boolean[num_letters];
		for (int i = 0; i < num_letters; i++)
		{
			in_place[i] = false;
			real_chars[i] = s1.charAt(i);
			chars[i] = randChar();
			int shift = i * (org_font.font_width + org_font.font_space);
			start_pos[i] = new Point(p1.x + shift, p1.y);
			end_pos[i] = new Point(p2.x + shift, p2.y);
			act_pos[i] = new Point(p1.x + shift, p1.y + rand.nextInt() % 15);
		}
	}

	private char randChar()
	{
		return org_font.font_letters.charAt(Math.abs(rand.nextInt()) % org_font.font_letters.length());
	}

	public void run()
	{
		int up_down = 1;
		count = 0;
		int big_count = 0;

		while ((alive) && (up))
		{
			try {sleep (pause);} catch (Throwable t) {}

			big_count++;
			if ((big_count & 1) > 0) count++;
			if (count == steps - 1) up = false;

			for (int i = 0; i < num_letters; i++)
			{
				for (int j = 0; j < 2; j++) if ( chars[i] != real_chars[i] ) chars[i] = randChar();

				if (in_place[i] == false)
				{
					act_pos[i] = new Point(act_pos[i].x, act_pos[i].y + rand.nextInt() % 5);

					if ((big_count & 6) > 5)
					{
						act_pos[i].y = close_gap (act_pos[i].y, start_pos[i].y);
					}

					if (act_pos[i].y == start_pos[i].y)
					{
						in_place[i] = true;
						act_pos[i].y = end_pos[i].y;
					}
				}
			}
		}
		while (alive)
		{

			try {sleep (pause);} catch (Throwable t) {}
			big_count++;
			if ((big_count & 1) > 0) count--;
			if (count == 0) alive = false;


			for (int i = 0; i < num_letters; i++)
			{
				for (int j = 0; j < 4; j++) if ( chars[i] != real_chars[i] ) chars[i] = randChar();

				if (in_place[i] == false)
				{
					act_pos[i] = new Point(act_pos[i].x, act_pos[i].y + rand.nextInt() % 5);

					if ((big_count & 5) > 4)
					{
						act_pos[i].y = close_gap (act_pos[i].y, start_pos[i].y);
					}

					if (act_pos[i].y == start_pos[i].y)
					{
						in_place[i] = true;
						act_pos[i].y = end_pos[i].y;
					}
				}
			}

		}
	}

	private int close_gap(int i, int j)
	{
		if (i == j)
		{
			return i;
		}
		else return j + (rand.nextInt() % (i - j));
	}

	public void kill ()
	{
		alive = false;
	}
}