package ec.gp.canonic.select;

import ec.EvolutionState;
import ec.Individual;
import ec.gp.koza.KozaFitness;
import ec.gp.semantic.statistics.ExtendedStatistics;
import ec.select.TournamentSelection;
import ec.util.MersenneTwisterFast;
import ec.util.Parameter;
import experimentdatabase.DataSet;

import java.util.LinkedList;
import java.util.Queue;

/**
 * Tournament Selection that for every mates sets of selected parents guarantees that no parent is selected twice.
 *
 * @author Tomasz Pawlak
 */
public class TS extends TournamentSelection {

	private static final String MATES = "mates";

	protected EvolutionState state;

	private int mates = 1;
	private Queue<Integer> nextIndividuals = new LinkedList<Integer>();

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

		this.state = state;
		this.mates = state.parameters.getInt(base.push(MATES), defaultBase().push(MATES));


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

	@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.nextIndividuals.isEmpty()) {
			return this.nextIndividuals.poll();
		}

		MersenneTwisterFast random = state.random[thread];

		int size = this.getTournamentSizeToUse(random);
		Individual[] individuals = state.population.subpops[subpopulation].individuals;
		// mask contains ids of individuals that We can pick from
		int max = individuals.length;
		int[] mask = new int[individuals.length];
		for (int i = 0; i < mask.length; ++i) {
			mask[i] = i;
		}

		int r;
		float bestFitness = Float.POSITIVE_INFINITY;
		int bestInMask = -1; // index in mask of the best individual
		int selected;
		for (int mate = 0; mate < this.mates; ++mate) {
			// conduct separate tournament selection for each mate
			for (int s = 0; s < size; ++s) {
				r = random.nextInt(max--);
				selected = mask[r];
				mask[r] = mask[max];
				mask[max] = selected;

				KozaFitness fitness = (KozaFitness) individuals[selected].fitness;
				if (fitness.standardizedFitness() < bestFitness) {
					bestFitness = fitness.standardizedFitness();
					bestInMask = max;
				}
			}

			// we mark for the next iteration, where mask ends
			// and we move selected individual behind this mark
			max = individuals.length - mate - 1;
			selected = mask[bestInMask];
			mask[bestInMask] = mask[max];
			mask[max] = selected;

			// reset best
			bestFitness = Float.POSITIVE_INFINITY;
			bestInMask = -1;

			assert !this.nextIndividuals.contains(selected);

			this.nextIndividuals.add(selected);
		}

		return this.nextIndividuals.poll();
	}


	@Override
	public void finishProducing(final EvolutionState s,
								final int subpopulation,
								final int thread) {
		this.nextIndividuals.clear();
	}
}
