package gp;

import java.util.Arrays;
import java.util.Random;
import java.util.Vector;

class RatedIndividual implements Comparable<RatedIndividual>
{
	public InterfaceIndividual indy;
	public double val;

	public RatedIndividual(InterfaceIndividual individual, double value)
	{
		indy = individual;
		val = value;
	}

	public int compareTo(RatedIndividual y)
	{
		double w = y.val;
		if (val<w) return -1; 
		else {
			if (val>w) return 1; 
			else return 0;
		}
	}
}

enum SelectionEnum {
	best, nonlinRank;
}

/**
 * Selection class.
 */
public class Selection {
	// Selection enthaelt Selektionsmethoden fuer den Algorithmus EvolutionaryAlgorithm.java.
	// Die Auswahl wird ueber das GUI getroffen und als Parameter an den Konstruktor uebergeben.
	static double worstFit=Math.pow(10,100);
	
	double selParam;
	double sigmaShare;
	EaGuiCallback gcb;
	int tAct;
	SelectionEnum method;
	boolean doSharing;

	public Selection(SelectionEnum meth, double selP, Random random, EaGuiCallback g, boolean doShare, double sigShare)
	{
		// Initialisiert ein Selection-Objekt mit Hilfe der von der GUI Ã¼bergebenen Parameter.
		init(meth, selP, random, g, doShare, sigShare);
	}
	
	public Selection(SelectionEnum meth, double selP, Random random, EaGuiCallback g)
	{
		// Initialisiert ein Selection-Objekt mit Hilfe der von der GUI Ã¼bergebenen Parameter.
		init(meth, selP, random, g, false, 1);
	}
	
	protected void init(SelectionEnum meth, double selP, Random random, EaGuiCallback g, boolean doShare, double sigShare) {
		selParam = selP;
		gcb = g;
		tAct = 0;
		method = meth;
		doSharing = false;
		sigmaShare = 10.; 
	}
	
	public Vector<InterfaceIndividual> generateMatingPool(InterfaceIndividual[] pop)
	{
		// Gibt einen Vektor aus Individual zurueck, abhaengig von der gewaehlten Methode.
		switch (method)
		{
		case best: return best(pop);
		case nonlinRank: return nonlinRanking(pop);
		}
		System.err.println("Selection method not implemented!");
		return null;
	}


	public InterfaceIndividual findBest(InterfaceIndividual[] pop)
	{
		// Gibt das beste Individuum aus pop zurueck.
		RatedIndividual a[] = scanPopulation(pop);
		return a[0].indy;
	}
	
	public void printBest(InterfaceIndividual[] pop, int n)
	{
		// Gibt das beste Individuum aus pop zurueck.

		RatedIndividual a[] = scanPopulation(pop);
		for (int i=0; i<n; i++) System.out.println(a[i].indy);
	}

	Vector<InterfaceIndividual> stochasticUniversalSampling(RatedIndividual[] a) {
		// Gibt einen Vektor aus Individual zurueck, erzeugt nach der Methode Stochastic Universal Sampling.
		// Grundlegend ist, dass die Summe der Bewertungen a[i].v gleich der Populationsgroesse n ist.
		// Dann werden n Individuen ausgewaehlt. z wird zufaellig initialisiert und spiegelt die Position
		// des ersten Zeigers wieder. Alle anderen Zeiger folgen im Abstand 1. Fuer jeden Abschnitt a[i].v
		// des Gluecksrads wird geprueft, wie viele Zeiger darauf zeigen.
		double s=0, z=RandomNumberGenerator.randomDouble();
		int i, n=a.length;
		Vector<InterfaceIndividual> matingPool = new Vector<InterfaceIndividual>();

		for (i=0; i<n; i++) {
			s += a[i].val;
			while (s>z) {matingPool.add(a[i].indy); z++;}
		}
		return matingPool;
	}

