package gp;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * A Generic GP tree implementation.
 * 
 * @author mkron
 *
 */
public abstract class GPTree {
	public static int MODE_FULL = 0;
	public static int MODE_GROW = 1;

	public Random ran = new Random();
	public int arity; // Stelligkeit des Knotens aha!
	public int type; // Knotentyp . Schon mal was von Enums gehört...
	public int height; // Hoehe des Baumes
	public int nodeCount; // Knotenanzahl in diesem Baum
	public GPTree up, left, right; // Zeiger auf Eltern- und Unterknoten

	/**
	 * Constructor with tree initialization.
	 * 
	 * @param rand
	 * @param parent
	 * @param h
	 *            maximum height of the tree
	 * @param mode
	 *            0 is "full", 1 is "grow"
	 */
	public GPTree(GPTree parent, int h, int mode) {
		initRandomTree(parent, h, mode);
		if (!this.isValid()) {
			System.err.println("Error, invalid initialization!");
		}
	}

	/**
	 * Copy constructor
	 * 
	 * @param tree
	 *            the other tree
	 */
	public GPTree(GPTree tree) {
		/*
		 * Dieser Konstruktor erzeugt mit Hilfe von copyTree() eine tiefe Kopie
		 * des Baumes tree.
		 */
		this.arity = tree.arity;
		this.type = tree.type;
		this.height = tree.height;
		this.nodeCount = tree.nodeCount;
		if (left != null) {
			left = copyTree(left);
			left.up = this;
		}
		if (right != null) {
			right = copyTree(right);
			right.up = this;
		}
	}

	/**
	 * Random tree initialization.
	 * 
	 * @param parent
	 * @param maxH
	 *            maximum height of the new tree
	 * @param mode
	 *            0 is "full", 1 is "grow"
	 */
	public void initRandomTree(GPTree parent, int maxH, int mode) {
		/*
		 * Erzeugen Sie mit Hilfe von makeRandomSubtree() einen zufaelligen
		 * Baum. Verwenden Sie nicht direkt rekursiv den Konstruktor von GPTree,
		 * da sonst abgeleitete Typen mit dem Typ GPTree vermischt werden kann.
		 */
		// :-D :-D whooo das kommentar ist der hammer, da hat einer Vererbung
		// und Typen einfach nicht verstanden
		if (maxH > 0) {
			parent = makeRandomSubtree(maxH, mode);// TODO mode
		}

	}

	private int getFunctionCount() {
		return getTermCount() + getUnaryCount() + getBinaryCount();
	}

	// ************* abstract methods
	// these two are abstract so that the subtypes can return the right type
	// they should again use GPTree's (copy) constructor!
	public abstract GPTree makeRandomSubtree(int h, int mode);

	public abstract GPTree copyTree(GPTree tree);

	public abstract double evalTreeAt(Object o);

	public abstract int getTermCount(); // return number of terminals

	public abstract int getUnaryCount(); // return number of unary functions

	public abstract int getBinaryCount(); // return number of binary functions

	public abstract String getTermSymbol(int i); // return String for a terminal

	public abstract String getUnarySymbol(int i); // return String for a unary
													// function

	public abstract String getBinarySymbol(int i); // return String for a binary
													// function

	// ************* abstract methods end

	@Override
	public String toString() {
		/*
		 * Stellt den Baum durch eine Zeichenkette dar.
		 */
		switch (arity) {
		case 0:
			return getTermSymbol(type);
		case 1:
			return getUnarySymbol(type) + "(" + left.toString() + ")";
		case 2:
			return getBinarySymbol(type) + "(" + left.toString() + ","
					+ right.toString() + ")";
		}
		System.err.println("Invalid arity in GPTree!"); // Exceptions ? **
		return "";
	}

	// Hilfsfunktion zum Testen des Baumes auf Konsistenz
	private boolean isValid() {
		if (getFunctionCount() == 0) {
			return true; // assume correctness for an empty function set
		}
		switch (arity) {
		case 0:
			return ((left == null) && (right == null) && (height == 0) && (nodeCount == 1));
		case 1:
			return ((left != null) && (right == null)
					&& (height == (left.height + 1))
					&& (nodeCount == (left.nodeCount + 1)) && left.isValid() && (left.up == this));
		case 2:
			return ((left != null) && (right != null)
					&& (height == (Math.max(left.height, right.height) + 1))
					&& (nodeCount == (left.nodeCount + right.nodeCount + 1))
					&& left.isValid() && right.isValid() && (left.up == this) && (right.up == this));
		}
		return false;
	}

	/**
	 * Perform a GP crossover between two binary trees.
	 * 
	 * @param otherTree
	 *            the crossover partner
	 * @param h
	 *            the maximal height
	 */
	public void crossover(GPTree otherTree, int h) {
		/*
		 * Crossover zwischen den Baeumen this und tree wird ausgefuehrt.
		 * 
		 * Hinweis: wählen Sie zuerst zwei zufällige Teilbäume von this und
		 * otherTree. Dabei kann eine Vorstellung der Numerierung der
		 * Unterknoten helfen.
		 */
		GPTree os = findSubtree(ran.nextInt(nodeCount) + 1);
		GPTree ts = findSubtree(ran.nextInt(nodeCount) + 1);
		if (os.up.left.equals(os)) {
			os.up.left = ts;
		} else {
			os.up.right = ts;
		}
		if (ts.up.left.equals(ts)) {
			ts.up.left = os;
		} else {
			ts.up.right = os;
		}
		GPTree op = os.up;
		os.up = ts.up;
		ts.up = op;
	}

	/**
	 * @param pMut
	 *            propability of mutation
	 * @param h
	 *            hight of the random subtree
	 * @param mode
	 *            0 is "full", 1 is "grow"
	 */
	public void mutate(double pMut, int h, int mode) {
		/*
		 * Der Baum wird mit einer Wahrscheinlichkeit von pMut mutiert und an
		 * einer zufaelligen Stelle durch einen zufaelligen Baum ersetzt.
		 */

		// Wie ist das zu verstehen? eine zufällige Stelle dieses Teilbaums?

		if (ran.nextDouble() <= pMut) {
			int index = ran.nextInt(nodeCount) + 1;// arraybaum index statet mit
													// 1
			GPTree sub = findSubtree(index);
			GPTree ne = makeRandomSubtree(h, mode);
			ne.up = sub.up;
			if (sub.up.left.equals(sub)) {
				ne.up.left = ne;
			} else {
				ne.up.right = ne;
			}
			sub.up = null;

		}
	}

	/**
	 * @param index
	 *            starts at 1
	 * @return the GPTree at this index
	 */
	private GPTree findSubtree(int index) {
		int cIndex = index;
		List<Integer> path = new ArrayList<Integer>(); // geht sicher
														// besser hab
														// ich mir
														// gerade selbst
														// so gedacht
		path.add(0, cIndex);
		while (cIndex < 2) {
			cIndex = cIndex / 2;
			path.add(0, cIndex);
		}
		GPTree aimNode = this;
		for (int i : path) {
			if ((i % 2) == 0) {
				aimNode = aimNode.left;
			} else {
				aimNode = aimNode.right;
			}
		}
		return aimNode;
	}
}
