package bsearch.algorithms;

import java.util.Arrays;
import java.util.HashMap;

import org.nlogo.util.MersenneTwisterFast;

import bsearch.app.BehaviorSearchException;
import bsearch.app.SearchProtocol;
import bsearch.evaluation.SearchManager;
import bsearch.nlogolink.NetLogoLinkException;
import bsearch.representations.Chromosome;
import bsearch.representations.ChromosomeFactory;
import bsearch.space.SearchSpace;

public strictfp class StandardGA extends AbstractSearchMethod{
	
	private double mutationRate = 0.01;
	private double crossoverRate = 0.7;
	private int populationSize = 50;
	private int tournamentSize = 3;
	private String populationModel = "generational";

	public StandardGA()
	{
	}
	
	public String getName()
	{
		return "StandardGA";
	}
	public String getDescription()
	{
		return "A standard Genetic Algorithm.";
	}	

	public void setSearchParams( HashMap<String , String> searchMethodParams ) throws SearchParameterException
	{
		mutationRate = validDoubleParam(searchMethodParams, "mutation-rate", 0.0, 1.0);
		crossoverRate = validDoubleParam(searchMethodParams, "crossover-rate", 0.0, 1.0);
		populationSize = validIntParam(searchMethodParams, "population-size", 2, 1000);
		tournamentSize = validIntParam(searchMethodParams, "tournament-size", 2, 10);
		populationModel = validChoiceParam(searchMethodParams, "population-model", Arrays.asList("generational","steady-state-replace-random","steady-state-replace-worst"));		
	}
	public HashMap<String , String> getSearchParams()
	{
		HashMap<String,String> params = new HashMap<String,String>();
		params.put("mutation-rate", Double.toString( mutationRate ));
		params.put("crossover-rate", Double.toString( crossoverRate ));
		params.put("population-size", Integer.toString( populationSize ));		
		params.put("tournament-size", Integer.toString( tournamentSize ));
		params.put("population-model", populationModel);
		
		return params;
	}
	public HashMap<String , String> getSearchParamsHelp()
	{
		HashMap<String,String> params = new HashMap<String,String>();
		params.put("mutation-rate", "likelihood of mutation occurring in each child");
		params.put("crossover-rate", "probability of using two parents to create a child (otherwise child is created asexually)");
		params.put("population-size", "the number of individuals in each generation");
		params.put("tournament-size", "for tournament selection, this is the number of individuals that compete to choose an individual that gets to reproduce."  );		
		params.put("population-model", "'generational', 'steady-state-replace-random', or 'steady-state-replace-worst' -- generational means the whole population is replaced at once, whereas steady-state means that just a single individual is replaced by reproduction each iteration - either a randomly-chosen individual, or the current worst."  );		
		return params;
	}


	public void search( SearchSpace space , ChromosomeFactory cFactory, SearchProtocol protocol ,
			 SearchManager manager, MersenneTwisterFast rng ) 
		throws BehaviorSearchException, NetLogoLinkException, InterruptedException
	{
		Chromosome[] population = new Chromosome[populationSize];
		
		for (int i = 0; i < populationSize; i++)
		{
			population[i] = cFactory.createChromosome(space, rng);
		}
		
		double[] fitness = manager.computeFitnessBatch(population, protocol.fitnessSamplingReplications, rng);
		
		if (populationModel.equals("generational"))
		{
			Chromosome[] newpopulation = new Chromosome[populationSize];
			while ( !manager.searchFinished() )
			{
			    int crossoverPairs = (int) (crossoverRate * populationSize / 2) ;
			    int newPopIndex = 0;
			    for (int i = 0; i < crossoverPairs; i++)
			    {
			    	Chromosome p1 = manager.tournamentSelect(population, fitness, tournamentSize, rng);
			    	Chromosome p2 = manager.tournamentSelect(population, fitness, tournamentSize, rng);
			    	Chromosome[] children = p1.crossoverWith( p2 , rng ) ;
			    	newpopulation[newPopIndex++] = children[0];
			    	newpopulation[newPopIndex++] = children[1];                	
			    }
			    while (newPopIndex < populationSize)
			    {
			    	newpopulation[newPopIndex++] = manager.tournamentSelect(population, fitness, tournamentSize, rng);
			    }
			    for (int i = 0; i < populationSize; i++)
			    {
			    	newpopulation[i] = newpopulation[i].mutate( mutationRate , rng );
			    }
			    // swap in new population
			    Chromosome[] temp = population;
			    population = newpopulation;
				newpopulation = temp;
				
				fitness = manager.computeFitnessBatch(population, protocol.fitnessSamplingReplications, rng);			
			}
		}
		else if (populationModel.startsWith("steady-state"))
		{
			while ( !manager.searchFinished() )
			{
				Chromosome child = manager.tournamentSelect(population, fitness, tournamentSize, rng);;
				if (rng.nextDouble() < crossoverRate)
				{
			    	Chromosome p2 = manager.tournamentSelect(population, fitness, tournamentSize, rng);
			    	child = child.crossoverWith( p2 , rng )[0] ;
			    }
				child = child.mutate(mutationRate, rng);
			    double childFitness = manager.computeFitnessSingle(child, protocol.fitnessSamplingReplications, rng);
				
			    int replaceIndex;
			    if (populationModel.equals("steady-state-replace-random"))
			    {
			    	replaceIndex = rng.nextInt(populationSize);
			    }
			    else
			    {
			    	double worstFitness = fitness[0];
			    	replaceIndex = 0;
			    	for (int i = 0; i < populationSize; i++)
			    	{
			    		if (manager.fitnessStrictlyBetter(worstFitness, fitness[i]))
			    		{
			    			replaceIndex = i;
			    			worstFitness = fitness[i];
			    		}
			    	}
			    }
			    
			    population[replaceIndex] = child;
				fitness[replaceIndex] = childFitness;
			}
		}
	}

}
