/*
 * Population.java
 *
 * Created on November 2, 2005, 5:26 PM
 *
 * To change this template, choose Tools | Options and locate the template under
 * the Source Creation and Management node. Right-click the template and choose
 * Open. You can then make changes to the template in the Source Editor.
 */

package cz.pcland.ga;

/**
 *
 * @author petr
 */

import java.util.*;
import javax.swing.*;
import cz.pcland.ga.selection.*;

public class Population {
    
    private BaseIndividual[] population;
    
    private double sumEvals;
    private double lowestEval;
    private double avgEval;
    private double bestEval;
    private BaseIndividual bestString;
    
    private double sumFitnesses;
    private double bestFitness;
    private double lowestFitness;
    private double avgFitness;
    
    private int generationsCount;
    
    private boolean done = false;
    double distribution_evals[];
    double distribution_fitnesses[];
    
    public BaseIndividual[] getPopulation(){
        return this.population;
    }
    
    public boolean isEnd() {
        return this.done;
    }
    
    
    public int getGenerationsCount() {return this.generationsCount;}
    
    public Population() {
        this.generationsCount = 0;
    }
    
    
    /*create a new random population*/
    public GenerationInfo initPopulation(int pt) {
        population = new BaseIndividual[GAConfiguration.popSize];
        
        for(int i=0;i<GAConfiguration.popSize;i++) {
            if(pt == PopulationType.BINARY)  population[i] = new BinaryIndividual(GAConfiguration.strLen);
            if(pt == PopulationType.INTEGER) population[i] = new TSPIntegerIndividual(GAConfiguration.strLen);
        }
        
        GAConfiguration.firnessObj.onStart(population);
        GAConfiguration.selection.onStart(this.population);
        
        GAConfiguration.firnessObj.init(population);
        
        evaluate();
        
       

        
        this.generationsCount = 1;
        if(bestString == null) System.out.println("bestString == null");
        return new GenerationInfo(this.bestEval, this.bestString, this.sumEvals, this.avgEval, this.lowestEval, this.generationsCount, distribution_evals, this.avgFitness, this.sumFitnesses, this.lowestFitness, this.bestFitness, this.distribution_fitnesses); //todo:null pointer exception pro prvnim stisku
    }
    
    
    
    public GenerationInfo nextGeneration() throws SelectionException {
        GAConfiguration.selection.init(population, this.sumFitnesses);
        GAConfiguration.firnessObj.init(population);
        
        /*initiate space for the next generation*/
        BaseIndividual nextGen[] = new BaseIndividual[GAConfiguration.popSize];
        
        /*sends elite strings to next generation*/
        BaseIndividual tmp;
        
        int i = GAConfiguration.popSize-1;
        while(i>=GAConfiguration.popSize-GAConfiguration.elite){
            int best = 0;
            
            for(int j=1;j<=i;j++){
                if(population[best].getFitness() < population[j].getFitness()) {
                    best = j;
                }
                
            }
            
            tmp = population[best].copy();
            
            population[best] = population[i].copy();
            population[i] = tmp;
            
            nextGen[i] = tmp;  
            i--;
        }
        
        int j=0;
        
        while(j < GAConfiguration.popSize-GAConfiguration.elite) {
            
            BaseIndividual parent1 = null;
            BaseIndividual parent2 = null;
            
            parent1 = GAConfiguration.selection.select(population, GAConfiguration.popSize, this.sumFitnesses);
            parent2 = GAConfiguration.selection.select(population, GAConfiguration.popSize, this.sumFitnesses);
            
            
            if(Math.random() < GAConfiguration.probOfCrossover) {
                
                BaseIndividual[] res = GAConfiguration.crossoverObject.crossOver(parent1, parent2, GAConfiguration.strLen);
                int k;
                for(k=0;k<res.length;k++) {
                    if(j < GAConfiguration.popSize-GAConfiguration.elite) {
                        nextGen[j] = res[k];
                        nextGen[j++].mutation(GAConfiguration.probOfMutation);
                    }
                }
                
            } else {
                nextGen[j] = parent1.copy();
                nextGen[j++].mutation(GAConfiguration.probOfMutation);
                
                if(j < GAConfiguration.popSize-GAConfiguration.elite) {
                    nextGen[j] = parent2.copy();
                    nextGen[j++].mutation(GAConfiguration.probOfMutation);
                }
            }
            
            
            
        }
        
        
        
        population = nextGen;
        this.generationsCount++;
        evaluate();
        
        return new GenerationInfo( this.bestEval, this.bestString, this.sumEvals, this.avgEval, this.lowestEval, this.generationsCount, distribution_evals, this.avgFitness, this.sumFitnesses, this.lowestFitness, this.bestFitness, this.distribution_fitnesses);
    }
    
