package ec.gp.semantic.breed;

import java.lang.reflect.Constructor;

import ec.*;
import ec.gp.*;
import ec.gp.breed.*;
import ec.gp.koza.*;
import ec.gp.semantic.*;
import ec.gp.semantic.func.*;
import ec.gp.semantic.inverters.*;
import ec.gp.semantic.inverters.SemanticInverter.ErrorNodePair;
import ec.util.*;

/**
 * Globally Geometric Semantic Crossover. The variant, that chooses the best crossover loci.
 * 
 * @author Tomasz Pawlak
 */
@Deprecated
public class AGXBest extends CrossoverPipeline {

	private static final Parameter DEFAULT_BASE = GPBreedDefaults.base().push("AGXBest");
	private SemanticInverter inverter;

	@Override
	public Parameter defaultBase() {
		return DEFAULT_BASE;
	}

	@Override
	public void setup(EvolutionState state, Parameter base) {
		super.setup(state, base);

		try {
			Class<?> clazz = (Class<?>) state.parameters.getClassForParameter(base.push("inverter"), defaultBase()
					.push("inverter"), SemanticInverter.class);
			Constructor<?> ctor = clazz.getConstructor(EvolutionState.class);
			this.inverter = (SemanticInverter<?>) ctor.newInstance(state);

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public int produce(int min, int max, int start, int subpopulation, Individual[] inds, EvolutionState state,
			int thread) {
		// how many individuals should we make?
		int n = typicalIndsProduced();
		if (n < min)
			n = min;
		if (n > max)
			n = max;

		// should we bother?
		if (!state.random[thread].nextBoolean(likelihood))
			return reproduce(n, start, subpopulation, inds, state, thread, true); // DO produce children from source -- we've not done so already

		GPInitializer initializer = ((GPInitializer) state.initializer);

		for (int q = start; q < n + start; /* no increment */) // keep on going until we're filled up
		{
			// grab two individuals from our sources
			if (sources[0] == sources[1]) // grab from the same source
				sources[0].produce(2, 2, 0, subpopulation, parents, state, thread);
			else // grab from different sources
			{
				sources[0].produce(1, 1, 0, subpopulation, parents, state, thread);
				sources[1].produce(1, 1, 1, subpopulation, parents, state, thread);
			}

			// at this point, parents[] contains our two selected individuals

			// are our tree values valid?
			if (tree1 != TREE_UNFIXED && (tree1 < 0 || tree1 >= parents[0].trees.length))
				// uh oh
				state.output
						.fatal("GP Crossover Pipeline attempted to fix tree.0 to a value which was out of bounds of the array of the individual's trees.  Check the pipeline's fixed tree values -- they may be negative or greater than the number of trees in an individual");
			if (tree2 != TREE_UNFIXED && (tree2 < 0 || tree2 >= parents[1].trees.length))
				// uh oh
				state.output
						.fatal("GP Crossover Pipeline attempted to fix tree.1 to a value which was out of bounds of the array of the individual's trees.  Check the pipeline's fixed tree values -- they may be negative or greater than the number of trees in an individual");

			int t1 = 0;
			int t2 = 0;
			if (tree1 == TREE_UNFIXED || tree2 == TREE_UNFIXED) {
				do
				// pick random trees  -- their GPTreeConstraints must be the same
				{
					if (tree1 == TREE_UNFIXED)
						if (parents[0].trees.length > 1)
							t1 = state.random[thread].nextInt(parents[0].trees.length);
						else
							t1 = 0;
					else
						t1 = tree1;

					if (tree2 == TREE_UNFIXED)
						if (parents[1].trees.length > 1)
							t2 = state.random[thread].nextInt(parents[1].trees.length);
						else
							t2 = 0;
					else
						t2 = tree2;
				} while (parents[0].trees[t1].constraints(initializer) != parents[1].trees[t2].constraints(initializer));
			} else {
				t1 = tree1;
				t2 = tree2;
				// make sure the constraints are okay
				if (parents[0].trees[t1].constraints(initializer) != parents[1].trees[t2].constraints(initializer)) // uh oh
					state.output
							.fatal("GP Crossover Pipeline's two tree choices are both specified by the user -- but their GPTreeConstraints are not the same");
			}

			// pick some nodes

			ISemantics p1Semantics = ((SimpleNodeBase) parents[0].trees[t1].child).getSemantics();
			ISemantics p2Semantics = ((SimpleNodeBase) parents[1].trees[t2].child).getSemantics();
			ISemantics avgSemantics = p1Semantics.getMidpointBetweenMeAnd(p2Semantics);

			inverter.invertTree(parents[0].trees[t1], avgSemantics, p1Semantics);
			inverter.invertTree(parents[1].trees[t2], avgSemantics, p2Semantics);

			/*double[] p1Sem1 = ((SimpleNodeBase) parents[0].trees[t1].child).getSemantics().clone();
			double[] p2Sem1 = ((SimpleNodeBase) parents[1].trees[t2].child).getSemantics().clone();
			
			((SimpleNodeBase) parents[0].trees[t1].child).resetSemanticsRecursive();
			((SimpleNodeBase) parents[1].trees[t2].child).resetSemanticsRecursive();
			
			double[] p1Sem2 = ((SimpleNodeBase) parents[0].trees[t1].child).getSemantics().clone();
			double[] p2Sem2 = ((SimpleNodeBase) parents[1].trees[t2].child).getSemantics().clone();
			
			double[] p1Sem3 = new double[p1Sem1.length];
			double[] p2Sem3 = new double[p2Sem1.length];
			
			((ISemanticsProvider) state.evaluator.p_problem).computeSemantics((SimpleNodeBase) parents[0].trees[t1].child, p1Sem3);
			((ISemanticsProvider) state.evaluator.p_problem).computeSemantics((SimpleNodeBase) parents[1].trees[t2].child, p2Sem3);
			
			
			for (int i = 0; i < avgSemantics.length; ++i) {
				assert p1Sem1[i] == p1Sem2[i];
				assert p2Sem1[i] == p2Sem2[i];
				
				assert p1Sem1[i] == ((SimpleNodeBase) parents[0].trees[t1].child).getSemantics()[i];
				assert p2Sem1[i] == ((SimpleNodeBase) parents[1].trees[t2].child).getSemantics()[i];
				
				assert p1Sem1[i] == p1Sem3[i];
				assert p2Sem1[i] == p2Sem3[i];
				
				assert 0.5 * (((SimpleNodeBase) parents[0].trees[t1].child).getSemantics()[i] + ((SimpleNodeBase) parents[1].trees[t2].child)
						.getSemantics()[i]) == avgSemantics[i];
			}*/

			// pick a node in individual 1
			ErrorNodePair replacement1 = inverter.chooseNodeWithSmallestError(
					(SimpleNodeBase) parents[0].trees[t1].child, this.maxDepth);

			// pick a node in individual 2
			ErrorNodePair replacement2 = inverter.chooseNodeWithSmallestError(
					(SimpleNodeBase) parents[1].trees[t2].child, this.maxDepth);

			// clear the cache
			inverter.clearCache();

			// at this point, res1 AND res2 are valid, OR
			// either res1 OR res2 is valid and we ran out of tries, OR
			// neither res1 nor res2 is valid and we rand out of tries.
			// So now we will transfer to a tree which has res1 or res2
			// valid, otherwise it'll just get replicated.  This is
			// compatible with both Koza and lil-gp.

			// at this point I could check to see if my sources were breeding
			// pipelines -- but I'm too lazy to write that code (it's a little
			// complicated) to just swap one individual over or both over,
			// -- it might still entail some copying.  Perhaps in the future.
			// It would make things faster perhaps, not requiring all that
			// cloning.

			// Create some new individuals based on the old ones -- since
			// GPTree doesn't deep-clone, this should be just fine.  Perhaps we
			// should change this to proto off of the main species prototype, but
			// we have to then copy so much stuff over; it's not worth it.

			GPIndividual j1 = (GPIndividual) (parents[0].lightClone());
			GPIndividual j2 = null;
			if (n - (q - start) >= 2 && !tossSecondParent)
				j2 = (GPIndividual) (parents[1].lightClone());

			// Fill in various tree information that didn't get filled in there
			j1.trees = new GPTree[parents[0].trees.length];
			if (n - (q - start) >= 2 && !tossSecondParent)
				j2.trees = new GPTree[parents[1].trees.length];

			// at this point, p1 or p2, or both, may be null.
			// If not, swap one in.  Else just copy the parent.

			for (int x = 0; x < j1.trees.length; x++) {
				if (x == t1) // we've got a tree with a kicking cross position!
				{
					j1.trees[x] = (GPTree) (parents[0].trees[x].lightClone());
					j1.trees[x].owner = j1;
					j1.trees[x].child = parents[0].trees[x].child.cloneReplacing(replacement1.replacingNode,
							replacement1.currentNode);
					j1.trees[x].child.parent = j1.trees[x];
					j1.trees[x].child.argposition = 0;
					j1.evaluated = false;
					((SimpleNodeBase) j1.trees[x].child).resetSemanticsRecursive();
				} // it's changed
				else {
					j1.trees[x] = (GPTree) (parents[0].trees[x].lightClone());
					j1.trees[x].owner = j1;
					j1.trees[x].child = (GPNode) (parents[0].trees[x].child.clone());
					j1.trees[x].child.parent = j1.trees[x];
					j1.trees[x].child.argposition = 0;
				}
			}

			if (n - (q - start) >= 2 && !tossSecondParent)
				for (int x = 0; x < j2.trees.length; x++) {
					if (x == t2) // we've got a tree with a kicking cross position!
					{
						j2.trees[x] = (GPTree) (parents[1].trees[x].lightClone());
						j2.trees[x].owner = j2;
						j2.trees[x].child = parents[1].trees[x].child.cloneReplacing(replacement2.replacingNode,
								replacement2.currentNode);
						j2.trees[x].child.parent = j2.trees[x];
						j2.trees[x].child.argposition = 0;
						j2.evaluated = false;
						((SimpleNodeBase) j2.trees[x].child).resetSemanticsRecursive();
					} // it's changed
					else {
						j2.trees[x] = (GPTree) (parents[1].trees[x].lightClone());
						j2.trees[x].owner = j2;
						j2.trees[x].child = (GPNode) (parents[1].trees[x].child.clone());
						j2.trees[x].child.parent = j2.trees[x];
						j2.trees[x].child.argposition = 0;
					}
				}

			// add the individuals to the population
			inds[q] = j1;
			q++;
			if (q < n + start && !tossSecondParent) {
				inds[q] = j2;
				q++;
			}
		}
		return n;
	}

}
