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.MutationPipeline;
import ec.gp.semantic.func.SimpleNodeBase;
import ec.gp.semantic.geometry.GeometricHelpers;
import ec.gp.semantic.geometry.IGeometryProvider;
import ec.gp.syntactic.simplification.ISimplifier;
import ec.util.Parameter;

public class SGM extends MutationPipeline {

    private static final Parameter DEFAULT_BASE = GPBreedDefaults.base().push("SGM");
    public static final String P_BUILDER = "build";

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

    private RandomTreeProvider randomTreeProvider0 = new RandomTreeProvider();
    private RandomTreeProvider randomTreeProvider1 = new RandomTreeProvider();

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

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

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

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

            this.randomTreeProvider0.setup(state, base.push("treeProvider.0"));
			this.randomTreeProvider1.setup(state, base.push("treeProvider.1"));

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

    @Override
    public int produce(int min, int max, int start, int subpopulation, Individual[] inds, EvolutionState state, int thread) {
        // grab individuals from our source and stick 'em right into inds.
        // we'll modify them from there
        int n = sources[0].produce(min, max, start, subpopulation, inds, state, thread);

        // should we bother?
        if (!state.random[thread].nextBoolean(likelihood))
            return reproduce(n, start, subpopulation, inds, state, thread, false); // DON'T produce children from source -- we already did

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

        // now let's mutate 'em
        for (int q = start; q < n + start; q++) {
            GPIndividual i = (GPIndividual) inds[q];

            if (tree != TREE_UNFIXED && (tree < 0 || tree >= i.trees.length))
                // uh oh
                state.output
                        .fatal("GP Mutation 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");

            int t;
            // pick random tree
            if (tree == TREE_UNFIXED)
                if (i.trees.length > 1)
                    t = state.random[thread].nextInt(i.trees.length);
                else
                    t = 0;
            else
                t = tree;

            // validity result...
            //boolean res = false;

            // prepare the nodeselector
            nodeselect.reset();

            // pick a node

            GPNode p1 = i.trees[t].child; // the node we pick
            GPNode random1 = this.randomTreeProvider0.getRandomTree(thread, i.trees[t], 3);
            GPNode random2 = this.randomTreeProvider1.getRandomTree(thread, i.trees[t], 3);
            GPNode p2 = this.geometryProvider.differencingCombine(state, p1, random1, random2);

            GPIndividual j = (GPIndividual) (i.lightClone());

            // Fill in various tree information that didn't get filled in there
            j.trees = new GPTree[i.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 < j.trees.length; x++) {
                if (x == t/* && res*/) // we've got a tree with a kicking cross position!
                {
                    j.trees[x] = (GPTree) (i.trees[x].lightClone());
                    j.trees[x].owner = j;
                    j.trees[x].child = p2;//i.trees[x].child.cloneReplacingNoSubclone(p2, p1);

                    this.simplifier.simplify(j.trees[x].child);

                    j.trees[x].child.parent = j.trees[x];
                    j.trees[x].child.argposition = 0;
                    j.evaluated = false;
                } // it's changed
                else {
                    j.trees[x] = (GPTree) (i.trees[x].lightClone());
                    j.trees[x].owner = j;
                    j.trees[x].child = (GPNode) (i.trees[x].child/*.clone()*/);
                    j.trees[x].child.parent = j.trees[x];
                    j.trees[x].child.argposition = 0;
                }
            }

            // add the new individual, replacing its previous source
            inds[q] = j;

            GeometricHelpers.logMutation(1,
                    (SimpleNodeBase<?>) i.trees[t].child,
                    (SimpleNodeBase<?>) j.trees[t].child,
                    i,
                    j,
                    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;
    }

}
