package library.inverter;

import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;

import library.generator.TreeNode;
import library.instructions.InstructionBase;

/**
 * Performs semantic backpropagation on a given program.
 * 
 * @author Tomasz Pawlak
 * 
 */
public abstract class Inverter {

	private int semanticsLength;

	private IdentityHashMap<TreeNode, Double[][]> inversions = new IdentityHashMap<TreeNode, Double[][]>();

	/**
	 * Constructor for inheritance purposes. Obligatory call init method from your derived class!
	 */
	protected Inverter() {

	}

	public Inverter(final TreeNode root, final Double[]... desiredRootSemantics) {
		this.init(root, desiredRootSemantics);
	}

	protected void init(final TreeNode root, final Double[]... desiredRootSemantics) {
		if (desiredRootSemantics.length == 0)
			throw new IllegalArgumentException("At least one desired semantics must be supplied.");

		this.semanticsLength = desiredRootSemantics[0].length;
		this.inversions.put(root, desiredRootSemantics);
		this.invertRecursive(root, desiredRootSemantics);
	}

	public Double[][] getDesiredSemanticsForNode(final TreeNode node) {
		return this.inversions.get(node);
	}

	protected abstract Double[] getSemanticsForNode(final TreeNode node);

	private void invertRecursive(final TreeNode node, Double[]... desiredOutputs) {
		Double[][] inversions;
		TreeNode[] children = node.getChildren();

		for (int i = 0; i < children.length; ++i) {
			inversions = this.invertNode(node, desiredOutputs, i);
			if (inversions.length == 0)
				continue; // cannot invert at current level
			
			this.inversions.put(children[i], inversions);
			this.invertRecursive(children[i], inversions);
		}

	}

	private Double[][] invertNode(final TreeNode node, final Double[][] desiredOutputs, final int forChild) {
		int childCount = node.getChildren().length;
		List<Double[]> writeTo = new ArrayList<Double[]>();
		int commitedSemanticsCount = 0;

		Double[][] otherChildren = null;
		Double[] otherArgs = null;

		// if there are other children, get their semantics
		if (childCount > 1) {
			otherChildren = new Double[childCount][];
			otherArgs = new Double[childCount];

			for (int i = 0; i < childCount; ++i) {
				if (i != forChild)
					otherChildren[i] = getSemanticsForNode(node.getChildren()[i]);//((SemanticTreeNode)node.getChildren()[i]).getSemantics();
			}
		}

		// for each given desired semantics calculate inversion
		for (final Double[] desiredSemantics : desiredOutputs) {
			for (int i = 0; i < this.semanticsLength; ++i) {
				if (childCount > 1) {
					for (int j = 0; j < childCount; ++j) {
						if (j != forChild) {
							otherArgs[j] = otherChildren[j][i];
						}
					}
				}

				// get inversions of single value
				Double[] inverted = ((InstructionBase<Double>) node.getInstruction()).invert(desiredSemantics[i],
						forChild, otherArgs);

				if (inverted == null) {
					// remove inserted partial semantics
					while (writeTo.size() > commitedSemanticsCount)
						writeTo.remove(writeTo.size() - 1);
					break;
				}

				// place the inverted values in semantics vectors
				for (int j = 0; j < inverted.length; ++j) {
					Double[] sem;
					if (commitedSemanticsCount + j < writeTo.size()) {
						sem = writeTo.get(commitedSemanticsCount + j);
					} else {
						sem = new Double[this.semanticsLength];
						writeTo.add(sem);
					}
					sem[i] = inverted[j];
				}
			}

			commitedSemanticsCount = writeTo.size();
		}

		return writeTo.toArray(new Double[0][0]);
	}
}