	RatedIndividual[] scanPopulation(InterfaceIndividual[] pop)
	{
		// Erzeugt ein sortiertes RatedIndividual-Array aus einem Individual-Array,
		// indem fuer jedes Individual seine Funktion eval() ausgewertet wird.
		// Ausserdem werden bester, mittlerer und schlechtester Fitnesswert in der GUI dargestellt.

		int i, n=pop.length;
		double fit, sum=0;
		double worst=Double.NEGATIVE_INFINITY;

		RatedIndividual a[] = new RatedIndividual[n];
		for (i=0; i<n; i++) 
		{ 
			fit=pop[i].evaluate();
			if (Double.isInfinite(fit)) fit = worstFit;
			sum+=fit; 
			a[i] = new RatedIndividual(pop[i], fit);
			if (fit > worst) worst = fit;
		}

		Arrays.sort(a);
		if (gcb!=null) {
		  if (tAct == 0) System.out.println("t  Best  Mean  Worst");
		  gcb.plotFit(tAct++,a[0].val,sum/n,a[n-1].val, a[0].indy);
		}
		else System.out.println(tAct + "  " + a[0].val + "  " + sum/n + "  " + a[n-1].val);
		return a;
	}
	

	Vector<InterfaceIndividual> dummy(InterfaceIndividual[] pop)
	{
		int i, n=pop.length;
		RatedIndividual a[] = scanPopulation(pop);
		Vector<InterfaceIndividual> matingPool = new Vector<InterfaceIndividual>();

		for (i=0; i<n; i++) matingPool.add(a[i].indy);
		return matingPool;
	}

	Vector<InterfaceIndividual> best(InterfaceIndividual[] pop)
	{
		int i, n=pop.length;
		RatedIndividual a[] = scanPopulation(pop);
		Vector<InterfaceIndividual> matingPool = new Vector<InterfaceIndividual>();

		for (i=0; i<n; i++) matingPool.add(a[0].indy);
		return matingPool;
	}
	
	Vector<InterfaceIndividual> linRanking(InterfaceIndividual[] pop) {
		// Gibt einen Vektor aus Individual zurueck gemaess linearem Ranking.
		// Der Parameter selemParam aus der GUI entspricht eta^+ im Abschnitt 5.6.3 des Skripts.
		// (eta^+)-(eta^-) ist somit 2*(selParam-1).
		// Die -1 im Skript fuer den Index i entfaellt aufgrund des Startwerts 0.
		// Der Faktor 1/lambda im Skript entfaellt aufgrund der Reskalierung, bei der
		// die Bewertungen mit der Populationsgroesse multipliziert werden.
		int i, n=pop.length;
		RatedIndividual a[] = scanPopulation(pop);
		double Ediff = 2*(selParam-1)/(n-1);

		for (i=0; i<n; i++) a[i].val = selParam-Ediff*i;
		return stochasticUniversalSampling(a);
	}

	Vector<InterfaceIndividual> nonlinRanking2(InterfaceIndividual[] pop) {
		// Gibt einen Vektor aus Individual zurueck gemaess nichtlinearem Ranking, Variante, die
		// keine Reskalierung benötigt.
		// Die Bewertungen der Individuen wird dazu fuer stochasticUniversalSampling() skaliert.
		int i, n=pop.length;
		RatedIndividual a[] = scanPopulation(pop);
		double s = n/Math.pow(n,selParam);

		for (i=0; i<n; i++) {
			a[i].val = (Math.pow(n-i,selParam)-Math.pow(n-i-1,selParam))*s;
		}
		return stochasticUniversalSampling(a);
	}
	
	Vector<InterfaceIndividual> nonlinRanking(InterfaceIndividual[] pop) {
		int i, n=pop.length;
		RatedIndividual a[] = scanPopulation(pop);		
		double sum = 0.;
		
		// Compute expected number of descendants for each individual based on its rank
		for (i=0; i<n; i++) {
			a[i].val = selParam * Math.pow((1. - selParam), ((double) i)) * n;
			sum += a[i].val;
		}
		
		// Rescaling is necessary
		double factor = (double) n / sum;
		sum = 0.;
		for (i=0; i<n; i++) {
			a[i].val *= factor;
			sum += a[i].val;
		}
		
		// If the total sum not equals n, then add the difference to the first element!
		if(sum < n) {	
			double diff = (double) n - sum;
			a[0].val += diff;
			sum += diff;
		}
		return stochasticUniversalSampling(a);
	}
}
