package lsystem;

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.net.*;
/**
 * Lindenmayer-System <br>
 * Belegarbeit AlgoDat WS99/SS2000, Aufgabe 8
 * <p>
 * Kontroll-Panel.
 *
 * @author Christian Semrau<br>
 * Christian.Semrau@student.uni-magdeburg.de<br>
 */
public class LSystemControls extends Panel
implements ItemListener, ActionListener {
	/** das Applet */
	private LSystemApplet applet;
	/** internes LSystem */
//	private LSystem LS;
	private String[] Regeln;
	/** aktuell angezeigte Regel */
	private int aktuelleRegel;

	/** der "Startknopf" */
	private Button Berechnen;
	/** der "Stopknopf" */
	private Button Stop;

	/** Beispiel-LSysteme */
	Choice GruppenChoice;
	LSystemChoiceGroup Gruppen;

	/** die Eingabe-Felder */
	private Choice Axiom, Regel;
	private TextField RegelFeld, WinkelFeld, TiefeFeld, StartWinkelFeld;
	/** Rekursionstiefe hoch/runter */
	private Button TiefeHoch, TiefeRunter;
	private TextField InfoZeile;

	/** konnte die txt-Datei gelesen werden */
	boolean couldReadFile;

	Panel DataManage;
	String aktCard;

	private Button Manage;
	Button Add;
	Button Replace;
	Button Remove;
	TextArea LSystemArea;
/**
 * Legt ein Kontroll-Panel an.
 * Ist app==null, werden keine Komponenten angelegt (ausser GruppenChoice und
 * Gruppen), sondern nur die Datei ausgewertet!
 *
 * @param L belegLsystem.LSystemApplet
 */
public LSystemControls(LSystemApplet app) {
	applet = app;

	GruppenChoice = new Choice();
	Gruppen = new LSystemChoiceGroup(this, GruppenChoice);

	ladeBeispiele();

	if (app!=null){
		Axiom = new Choice();
		Regel = new Choice();
		RegelFeld = new TextField(20);
		WinkelFeld = new TextField(5);
		TiefeFeld = new TextField(2);
		StartWinkelFeld = new TextField(5);
		TiefeHoch = new Button("+");
		TiefeRunter = new Button("-");
		InfoZeile = new TextField(20);
		InfoZeile.setEditable(false);

		// Axiom und Regel fuellen
		for (int i=0; i<26; i++){
			String s = Utils.letterString(i);
			Axiom.addItem(s);
			Regel.addItem(s);
		}
		Berechnen = new Button("Berechnen");
		Stop = new Button("Stop");

		Manage = new Button("Manage");
		Add = new Button("Hinzufuegen");
		Add.setActionCommand("Add");
		Replace = new Button("Ersetzen");
		Replace.setActionCommand("Replace");
		Remove = new Button("Loeschen");
		Remove.setActionCommand("Remove");
		LSystemArea = new TextArea("",3,20,TextArea.SCROLLBARS_BOTH);

		TiefeHoch.setActionCommand("TiefeHoch");
		TiefeRunter.setActionCommand("TiefeRunter");
		// wir wollen nicht "+" und "-" als ActionCommand

		initGUI();
		addListeners();
	}
}
/**
 * Reagiert auf die Buttons und einige TextFields.
 * @param event java.awt.event.ActionEvent
 */
public void actionPerformed(ActionEvent event) {
	Object obj = event.getSource();
	if (obj instanceof Button){
		String action = event.getActionCommand();
		if (action.equals("TiefeHoch")){
			// Tiefe aus dem Eingabefeld auslesen
			int T = readTiefe();
			// T<=0 bedeutet Fehler
			// neue (erhoehte) Tiefe ins Eingabefeld schreiben
			if (T>0) TiefeFeld.setText(""+(++T));
			setInfo(copyTextFieldsToLSystem());
		}else
		if (action.equals("TiefeRunter")){
			int T = readTiefe();
			// T<=0 bedeutet Fehler, T darf nicht ==0 werden
			if (T>1) TiefeFeld.setText(""+(--T));
			setInfo(copyTextFieldsToLSystem());
		}
		if (action.equals("Berechnen")){
			// alle Werte aus den Eingabefeldern holen
			LSystem LS = copyTextFieldsToLSystem();
			// LS ist null, wenn die Eingaben fehlerhaft sind
			if (LS!=null){
				setInfo(LS);
				applet.startThread(LS);
			}
		}
		if (action.equals("Stop")){
			applet.stopThread();
		}
		if (action.equals("Manage")){
			if ("Data".equals(aktCard)){
				LSystemChoice LSC = Gruppen.aktChoice;
				Remove.setEnabled(LSC.Name.equals("default"));
				LSystem L = copyTextFieldsToLSystem();
				LSystemArea.setText(L.toString()+'\n');
				aktCard = "Manage";
			}else{
				aktCard = "Data";
			}
			selectCard(aktCard);
		}
		if ("Add".equals(action)){
			String S = LSystemArea.getText();
//			System.out.println(Utils.specString(S));
			parseAdd("default", Utils.splitIntoLines(S));
			selectCard("Data");
		}else
		if ("Replace".equals(action)){
			String S = LSystemArea.getText();
//			System.out.println(Utils.specString(S));
			parseReplace(Utils.splitIntoLines(S));
			selectCard("Data");
		}else
		if ("Remove".equals(action)){
			String S = LSystemArea.getText();
//			System.out.println(Utils.specString(S));
			parseRemove(Utils.splitIntoLines(S));
			selectCard("Data");
		}
	}//if (obj instanceof Button)
	else
	if (obj==TiefeFeld){
//		System.out.println(event);
		setInfo(copyTextFieldsToLSystem());
	}else
	if (obj==RegelFeld){
		andereRegel();
//		setInfo(copyTextFieldsToLSystem());
	}
}//actionPerformed
/**
 * Listener fuer die Komponenten registrieren.
 */
public void addListeners() {
	// GruppenChoice und Gruppen haben ihren eigenen Listener
	Axiom.addItemListener(this);
	Regel.addItemListener(this);
	Berechnen.addActionListener(this);
	Stop.addActionListener(this);
	TiefeHoch.addActionListener(this);
	TiefeRunter.addActionListener(this);

	TiefeFeld.addActionListener(this);
	RegelFeld.addActionListener(this);
}
/**
 * Sichert die vorige Regel und schreibt die aktuelle Regel.
 */
private void andereRegel() {
	// Sichert die vorige Regel (auch wenn sie fehlerhaft ist)
	String R = RegelFeld.getText();
	if (LSystem.regelOK(R)){
		Regeln[aktuelleRegel] = R;
		// Schreibt die neue Regel ins Eingabefeld
		setRegelText();
		setInfo(copyTextFieldsToLSystem());
	}else{
		Regel.select(aktuelleRegel);
		InfoZeile.setText("Fehler in Regel");
	}
}
/**
 * Kopiert das LSystem in die Textfelder.
 */
public void copyLSystemToTextFields(LSystem L) {
	L = new LSystem(L); // Kopie anlegen, Fehler werden dabei bereinigt
	int i = L.getAxiom()-'A';
	Axiom.select(i); // waehlt Index aus
	Regel.select(i); // waehlt Index aus
	Regeln = L.getRegeln();
	setRegelText();
	WinkelFeld.setText(""+L.getWinkel());
	TiefeFeld.setText(""+L.getTiefe());
	StartWinkelFeld.setText(""+L.getStartWinkel());

	setInfo(L);
}
/**
 * Liefert das eingegebene LSystem, oder null, wenn es fehlerhaft ist.
 */
public LSystem copyTextFieldsToLSystem() {
	String r = RegelFeld.getText();
	if (!LSystem.regelOK(r)){
		InfoZeile.setText("Fehler in Regel");
		return null;
	}else{
		Regeln[aktuelleRegel] = r;
	}

	String name = Gruppen.aktChoice.getSelectedItem();
	char axiom = Utils.letter(Axiom.getSelectedIndex());
	float winkel=readWinkel();
	int tiefe=readTiefe();
	float startWinkel=readStartWinkel();
	if (winkel>1e6) return null;
	if (tiefe<0) return null;
	if (startWinkel>1e6) return null;

	return new LSystem(name, axiom, Regeln, winkel, tiefe, startWinkel);
}
/**
 * 
 */
public void initGUI() {
	GridBagLayout gbl = new GridBagLayout();
	GridBagConstraints gbc;
	setLayout(gbl);
	Panel p;
	FlowLayout l = new FlowLayout(FlowLayout.LEFT,5,5);
//	setBackground(Color.blue);

	p = new Panel(l);
	p.add(Berechnen);
	p.add(Stop);
	p.add(Manage);
	Manage.addActionListener(this);

	gbc = Utils.makegbc(0,0,1,1,GridBagConstraints.NONE);
	gbc.anchor = GridBagConstraints.WEST;
//	gbc.weightx = 100;
	gbl.setConstraints(p, gbc);
	add(p);

	Panel p1 = initGUIData();
	Panel p2 = initGUIManage();
	DataManage = new Panel(new CardLayout(0,0));
	DataManage.add("Data", p1);
	DataManage.add("Manage", p2);

	gbc = Utils.makegbc(0,1,1,1,GridBagConstraints.NONE);
	gbc.anchor = GridBagConstraints.WEST;
//	gbc.weightx = 100;
	gbl.setConstraints(DataManage, gbc);
	add(DataManage);
	aktCard = "Data";

}
/**
 * 
 */
public Panel initGUIData() {

	GridBagLayout gbl = new GridBagLayout();
	GridBagConstraints gbc;
	Panel p;
	FlowLayout l = new FlowLayout(FlowLayout.LEFT,5,5);

	Panel D = new Panel();
	D.setLayout(gbl);
//	D.setBackground(Color.lightGray);
	int line = 0;

	p = new Panel(l);
	p.add(GruppenChoice);
	
	gbc = Utils.makegbc(0,line++,1,1,GridBagConstraints.NONE);
	gbc.anchor = GridBagConstraints.WEST;
//	gbc.weightx = 100;
	gbl.setConstraints(p, gbc);
	D.add(p);

	gbc = Utils.makegbc(0,line++,1,1,GridBagConstraints.NONE);
	gbc.anchor = GridBagConstraints.WEST;
//	gbc.weightx = 100;
	gbl.setConstraints(Gruppen, gbc);
	D.add(Gruppen);

	p = new Panel(l);
	p.add(new Label("Axiom:",Label.LEFT));
	p.add(Axiom);

	gbc = Utils.makegbc(0,line++,1,1,GridBagConstraints.NONE);
	gbc.anchor = GridBagConstraints.WEST;
//	gbc.weightx = 100;
	gbl.setConstraints(p, gbc);
	D.add(p);

	p = new Panel(l);
	p.add(new Label("Regel:",Label.LEFT));
	p.add(Regel);

	gbc = Utils.makegbc(0,line++,1,1,GridBagConstraints.NONE);
	gbc.anchor = GridBagConstraints.WEST;
//	gbc.weightx = 100;
	gbl.setConstraints(p, gbc);
	D.add(p);

	p = new Panel(l);
	p.add(RegelFeld);

	gbc = Utils.makegbc(0,line++,1,1,GridBagConstraints.NONE);
	gbc.anchor = GridBagConstraints.WEST;
//	gbc.weightx = 100;
	gbl.setConstraints(p, gbc);
	D.add(p);

	p = new Panel(l);
	p.add(new Label("Winkel:",Label.LEFT));
	p.add(WinkelFeld);

	gbc = Utils.makegbc(0,line++,1,1,GridBagConstraints.NONE);
	gbc.anchor = GridBagConstraints.WEST;
//	gbc.weightx = 100;
	gbl.setConstraints(p, gbc);
	D.add(p);
	
	p = new Panel(l);
	p.add(new Label("Tiefe:",Label.LEFT));
	p.add(TiefeFeld);
	p.add(TiefeHoch);
	p.add(TiefeRunter);

	gbc = Utils.makegbc(0,line++,1,1,GridBagConstraints.NONE);
	gbc.anchor = GridBagConstraints.WEST;
//	gbc.weightx = 100;
	gbl.setConstraints(p, gbc);
	D.add(p);

	p = new Panel(l);
	p.add(new Label("StartWinkel:",Label.LEFT));
	p.add(StartWinkelFeld);

	gbc = Utils.makegbc(0,line++,1,1,GridBagConstraints.NONE);
	gbc.anchor = GridBagConstraints.WEST;
//	gbc.weightx = 100;
	gbl.setConstraints(p, gbc);
	D.add(p);

	p = new Panel(l);
	p.add(InfoZeile);

	gbc = Utils.makegbc(0,line++,1,1,GridBagConstraints.NONE);
	gbc.anchor = GridBagConstraints.WEST;
//	gbc.weightx = 100;
	gbl.setConstraints(p, gbc);
	D.add(p);

	return D;
}
/**
 * 
 */
public Panel initGUIManage() {
	GridBagLayout gbl = new GridBagLayout();
	GridBagConstraints gbc;
	Panel p;
	FlowLayout l = new FlowLayout(FlowLayout.LEFT,5,5);
	int line = 0;

	Panel D = new Panel();
	D.setLayout(gbl);
//	D.setBackground(Color.green);

	p = new Panel(l);
	p.add(Add);
	p.add(Replace);
	gbc = Utils.makegbc(0,line++,1,1,GridBagConstraints.NONE);
	gbc.anchor = GridBagConstraints.WEST;
//	gbc.weightx = 100;
	gbl.setConstraints(p, gbc);
	D.add(p);

	p = new Panel(l);
	p.add(Remove);
	gbc = Utils.makegbc(0,line++,1,1,GridBagConstraints.NONE);
	gbc.anchor = GridBagConstraints.WEST;
//	gbc.weightx = 100;
	gbl.setConstraints(p, gbc);
	D.add(p);

	gbc = Utils.makegbc(0,line++,1,1,GridBagConstraints.HORIZONTAL);
	gbc.anchor = GridBagConstraints.WEST;
//	gbc.weightx = 100;
	gbl.setConstraints(LSystemArea, gbc);
	D.add(LSystemArea);

	Add.addActionListener(this);
	Replace.addActionListener(this);
	Remove.addActionListener(this);
	return D;
}
/**
 * Reagiert auf ItemEvents der Choice-Objekte.
 * @param event java.awt.event.ItemEvent
 */
public void itemStateChanged(ItemEvent event) {
//	System.out.println(event);
	ItemSelectable source = event.getItemSelectable();

	if (source==Axiom){
		// wenn das Axiom geaendert wird, wird auch eine andere Regel angezeigt.
		Regel.select(Axiom.getSelectedIndex());
		andereRegel();
	}else
	if (source==Regel){
		andereRegel();
	}
}
/**
 * Laedt die Beispiel-LSysteme.
 * Es wird dafuer gesorgt, dass kein Name doppelt in der Liste ist.
 * @return belegLsystem.LSystem[]
 */
private void ladeBeispiele() {
	String fn = "lsystem/lsystems.txt";
	if (applet!=null){
		String s = applet.getParameter2("IniFile");
		if (s!=null) fn = s;
	}

	String[] S = null;
	if (applet==null)
		S = Utils.readStringArray(fn);
	else{
		URL base = applet.getDocumentBase2();
		if (base!=null)
			S = Utils.readFile(base, fn);
		else
			S = Utils.readStringArray(fn);
	}
	// Beispiele einlesen
	couldReadFile = (S!=null);
	
	parseAdd("IniDefault", S);
	// Beispiele parsen und LSysteme erzeugen

	if (!couldReadFile){
		// nur wenn die Datei nicht gelesen werden konnte
		LSystemChoice LSC = Gruppen.getChoice("IniDefault");
		LSC.addLSystem(new LSystem("Koch-Kurve", 'F', "F:F-F++F-F;", 60, 5, 0));
		LSC.addLSystem(new LSystem("Busch", 'F', "F:FF-[-F+F+F]+[+F-F-F];", 22.5f, 4, -70));
		LSC.addLSystem(new LSystem("Baumkurve", 'F', "F:[-G][+G];G:H<50[+G][-G];", 45, 7, -90));
		LSC.addLSystem(new LSystem("Drachenkurve", 'F', "F:F+G;G:F-G;", 90, 12, 0));
		LSC.addLSystem(new LSystem("Hilbertkurve", 'X', "X:-YF+XFX+FY-; Y:+XF-YFY-FX+;", 90, 4, 0));
		LSC.addLSystem(new LSystem("quadratische Kochkurve", 'F', "F:F-F+F+F-F;", 90, 3, 0));
		LSC.addLSystem(new LSystem("Baumgruppe", 'L', "L:LF[/90X]F; X:F-[X][+X]+F[+FX]-X; F:FF;", 22, 6, 0));
		LSC.addLSystem(new LSystem("Sierpinski", 'F', "F:G+F+G;G:F-G-F;", 60, 6, 0));
/*
		new LSystem("ChS Initialen :-)", 'F',
			"F:[_EHEEHE](E)[/90EG[JEJGE]GGGE](EEGG)[EGIEIEJEJGE]; "+
			"E:EE; H:+>41G+G+; I:-<29G-G-; J:+<29G+G+;",
			30, 2, 0).addToVector(v);
*/
	}
}
/**
 * Test-Routine.
 */
public static void main(String args[]) {
	System.out.println("-");
	LSystemControls LSC = new LSystemControls(null);
}
/**
 * Verarbeitet das String-array und fuegt erzeugte LSysteme ein.
 */
private void parseAdd(String defGroup, String[] Sa) {
	if (Sa==null||Sa.length<=0) return;
	Vector sv2 = parseToVector(Sa);

	String aktGroup = defGroup;
	for(Enumeration e = sv2.elements(); e.hasMoreElements(); ){
		String akt = (String)e.nextElement();
		if (akt.startsWith("#[")){
			if (akt.endsWith("]")){
				aktGroup = akt.substring(2,akt.length()-1).trim();
			}
		}else{
			if (akt.startsWith("(")){
				LSystem L = new LSystem(akt);
//				System.out.println(L);
				Gruppen.addLSystem(aktGroup, L);
			}
		}
	}
}
/**
 * Verarbeitet das String-array und ersetzt erzeugte LSysteme.
 */
private void parseRemove(String[] Sa) {
	if (Sa==null||Sa.length<=0) return;
	Vector sv2 = parseToVector(Sa);

	String aktGroup = "default";
	for(Enumeration e = sv2.elements(); e.hasMoreElements(); ){
		String akt = (String)e.nextElement();
//		System.out.println("\""+akt+"\"");
		if (akt.startsWith("#[")){
			aktGroup = akt.substring(2,akt.length()-1);
//			System.out.println(aktGroup);
		}else{
			if (akt.startsWith("(")){
				String[] A = LSystem.splitLine(akt);
//				System.out.println("t1");
//				System.out.println(akt);
//				LSystem L = new LSystem(akt);
//				System.out.println("t2");
//				System.out.println(L);
				Gruppen.removeLSystem(aktGroup, A[0]); //L.getName());
			}else
				Gruppen.removeLSystem(aktGroup, akt);
		}
	}
}
/**
 * Verarbeitet das String-array und ersetzt erzeugte LSysteme.
 */
private void parseReplace(String[] Sa) {
	if (Sa==null||Sa.length<=0) return;
	Vector sv2 = parseToVector(Sa);

	String aktGroup = "default";
	for(Enumeration e = sv2.elements(); e.hasMoreElements(); ){
		String akt = (String)e.nextElement();
//		System.out.println("\""+akt+"\"");
		if (akt.startsWith("#[")){
			aktGroup = akt.substring(2,akt.length()-1);
//			System.out.println(aktGroup);
		}else{
			if (akt.startsWith("(")){
				LSystem L = new LSystem(akt);
//				System.out.println(L);
				Gruppen.replaceLSystem(aktGroup, L);
			}
		}
	}
}
/**
 * Filtert aus dem array alle ueberfluessigen Zeilen und erstellt einen Vector.
 * @return java.util.Vector
 * @param S java.lang.String[]
 */
public static Vector parseToVector(String[] Sa) {
	Vector sv = new Vector(Sa.length);

	// Leerzeilen und Kommentare rausfiltern
	int commentDepth = 0;
	for (int i = 0; i<Sa.length; ){
		String akt = Sa[i++].trim(); // Leerzeichen entfernen
		if (akt.length()<=0);
		else
		if (akt.startsWith("#["))  // Gruppenbezeichner
			sv.addElement(akt);
		else
		if (akt.charAt(0)!='#'){
			if (akt.startsWith("/*")) commentDepth++;
			else
			if (akt.endsWith("*/")) commentDepth--;
			else
			if (commentDepth==0){
				sv.addElement(akt);
			}
		}
	}

	// jetzt haben wir einen Vector sv, der alle zu verwertenden Zeilen enthaelt
	// wir bastelt daraus einen Vector, in dem die mehrzeiligen LSysteme
	// einzeilig sind, mehrere LSysteme in einer Zeile werden noch nicht erkannt
	Vector sv2 = new Vector(sv.size());
	for (Enumeration e = sv.elements(); e.hasMoreElements(); ){
		String akt = (String)e.nextElement();
		if (akt.charAt(0)!='(')
			sv2.addElement(akt);
		else{
			while(!akt.endsWith(")")&&e.hasMoreElements())
				akt += (String)e.nextElement();
			sv2.addElement(akt);
		}
	}
	// sv2 enthaelt jetzt noch die Gruppenmarker und die LSysteme einzeilig
	return sv2;
}
/**
 * Versucht, den Startwinkel zu bestimmen. Liefert unendlich, wenn es misslingt.
 */
private float readStartWinkel() {
	try{
		return new Float(StartWinkelFeld.getText()).floatValue();
	}catch(NumberFormatException e){
	}
	return Float.POSITIVE_INFINITY;
}
/**
 * Versucht, die Rekursionstiefe zu bestimmen. Liefert -1, wenn es misslingt.
 */
private int readTiefe() {
	try{
		return new Integer(TiefeFeld.getText()).intValue();
	}catch(NumberFormatException e){
	}
	return -1;
}
/**
 * Versucht, den Drehwinkel zu bestimmen. Liefert unendlich, wenn es misslingt.
 */
private float readWinkel() {
	try{
		return new Float(WinkelFeld.getText()).floatValue();
	}catch(NumberFormatException e){
	}
	return Float.POSITIVE_INFINITY;
}
/**
 * Listener fuer die Komponenten entfernen.
 */
public void removeListeners() {
	// GruppenChoice und Gruppen haben ihren eigenen Listener
	Axiom.removeItemListener(this);
	Regel.removeItemListener(this);
	Berechnen.removeActionListener(this);
	Stop.removeActionListener(this);
	TiefeHoch.removeActionListener(this);
	TiefeRunter.removeActionListener(this);

	TiefeFeld.removeActionListener(this);
	RegelFeld.removeActionListener(this);
}
/**
 * Ersetzt ein LSystem in der gewuenschten Choice.
 */
public void replaceLSystem(String gr, LSystem L) {
	if (L==null) return;
	if (gr==null) gr="default";
	Gruppen.replaceLSystem(gr, L);
}
/**
 * Selektiert das gewuenschte LSystem.
 * @param gr java.lang.String
 * @param name java.lang.String
 */
public void select(String gr, String name) {
	Gruppen.selectChoice(gr); // Exception, wenn gr nicht gefunden
	Gruppen.aktChoice.select(name); // tut nichts, wenn name nicht gefunden
	LSystem L = Gruppen.getSelectedLSystem();
	copyLSystemToTextFields(L);
}
/**
 * 
 * @param name java.lang.String
 */
public void selectCard(String name) {
	aktCard = name;
	((CardLayout)DataManage.getLayout()).show(DataManage, aktCard);
}
/**
 * 
 */
public void selectFirst() {
	Gruppen.selectFirst();
	Gruppen.aktChoice.selectFirst();
	LSystem L = Gruppen.getSelectedLSystem();
	copyLSystemToTextFields(L);
}
/**
 * 
 */
public void setInfo(LSystem LS) {
	InfoZeile.setText(LSystem.calcLines(LS)+" Linien");
}
/**
 * Kopiert den Text der aktuell ausgewaehlten Regel nach RegelFeld.
 */
private void setRegelText() {
	aktuelleRegel = Regel.getSelectedIndex();
	String R = Regeln[aktuelleRegel];
	if (!RegelFeld.getText().equals(R))
		// nur neu schreiben, wenn veraendert (wegen Cursorposition)
		RegelFeld.setText(R);
}
}