package ec.gp.semantic.breed;

import ec.EvolutionState;
import ec.Individual;
import ec.gp.GPIndividual;
import ec.gp.GPNode;
import ec.gp.GPTree;
import ec.gp.breed.GPBreedDefaults;
import ec.gp.koza.MutationPipeline;
import ec.gp.semantic.DesiredSemanticsBase;
import ec.gp.semantic.ISemanticProblem;
import ec.gp.semantic.ISemantics;
import ec.gp.semantic.func.SimpleNodeBase;
import ec.gp.semantic.inverters.SemanticInverter;
import ec.gp.semantic.inverters.SemanticInverter.ErrorNodePair;
import ec.gp.semantic.statistics.ExtendedStatistics;
import ec.util.Parameter;
import ec.util.ParameterDatabase;

import java.lang.reflect.Constructor;

/**
 * Random Desired Operator as presented in Tomasz Pawlak et. al. "Semantic Backpropagation for Designing Search Operators
 * in Genetic Programming"
 *
 * @author Tomasz Pawlak
 */
public class RDO extends MutationPipeline {

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

    protected SemanticInverter<?> inverter;

    /**
     * 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<?> clazz = (Class<?>) db.getClassForParameter(base.push("inverter"), defaultBase().push("inverter"), SemanticInverter.class);
            Constructor<?> ctor = clazz.getConstructor(EvolutionState.class);
            this.inverter = (SemanticInverter<?>) ctor.newInstance(state);

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

            ExtendedStatistics stats = (ExtendedStatistics) state.statistics;
            stats.getExperiment().put("in_mutationAttempts", this.numTries);
            stats.getExperiment().put("in_mutationSensitivity", this.sensitivity);

        } 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) {
        // 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 = null; // the node we pick
            ISemanticProblem<?> provider = (ISemanticProblem<?>) state.evaluator.p_problem;
            SimpleNodeBase<?> offspring = null;
            ISemantics offspringSemantics = null;
            ISemantics parentSemantics = ((SimpleNodeBase<?>) i.trees[t].child).getSemantics();
            DesiredSemanticsBase<?> target = new DesiredSemanticsBase(provider.getTargetSemantics());

            inverter.invertTree(i.trees[t], target);


            for (int x = 0; x < numTries; x++) {
                // pick a node in individual 1
                p1 = nodeselect.pickNode(state, subpopulation, thread, i, i.trees[t]);

                ErrorNodePair replacement = (ErrorNodePair) inverter.getBestReplacementFor((SimpleNodeBase<?>) p1, this.maxDepth - p1.atDepth());
                assert replacement != null;


                offspring = (SimpleNodeBase<?>) i.trees[t].child.cloneReplacingNoSubclone(replacement.replacingNode, p1);
                offspringSemantics = offspring.getSemantics();

                if (parentSemantics.distanceTo(offspringSemantics) < sensitivity) {
                    continue;
                }

                res = true;
                // check for depth and swap-compatibility limits
                assert verifyPoints(replacement.replacingNode, p1); // p2 can fit in p1's spot  -- the order is important!;

                break;
            }

            inverter.clearCache();

            GPIndividual j;

            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 = offspring;// i.trees[x].child.cloneReplacingNoSubclone(p2, p1);
                    j.trees[x].child.parent = j.trees[x];
                    j.trees[x].child.argposition = 0;
                    j.evaluated = false;
                    ((SimpleNodeBase<?>) j.trees[x].child).resetSemanticsRecursive();
                } // 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;
        }
        return n;
    }

}
