package ec.gp.semantic.inverters;

import ec.EvolutionState;
import ec.gp.GPNode;
import ec.gp.GPTree;
import ec.gp.semantic.DesiredSemanticsBase;
import ec.gp.semantic.EvoState;
import ec.gp.semantic.ISemantics;
import ec.gp.semantic.SemanticsBase;
import ec.gp.semantic.func.SimpleNodeBase;
import ec.gp.semantic.library.ILibrary;
import library.space.SearchResult;

import java.util.IdentityHashMap;

public abstract class SemanticInverter<SemType> {
	
	private static final DesiredSemanticsBase[] EMPTY_ARRAY = new DesiredSemanticsBase[0];

	protected IdentityHashMap<GPNode, Descriptor> desiredSemantics = new IdentityHashMap<GPNode, Descriptor>(0x100);
	protected ILibrary<SemType> library;
	
	//protected boolean allowOnlyOneInversion = false;
	
	public SemanticInverter(EvolutionState state) {
		this.library = (ILibrary<SemType>) ((EvoState) state).getLibrary();
		//this.allowOnlyOneInversion = state.parameters.getBoolean(
		//		new Parameter("SemanticInverter.allowOnlyOneInversion"), null, false);
	}
	
	public void clearCache() {
		this.desiredSemantics.clear();
	}
	
	protected abstract DesiredSemanticsBase<SemType> invertInstruction(final SimpleNodeBase<?> node,
	                                                                   final int forChild, final DesiredSemanticsBase<SemType> desiredSemantics,
	                                                                   final ISemantics[] childrenSemantics);
	
	public abstract DesiredSemanticsBase<SemType> convert(ISemantics semantics);
	
	private DesiredSemanticsBase<SemType> invert(final SimpleNodeBase<?> node,
	                                             final DesiredSemanticsBase<SemType> desiredSemantics, final int forChild) {
		
		ISemantics[] childrenSemantics = null;
		if (node.children.length > 1) {
			childrenSemantics = new ISemantics[node.children.length - 1];
			for (int i = 0, j = 0; i < node.children.length; ++i) {
				if (i == forChild)
					continue;
				
				childrenSemantics[j++] = ((SimpleNodeBase<?>) node.children[i]).getSemantics();
			}
		}
		
		// calculate inversion
		return this.invertInstruction(node, forChild, desiredSemantics, childrenSemantics);
		
	}
	
	public void invertTree(final GPTree tree, ISemantics _desired, ISemantics... _forbidden) {
		DesiredSemanticsBase<SemType> desired = this.convert((SemanticsBase<SemType>) _desired);
		
		DesiredSemanticsBase<SemType>[] forbidden = _forbidden.length == 0 ? EMPTY_ARRAY : new DesiredSemanticsBase[_forbidden.length];
		for (int i = 0; i < _forbidden.length; ++i) {
			forbidden[i] = this.convert(_forbidden[i]);
		}
		this.invertTree(tree, desired, forbidden);
	}
	
	public void invertTree(final GPTree tree, DesiredSemanticsBase<?> desired, DesiredSemanticsBase<?>... forbidden) {
		this.desiredSemantics.put(tree.child, new Descriptor((DesiredSemanticsBase<SemType>) desired, (DesiredSemanticsBase<SemType>[]) forbidden));
		this.invertRecursive((SimpleNodeBase<?>) tree.child, (DesiredSemanticsBase<SemType>) desired, (DesiredSemanticsBase<SemType>[]) forbidden);
	}
	
	protected void invertRecursive(final SimpleNodeBase<?> node, final DesiredSemanticsBase<SemType> desired,
	                               final DesiredSemanticsBase<SemType>... forbidden) {
			for (int i = 0; i < node.children.length; ++i) {
				DesiredSemanticsBase<SemType> desiredInversions = invert(node, desired, i);
				DesiredSemanticsBase<SemType>[] forbiddenInversions;
				DesiredSemanticsBase<SemType>[] forbiddenInversionsPlusCurrent;
				
				if (forbidden.length == 0) {
					forbiddenInversions = EMPTY_ARRAY;
					forbiddenInversionsPlusCurrent = EMPTY_ARRAY;
				} else {
					forbiddenInversions = new DesiredSemanticsBase[forbidden.length];
					forbiddenInversionsPlusCurrent = new DesiredSemanticsBase[forbidden.length + 1];
				}
				
				for (int f = 0; f < forbidden.length; ++f) {
					forbiddenInversions[f] = invert(node, forbidden[f], i);
					forbiddenInversionsPlusCurrent[f] = forbiddenInversions[f];
				}
				
				if (forbidden.length > 0) {
					forbiddenInversionsPlusCurrent[forbidden.length] = this.convert(((SimpleNodeBase<?>) node.children[i]).getSemantics());
				}
				
				this.desiredSemantics.put(node.children[i], new Descriptor(desiredInversions, forbiddenInversionsPlusCurrent));
				
				this.invertRecursive((SimpleNodeBase<?>) node.children[i], desiredInversions, forbiddenInversions);
			}
	}
	