    /**
     * Evaluates new population.
     */
    public void evaluate(){
        
        if(GAConfiguration.popSize != 0) {
            double max = Double.MIN_VALUE;
            double min = Double.MAX_VALUE;
            
            for(int ii=0;ii<GAConfiguration.popSize;ii++) {
                double eval = GAConfiguration.firnessObj.getEvaluation(population[ii]);
                population[ii].setFitness(eval);
                population[ii].setEvaluation(eval);
                
                if(population[ii].getFitness() < min) min = population[ii].getFitness();
                if(population[ii].getFitness() > max) max = population[ii].getFitness();
            }
            
            GAConfiguration.firnessObj.postEvaluate(population, min, max);
            
            
            if(GAConfiguration.setLowestFitnessToZero){
                double minimum = Double.MAX_VALUE;
                for(int ii=0;ii<population.length;++ii){
                    if(population[ii].getFitness() < minimum) minimum = population[ii].getFitness();
                }
                
                for(int ii=0;ii<population.length;++ii){
                    population[ii].setFitness( population[ii].getFitness()-minimum);
                }
            }
            
            this.bestString = this.population[0];
            
            this.bestEval = this.population[0].getEvaluation();
            this.sumEvals = population[0].getEvaluation();
            this.lowestEval = this.population[0].getEvaluation();
            
            this.bestFitness = this.population[0].getFitness();
            this.sumFitnesses = population[0].getFitness();
            this.lowestFitness = population[0].getFitness();
            
            for(int i=1;i<GAConfiguration.popSize;i++) {       
                this.sumEvals += this.population[i].getEvaluation();
                this.sumFitnesses += this.population[i].getFitness();
                
                if(bestEval < this.population[i].getEvaluation()) {
                    bestEval = this.population[i].getEvaluation();
                }
                
                if(bestFitness < this.population[i].getFitness()) {
                    bestFitness = this.population[i].getFitness();
                    this.bestString = this.population[i];
                }
                
                if(lowestFitness > this.population[i].getFitness()) {
                    lowestFitness = this.population[i].getFitness();
                }
                
                if(this.lowestEval > this.population[i].getEvaluation()){
                    this.lowestEval = this.population[i].getEvaluation();
                }
                
            }
            
            
            this.avgEval = this.sumEvals/GAConfiguration.popSize;
            this.avgFitness = this.sumFitnesses/GAConfiguration.popSize;
            this.done = GAConfiguration.firnessObj.isEnd(this.bestString);
            
            
            distribution_evals = new double[GAConfiguration.distributionDensity];
            distribution_fitnesses = new double[GAConfiguration.distributionDensity];
            
            for(int i=0;i<GAConfiguration.popSize;i++) {                
                long index_evals = Math.round(  ( (double)Math.abs((double)this.population[i].getEvaluation() - (double)lowestEval) / (double)Math.abs((double)bestEval-(double)lowestEval)   )*(double)(GAConfiguration.distributionDensity - 1));
                long index_fitnesses = Math.round(  ( (double)Math.abs((double)this.population[i].getFitness() - (double)lowestFitness) / (double)Math.abs((double)bestFitness-(double)lowestFitness)   )*(double)(GAConfiguration.distributionDensity - 1));
                
                distribution_evals[(int)index_evals]+=(1.0d/(double)GAConfiguration.popSize);
                distribution_fitnesses[(int)index_fitnesses]+=(1.0d/(double)GAConfiguration.popSize);
            }
        }
    }
}
