package lsystem;

import java.awt.*;
/**
 * Lindenmayer-System <br>
 * Belegarbeit AlgoDat WS99/SS2000, Aufgabe 8
 * <p>
 * Thread zur Berechnung des Bildes.
 *
 * @author Christian Semrau<br>
 * Christian.Semrau@student.uni-magdeburg.de<br>
 */
public final class LSystemThread implements Runnable {
	int verbose;
	static final int
	VERBOSE_QUIT = 0,  //
	VERBOSE_TIME = 1,  // Linienzahl, benoetigte Zeit u.a. anzeigen
	VERBOSE_STAT = 2;  // Vergleich der Zeit von erstem und zweitem Durchlauf
	/** dieser Thread */
	private Thread me;
	/** das zu zeichnende LSystem */
	private LSystem LS;
	/** die Schildkroete */
	private Turtle kroete;

	/** true = die Berechnung soll abgebrochen werden */
	boolean stopped;
	/** true = der Thread hat seine Arbeit beendet */
	boolean died;

	/** Millisekunden seit dem letzten yield() in paintLSystem() */
	private long lastYield;
	/** Startzeitpunkt */
	private long startTime;
	/** ausgewertete Zeichen */
	private int chars;
	// der zweite Durchlauf dauert 3- bis 4-mal solange wie der erste
	private static final int lf1 = 1;
	private static final int lf2 = 4;
	private int lineFactor;
	/** Gesamtzahl der auszuwertenden Zeichen in beiden Durchgaengen */
	private long totalChars;
	private long totalLines;
	/** die Zeichenflaeche */
	private LSystemCanvas canvas;
/**
 * Legt einen LSystemThread an und startet den Thread.
 */
public LSystemThread(LSystem s, LSystemCanvas c) {
	LS = s;
	canvas = c;
	stopped = false;
	died = false;
	verbose =
//		VERBOSE_STAT |
		VERBOSE_TIME |
		VERBOSE_QUIT;
	chars = 0;
	totalChars = s.calcChars(s);
	totalLines = s.calcLines(s);
	if ((verbose&VERBOSE_TIME)!=0)
		System.out.println("Linien zu zeichnen: "+totalLines+
		", Zeichen auszuwerten: "+totalChars);

	// fuer die unterschiedlichen Geschwindigkeiten
	// beim ersten und zweiten Durchlauf
	totalChars *= (lf1+lf2);
	me = new Thread(this);
	me.start();
}
/**
 * Berechnet das Bild.
 * @return java.awt.Image
 */
private Image calculateImage(LSystem LS) {
	chars = 0;
	startTime = lastYield = System.currentTimeMillis();

	// Start-Regel ermitteln
	String R = LS.getStartRegel();
	// erster Durchlauf: Kurve durchlaufen ohne zu zeichnen
	// damit wir die Groesse der Kurve erfahren
	kroete = new Turtle(null,0,0, LS.getStartWinkel());
	lineFactor = lf1;
	paintLSystem(R, 1000, LS.getTiefe(), LS);

	// wurden wir abgebrochen?
	if (stopped)
		return null;

	// Groesse der Zeichenflaeche
	Dimension d = canvas.getSize();
	int width = d.width;
	int height = d.height;
	// freizulassender Rand
	int border = 10;
	if (width<=2*border || height<=2*border) return null;

	// dieses Stueck Mathematik ermittelt die ideale Groesse
	// der Basislaenge in scale und den Startpunkt in Xoff und Yoff
	float sizeX = Math.max(kroete.Xmax-kroete.Xmin, 1e-5f);
	float sizeY = Math.max(kroete.Ymax-kroete.Ymin, 1e-5f);
	float scaleX = (width-border*2)/sizeX;
	float scaleY = (height-border*2)/sizeY;
	float scale = Math.min(scaleX,scaleY);
	float Xoff = -kroete.Xmin*scale;
	float Yoff = -kroete.Ymax*scale;

	// Offscreen-Image anlegen
	Image img = canvas.createImage(width, height);
	Graphics g = img.getGraphics();

	// Weiss fuellen und schwarze Randlinie zeichnen
	g.setColor(Color.white);
	g.fillRect(0, 0, width-1, height-1);
	g.setColor(Color.black);
	g.drawRect(0, 0, width-1, height-1);
//	g.setColor(Color.black);

	long zwischenZeit = System.currentTimeMillis();
	float part1 = (float)(zwischenZeit-startTime)/1000;

	// zweite Haelfte
	kroete = new Turtle(g,(int)(Xoff+border),(int)(height-border+Yoff), LS.getStartWinkel());
	lineFactor = lf2;
	// jetzt wirklich zeichnen
	paintLSystem(R, scale*1000, LS.getTiefe(), LS);

	if (stopped)
		return null;

	// hier kommt noch ne Menge Auswertung des Berechnungs-Durchlaufs

	long stopTime = System.currentTimeMillis();
	float elapsed = (float)(stopTime-startTime)/1000;
	float part2 = (float)(stopTime-zwischenZeit)/1000;

	if (totalChars!=chars)
		System.out.println(chars/(lf1+lf2)+" Zeichen gezeichnet!");
	if ((verbose&VERBOSE_TIME)!=0){
		System.out.println("zwischenZeit: "+part1+" sek, ZeichenZeit : "+part2+
		" sek, gesamtZeit  : "+elapsed+" sek");
	}
	if ((verbose&VERBOSE_STAT)!=0)
	if (part1>0){
		float f1 = part2/part1, f2 = (float)totalChars/(lf1+lf2)/totalLines;
		if ((verbose&VERBOSE_TIME)!=0){
			System.out.println("zwischenZeit:ZeichenZeit = 1:"+f1);
			System.out.println("Linien      :Zeichen     = 1:"+f2);
		}
		System.out.println("#"+f1+"\t"+f2);
	}
	if ((verbose&VERBOSE_TIME)!=0){
		if (part2>0)
			System.out.print("Linien/Sek : "+totalLines/part2+" ");
		if (elapsed>0)
			System.out.print("Zeichen/Sek: "+totalChars/elapsed+" ");
		System.out.println("\n");
	}

	// wenn wir nicht unterbrochen wurden geben wir das fertige Bild zurueck
	return img;
}
private void paintLSystem(String F, float len, int t, LSystem LS) {
	if (t<=0){
		// Rekursionsstufe 0 erreicht
		kroete.forward(len);  // Linie zeichnen
		chars+=lineFactor;  // Zaehler erhoehen
		long aktTime;
		if (( (chars&8191) < lineFactor )
		&& Math.abs( lastYield-(aktTime=System.currentTimeMillis()) )>1000){
		// wenn ein Vielfaches von 8192 Linien gezeichnet
		// und eine Sekunde vergangen ist
			lastYield = aktTime;  // Zeit merken
			canvas.perc = (float)chars/totalChars*100; // Prozentangabe berechnen
			canvas.justInfo = true;  // nicht das ganze Bild, nur den Textblock
			canvas.repaint();  // Anzeige aktualisieren
			me.yield();  // Rechenzeit freigeben (noetig fuer Solaris-System)
		}
		return;
	};
	if (stopped) // wenn der Thread gestoppt werden soll, abbrechen
		return;

	// hoehere Rekursionsstufe
	int gewTiefe = 0;
	float n = len;  // aktuelle Linienlaenge
	for (int i=0; i<F.length(); i++){
		// die aktuelle Regel durchwandern
		chars+=lineFactor;
		char c = F.charAt(i);  // aktuelles Zeichen holen
//		if (Utils.isLatinLetter(c)){  // Buchstaben rufen neue Regeln auf
		if ((c>='a'&&c<='z')||(c>='A'&&c<='Z')){
			// die Buchstaben X,Y,Z werden nicht mehr an Tiefe 0 weitergegeben,
			// weil fuer diese keine Linie gezeichnet werden soll.
			if (t > (c>='X' ? 1 : gewTiefe) ){
				paintLSystem(LS.getRegel(c), n, t-1, LS);
			}
		}else
		// kein Buchstabe
		if (c=='<'||c=='>'||c=='/'||c=='\\'){
			try{
//				String sub = F.substring(i+1,i+3);
//				int val = new Integer(sub).intValue();
				int val = Utils.getDigit(F.charAt(i+1))*10+
					Utils.getDigit(F.charAt(i+2));
//				System.out.println("Befehl '"+c+"': "+sub+" "+val);
				if (c=='<')
					n -= len*val/100;
				else if (c=='>')
					n += len*val/100;
				else if (c=='/')
					kroete.turn(-val);
				else if (c=='\\')
					kroete.turn(val);
			}
//			catch(NumberFormatException e){}
			catch(StringIndexOutOfBoundsException e){}
			i+=2; // zwei Zeichen (die Ziffern) ueberspringen
			chars+=2*lineFactor;
		}else
		switch(c){
			// Leerzeichen ueberspringen
			case ' ': break;
			// Uhrzeigersinn
			case '+': kroete.turn(LS.getWinkel()); break;
			// Gegenuhrzeigersinn
			case '-': kroete.turn(-LS.getWinkel()); break;
			// Stift heben
			case '(': kroete.penUp(); break;
			// Stift senken
			case ')': kroete.penDown(); break;
			// Position merken
			case '[': kroete.push(); break;
			// Position holen
			case ']': kroete.pop(); break;
			// Drehsinn umkehren
			case '!': kroete.mirror(); break;
			// um 180 Grad drehen
			case '_': kroete.turn(180); break;
			// gewuenschte Abstiegs-Tiefe
			case '0': gewTiefe = 0; break;
			case '1': gewTiefe = 1; break;
		} // switch
	} // for
} // paintLSystem()
/**
 * Contains the thread execution loop.
 */
public void run() {
	// Beginn der Berechnung anzeigen
	canvas.setImage(null);
	// Bild berechnen
	Image theImage = calculateImage(LS);
	// wenn die Berechnung erfolgreich war, und der thread nicht gestoppt wurde
	if (theImage != null && !stopped)
		// Bild anzeigen
		canvas.setImage(theImage);
	died = true;
}//run()
/**
 * Setzt ein Flag, das dem Thread anzeigt, dass er die Berechnung abbrechen soll.
 */
public void stop() {
	stopped = true;
}
}