	public class ErrorNodePair {
		public double currentNodeError = Double.MAX_VALUE;
		public double replacingNodeError = Double.MAX_VALUE;
		public SimpleNodeBase<?> currentNode;
		public SimpleNodeBase<?> replacingNode;
		
		@Override
		public String toString() {
			return String.format("Error: %.2f, current: %s, replacing: %s", replacingNodeError,
					currentNode.makeLispTree(),
					replacingNode.makeLispTree());
		}
	}
	
	public ErrorNodePair chooseNodeWithSmallestError(final SimpleNodeBase<?> node, final int maxHeight) {
		ErrorNodePair bestSoFar = new ErrorNodePair();
		chooseNodeWithSmallestError(node, bestSoFar, maxHeight);
		return bestSoFar;
	}
	
	protected void chooseNodeWithSmallestError(final SimpleNodeBase<?> node, final ErrorNodePair bestSoFar,
	                                           final int maxHeight) {
		SearchResult<GPNode> closestProcedure;
		
		Descriptor descriptor = this.desiredSemantics.get(node);
		if (descriptor != null) {
			closestProcedure = library.getProgram(descriptor.desired, descriptor.forbidden, maxHeight);
			
			double error = closestProcedure.getError();
			
			if (error < bestSoFar.replacingNodeError) {
				bestSoFar.currentNode = node;
				bestSoFar.currentNodeError = this.library.calculateError(descriptor.desired, descriptor.forbidden, node.getSemantics());
				bestSoFar.replacingNode = (SimpleNodeBase<?>) closestProcedure.getProgram();
				bestSoFar.replacingNodeError = error;
			}
		}
		
		if (maxHeight > 1) {
			for (final GPNode child : node.children) {
				chooseNodeWithSmallestError((SimpleNodeBase<?>) child, bestSoFar, maxHeight - 1);
			}
		}
	}
	
	public ErrorNodePair getBestReplacementFor(final SimpleNodeBase<?> node, final int maxHeight) {
		SearchResult<GPNode> closestProcedure;
		ErrorNodePair pair = new ErrorNodePair();
		
		Descriptor descriptor = this.desiredSemantics.get(node);
		if (descriptor != null) {
			closestProcedure = library.getProgram(descriptor.desired, descriptor.forbidden, maxHeight);
			
			pair.currentNode = node;
			pair.currentNodeError = library.calculateError(descriptor.desired, descriptor.forbidden, node.getSemantics());
			pair.replacingNode = (SimpleNodeBase<?>) closestProcedure.getProgram();
			pair.replacingNodeError = closestProcedure.getError();
		}
		
		if (pair.currentNode == null)
			return null;
		return pair;
	}
	
	class Descriptor {
		public final DesiredSemanticsBase<SemType> desired;
		public final DesiredSemanticsBase<SemType>[] forbidden;
		
		Descriptor(DesiredSemanticsBase<SemType> desiredSemantics, DesiredSemanticsBase<SemType>[] forbiddenSemantics) {
			this.desired = desiredSemantics;
			this.forbidden = forbiddenSemantics;
		}
	}
	
	/**
	 *
	 * @param toNode
	 *            Node to which the path will be inverted
	 * @param desiredRootSemantics
	 */
	/*public void invertPath(final SimpleNodeBase toNode, final ISemantics desiredRootSemantics) {
		this.invertPath(toNode, -1, desiredRootSemantics);
	}

	private void invertPath(final SimpleNodeBase node, final int childIndex, final ISemantics desiredRootSemantics) {
		// if we have cached desired semantics for node 'node', we must have cached all 
		// semantics on the path to the root (assuming they were inverted by invertPath method),
		// so stop inverting ancestors
		if (desiredSemantics.containsKey(node)) {
			return;
		}

		if (node.parent instanceof GPNode) {
			// go bottom-up
			invertPath((SimpleNodeBase) node.parent, node.argposition, desiredRootSemantics);
		} else {
			// we are the root, add desired semantics to cache
			desiredSemantics.put(node, new double[][] { desiredRootSemantics });
		}

		// go top-down
		// we are the root or the desired semantics is calculated for us

		if (childIndex < 0)
			return; // do not go deeper

		double[][] myInversions = this.desiredSemantics.get(node);
		if (myInversions == null)
			return; // cannot invert path, return

		List<double[]> childInversions = new ArrayList<double[]>();
		for (double[] inversion : myInversions) {
			childInversions.addAll(invert(node, inversion, childIndex));
		}
		desiredSemantics.put(node.children[childIndex], childInversions.toArray(new double[0][]));
	}*/
	
}
