package ec.gp.semantic.breed;

import ec.EvolutionState;
import ec.Individual;
import ec.gp.GPIndividual;
import ec.gp.GPInitializer;
import ec.gp.GPNode;
import ec.gp.GPTree;
import ec.gp.breed.GPBreedDefaults;
import ec.gp.koza.CrossoverPipeline;
import ec.gp.semantic.ISemantics;
import ec.gp.semantic.func.SimpleNodeBase;
import ec.gp.semantic.geometry.GeometricHelpers;
import ec.gp.semantic.geometry.IGeometryProvider;
import ec.gp.semantic.statistics.ExtendedStatistics;
import ec.gp.syntactic.simplification.ISimplifier;
import ec.util.Parameter;
import ec.util.ParameterDatabase;

/**
 * Semantic Geometric Crossover extended for n parents.
 *
 * @author Tomasz Pawlak
 */
public class SGXn extends CrossoverPipeline {

	private static final Parameter DEFAULT_BASE = GPBreedDefaults.base().push("SGX");
	protected static final String SENSITIVITY = "sensitivity";

	private IGeometryProvider geometryProvider;
	private ISimplifier simplifier;
	//private SubtreeRewriter rewriter = new SubtreeRewriter();

	private RandomTreeProvider randomTreeProvider = new RandomTreeProvider();

	/**
	 * Discards offspring if it distance to any of parents is lower than the given value. 0 to disable.
	 */
	protected double sensitivity = 0.0;

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

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

		ParameterDatabase db = state.parameters;

		try {
			Class<?> geometryProviderClass = (Class<?>) db.getClassForParameter(base.push("geometryProvider"), defaultBase().push("geometryProvider"), IGeometryProvider.class);
			this.geometryProvider = (IGeometryProvider) geometryProviderClass.newInstance();

			Class<?> simplifierClass = (Class<?>) db.getClassForParameter(base.push("simplifier"), defaultBase().push("simplifier"), ISimplifier.class);
			this.simplifier = (ISimplifier) simplifierClass.newInstance();
			this.simplifier.setup(state, base);

			this.randomTreeProvider.setup(state, base.push("treeProvider"));

			this.sensitivity = db.getDoubleWithDefault(base.push(SENSITIVITY), defaultBase().push(SENSITIVITY), this.sensitivity);

			assert this.sources.length > 0;
			this.parents = new GPIndividual[this.sources.length];

			ExtendedStatistics stats = (ExtendedStatistics) state.statistics;
			stats.getExperiment().put("in_crossoverAttempts", this.numTries);
			stats.getExperiment().put("in_crossoverSensitivity", this.sensitivity);
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
	}

	@Override
	public int numSources() {
		return DYNAMIC_SOURCES; // This causes number of sources to be read from params file. See BreedingPipeline.setup()
	}

	@Override
	public int typicalIndsProduced() {
		return 1;
	}

	@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
		{
			assert this.sources.length == this.parents.length;
			assert this.parents.length >= 2;

			ISemantics[] parentSemantics = new ISemantics[this.parents.length];
			SimpleNodeBase[] parentTrees = new SimpleNodeBase[this.parents.length];

			for (int i = 0; i < this.parents.length; ++i) {
				this.sources[i].produce(1, 1, i, subpopulation, this.parents, state, thread);
				parentSemantics[i] = ((SimpleNodeBase<?>) this.parents[i].trees[0].child).getSemantics();
				parentTrees[i] = (SimpleNodeBase<?>) this.parents[i].trees[0].child;
			}

			// at this point, parents[] contains n 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");
			}*/

			// 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];

			SimpleNodeBase<?> offspring = null;
			ISemantics offspringSemantics;

			for (int t = 0; t < this.numTries; ++t) {
				GPNode[] randomTrees = new SimpleNodeBase[(int) Math.ceil(Math.log(parents.length))];
				for (int r = 0; r < randomTrees.length; ++r) {
					randomTrees[r] = this.randomTreeProvider.getRandomTree(thread, parents[0].trees[0], 3);
				}

				offspring = (SimpleNodeBase<?>) this.geometryProvider.combine(
						state,
						parentTrees,
						randomTrees);

				this.simplifier.simplify(offspring);
				offspringSemantics = offspring.getSemantics();

				// We need to use negation here (instead of use of De Morgan rules) due to NaNs and Infinities that can occur.
				// In such a case we need a more sophisticated comparison, which we omit here.
				boolean allDifferent = true;
				for (int p = 0; p < parentSemantics.length; ++p) {
					if (parentSemantics[p].distanceTo(offspringSemantics) < this.sensitivity) {
						allDifferent = false;
						break;
					}
				}

				if (allDifferent) {

					// offspring1 and offspring2 are effective, break the loop
					break;
				}
			}

			for (int x = 0; x < j1.trees.length; x++) {
				if (x == 0) // 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 = offspring;
					j1.trees[x].child.parent = j1.trees[x];
					j1.trees[x].child.argposition = 0;
					j1.evaluated = false;
				} // 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) {
				//randomTree = this.randomTreeProvider.getRandomTree(thread, parents[0].trees[t1], 2);
				assert false : "we have produced only one offspring";

				for (int x = 0; x < j2.trees.length; x++) {
					if (x == 0) // 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 = offspring;
						j2.trees[x].child.parent = j2.trees[x];
						j2.trees[x].child.argposition = 0;
						j2.evaluated = false;
					} // 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++;

			/*GeometricHelpers.logGeometricity(1,
					(SimpleNodeBase<?>) parents[0].trees[t1].child,
					(SimpleNodeBase<?>) parents[1].trees[t2].child,
					(SimpleNodeBase<?>) j1.trees[t1].child,
					parents[0],
					parents[1],
					j1,
					state);

			if (q < n + start && !tossSecondParent) {
				inds[q] = j2;
				q++;

				GeometricHelpers.logGeometricity(1,
						(SimpleNodeBase<?>) parents[0].trees[t1].child,
						(SimpleNodeBase<?>) parents[1].trees[t2].child,
						(SimpleNodeBase<?>) j2.trees[t2].child,
						parents[0],
						parents[1],
						j2,
						state);
			}
			*/
		}

		if (start + n >= inds.length) {
			this.simplifier.reset();

			/*long sTime = System.currentTimeMillis();
			state.output.message("Running tree rewriting...");
			this.rewriter.rewrite(inds);
			state.output.message(String.format("Tree rewriting done in %.4fs...",
					(double) (System.currentTimeMillis() - sTime) * 0.001));*/
		}

		return n;
	}
}
