package ec.gp.semantic.select;

import ec.EvolutionState;
import ec.Individual;
import ec.gp.GPIndividual;
import ec.gp.semantic.ISemanticProblem;
import ec.gp.semantic.ISemantics;
import ec.gp.semantic.func.SimpleNodeBase;
import ec.gp.semantic.statistics.ExtendedStatistics;
import ec.select.TournamentSelection;
import ec.util.Parameter;
import experimentdatabase.DataSet;

/**
 * Competent Tournament Selection as described in Tomasz Pawlak "Competent Algorithms for Geometric Semantic Genetic Programming"
 *
 * @author Tomasz Pawlak
 */
public class CTS extends TournamentSelection {

    private static final Parameter DEFAULT_BASE = new Parameter("CTS");

    protected EvolutionState state;
    protected ISemantics target;
    protected int nextChosenInd = -1;

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

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

        this.state = state;
        this.target = ((ISemanticProblem) state.evaluator.p_problem).getTargetSemantics();

        int size = this.getTournamentSizeToUse(state.random[0]);
        ExtendedStatistics statistics = (ExtendedStatistics)state.statistics;
        DataSet experiment = statistics.getExperiment();
        experiment.put("in_tournamentSize", size);
    }

    @Override
    public int typicalIndsProduced() {
        return 2; // this method is designed to return two individuals
    }

    @Override
    public int produce(int subpopulation, EvolutionState state, int thread) {
        if (this.nextChosenInd >= 0) {
            int id = this.nextChosenInd;
            this.nextChosenInd = -1;
            return id;
        }

        final int firstParent = super.produce(subpopulation, state, thread);
        final GPIndividual firstParentInd = (GPIndividual) state.population.subpops[subpopulation].individuals[firstParent];
        final ISemantics firstParentSem = ((SimpleNodeBase<?>) firstParentInd.trees[0].child).getSemantics();

        // pick size random individuals, then pick the best.
        final Individual[] oldinds = state.population.subpops[subpopulation].individuals;

        int s = getTournamentSizeToUse(state.random[thread]);
        int r;
        ISemantics candidateSem;
        ISemantics bestSem;
        int best = -1;
        double bestDistance = Double.POSITIVE_INFINITY;
        double candidateDistance;
        double targetToCandidate;
        double firstParentToCandidate;
        double firstParentToTarget = this.target.distanceTo(firstParentSem);

        assert s < oldinds.length;
        int[] randoms = new int[oldinds.length];
        for (int i = 0; i < randoms.length; ++i)
            randoms[i] = i;

        int[] ids = new int[s];
        for (int i = 0; i < s; ++i) {
            // pick a random individual from the population, repetitions are forbidden
            r = state.random[thread].nextInt(randoms.length - i);
            ids[i] = randoms[r];
            randoms[r] = randoms[randoms.length - i - 1];
            // It's here for clarity, actually we will never read this value.
            // randoms[randoms.length - i - 1] = ids[i];

            // candidate index is given by ids[i]
            candidateSem = ((SimpleNodeBase<?>) ((GPIndividual) oldinds[ids[i]]).trees[0].child).getSemantics();
            targetToCandidate = this.target.distanceTo(candidateSem);
            firstParentToCandidate = firstParentSem.distanceTo(candidateSem);
            candidateDistance = targetToCandidate / firstParentToCandidate * (1.0 + Math.abs(firstParentToTarget - targetToCandidate));

            assert Double.POSITIVE_INFINITY == Double.POSITIVE_INFINITY;
            if (candidateDistance < bestDistance || best == -1 || (Double.isNaN(bestDistance) && !Double.isNaN(candidateDistance))) {
                bestDistance = candidateDistance;
                best = ids[i];
                bestSem = candidateSem;
            }
        }

        assert best >= 0 && best < oldinds.length && best != firstParent;
        this.nextChosenInd = best;
        return firstParent;
    }

    @Override
    public void finishProducing(final EvolutionState s,
                                final int subpopulation,
                                final int thread) {
        this.nextChosenInd = -1;
    }

}
