package ec.gp.semantic.select;

import ec.EvolutionState;
import ec.Individual;
import ec.gp.GPIndividual;
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;

/**
 * Semantic Tournament Selection as described in Edgar Galvan-Lopez, Brendan Cody-Kenny, Leonardo Trujillo, Ahmed Kattan
 * "Using Semantics in Selection Mechanism in Genetic Programming: a Simple Methods for Promoting Semantic Diversity",
 * with an exception that we added a semantic sensitivity threshold for comparison of semantics.
 * 
 * @author Tomasz Pawlak
 * 
 */
public class STS extends TournamentSelection {

	private static final Parameter DEFAULT_BASE = new Parameter("STS");
	private static final String SENSITIVITY = "sensitivity";

	protected EvolutionState state;

	// 2^-52 = 0x3FF0000000000000L - 0x0340000000000000L
	protected double sensitivity = Double.longBitsToDouble(0x3CB0000000000000L);
	private 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.sensitivity = state.parameters.getDouble(base.push(SENSITIVITY), defaultBase().push(SENSITIVITY));

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

	@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 (nextChosenInd >= 0) {
			int id = nextChosenInd;
			nextChosenInd = -1;
			return id;
		}

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

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

		int s = getTournamentSizeToUse(state.random[thread]);
		int competitor;
		GPIndividual competitorInd;
		ISemantics competitorSem;

		int best = this.getRandomIndividual(0, subpopulation, state, thread);

		for (int i = 1; i <= s; ++i) {
			competitor = this.getRandomIndividual(i, subpopulation, state, thread);
			competitorInd = (GPIndividual) state.population.subpops[subpopulation].individuals[competitor];
			competitorSem = ((SimpleNodeBase<?>) competitorInd.trees[0].child).getSemantics();

			if (firstParentSem.distanceTo(competitorSem) >= this.sensitivity) {
				if (this.pickWorst && betterThan(oldinds[best], oldinds[competitor], subpopulation, state, thread)) {
					best = competitor;
				} else if (betterThan(oldinds[competitor], oldinds[best], subpopulation, state, thread)) {
					best = competitor;
				}
			}

		}

		this.nextChosenInd = best;
		return firstParent;
	}

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