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;

/**
 * Competent Tournament Selection with additional statistics.
 */
public class CTSStat extends CTS {

	private int totalDenormals = 0;
	private int totalNaNs = 0;
	private int totalInf = 0;
	private long totalComputationTime = 0;

	@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;

		long startCompTime = System.nanoTime();


		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;
			this.totalDenormals += (-Double.MIN_VALUE < candidateDistance && candidateDistance < Double.MIN_VALUE) ? 1 : 0;
			this.totalNaNs += Double.isNaN(candidateDistance) ? 1 : 0;
			this.totalInf += Double.isInfinite(candidateDistance) ? 1 : 0;

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

		this.totalComputationTime += System.nanoTime() - startCompTime;

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

	@Override
	public void finishProducing(EvolutionState s, int subpopulation, int thread) {
		super.finishProducing(s, subpopulation, thread);
		System.out.println(String.format("CTS CompTime=%5dns Denormals=%3d NaNs=%3d Infs=%3d TotalNotNumeric=%3d",
				this.totalComputationTime,
				this.totalDenormals,
				this.totalNaNs,
				this.totalInf,
				this.totalDenormals + this.totalNaNs + this.totalInf));

		this.totalDenormals = 0;
		this.totalComputationTime = 0;
		this.totalNaNs = 0;
		this.totalInf = 0;
	}
}
