package ec.gp.semantic.select;

import ec.*;
import ec.gp.*;
import ec.gp.semantic.*;
import ec.gp.semantic.func.*;
import ec.select.*;
import ec.util.*;

/**
 * Geometric Semantic Tournament. It draws at random n individuals from the population, then it chooses such a pair of
 * them, that minimizes the sum of semantic distance between target and the intermediate point between individuals. It
 * prohibits the chosen parents to be semantically equal.
 * 
 * @author Tomasz Pawlak
 * 
 */
public class GST2 extends TournamentSelection {

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

	protected EvolutionState state;
	protected double sensitivity = 1E-6;
	protected ISemantics target;
	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.target = ((ISemanticProblem) state.evaluator.p_problem).getTargetSemantics();
		this.sensitivity = state.parameters.getDouble(base.push(SENSITIVITY), defaultBase().push(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;
		}

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

		int s = getTournamentSizeToUse(state.random[thread]);
		double bestDistance = pickWorst ? 0 : Double.MAX_VALUE;
		double bestNormalizedDistance = pickWorst ? 0 : Double.MAX_VALUE;
		int bestI = -1;
		int bestJ = -1;
		double[] distances = new double[s];
		double distance;
		double distanceBetweenCandidates;
		double normalizedDistance;
		ISemantics iSem, jSem;
		int r;

		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];
			randoms[randoms.length - i - 1] = ids[i];

			iSem = ((SimpleNodeBase<?>) ((GPIndividual) oldinds[ids[i]]).trees[0].child).getSemantics();
			distances[i] = target.distanceTo(iSem);
			for (int j = 0; j < i; ++j) {
				jSem = ((SimpleNodeBase<?>) ((GPIndividual) oldinds[ids[j]]).trees[0].child).getSemantics();
				distanceBetweenCandidates = iSem.distanceTo(jSem);

				if (!(distanceBetweenCandidates >= this.sensitivity))
					continue;

				distance = distances[i] + distances[j];
				//normalizedDistance = distance / distanceBetweenCandidates;
				//double p = 0.5 * (distance + distanceBetweenCandidates);
				//normalizedDistance = distance / Math.sqrt(p * (p - distances[i]) * (p - distances[j]) * (p - distanceBetweenCandidates));
				normalizedDistance = iSem.getMidpointBetweenMeAnd(jSem).distanceTo(target);

				if (Double.isNaN(normalizedDistance) || Double.isNaN(distance)) {
					continue;
				}

				if (Double.isInfinite(normalizedDistance))
					// it is slightly less than maximum value Double.MAX_VALUE; we must use this number due to comparison few lines below
					normalizedDistance = 1E308;

				if (Double.isInfinite(distance))
					// it is slightly less than maximum value Double.MAX_VALUE; we must use this number due to comparison few lines below
					distance = 1E308;

				boolean accept = (pickWorst
						&& (normalizedDistance > bestNormalizedDistance || (normalizedDistance == bestNormalizedDistance && distance > bestDistance)))
						|| (!pickWorst
						&& (normalizedDistance < bestNormalizedDistance || (normalizedDistance == bestNormalizedDistance && distance < bestDistance)));

				if (accept) {
					bestNormalizedDistance = normalizedDistance;
					bestDistance = distance;
					bestI = i;
					bestJ = j;
				}
			}
		}

		/*HashSet<Integer> h = new HashSet<Integer>();
		for (int i = 0; i < ids.length; ++i)
			h.add(ids[i]);
		assert h.size() == ids.length;*/

		if (bestJ >= 0) {
			nextChosenInd = ids[bestJ];
		}
		return ids[bestI];
	}

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