package eva2.optimization.operator.nichepso.subswarmcreation;

import eva2.optimization.individuals.AbstractEAIndividual;
import eva2.optimization.individuals.ESIndividualDoubleData;
import eva2.optimization.operator.mutation.MutateESFixedStepSize;
import eva2.optimization.population.Population;
import eva2.optimization.strategies.ParticleSubSwarmOptimization;

/**
 * The standard deviation in the fitness of each main swarm particle over the last 3 iterations is calculated.
 * If this standard deviation falls below a given threshold delta it is assumed that the particle is converging
 * on an optimum and a subswarm is created with that particle and a generated neighbor.
 * This neighbor is generated by mutating the converged particle using the mutation step size mu.
 * delta = 0.0001 and mu = 0.1 could experimentally produce acceptable results
 * but further effort to obtain appropriate values for mu may significantly improve the performance of this strategy.
 */
public class GenerateNeighborSubswarmCreationStrategy extends
        StandardSubswarmCreationStrategy {

    private double mu = 0.1;

    /**
     * @tested creates a subswarm from the given particle and its neighbor in the mainswarm.
     * If the neighbors pbest is better than the particles pbest, a new neighbor is generated.
     */
    @Override
    public void createSubswarm(ParticleSubSwarmOptimization preparedSubswarm, AbstractEAIndividual indy, ParticleSubSwarmOptimization mainSwarm) {
        // get the neighbor in the mainswarm
        AbstractEAIndividual neighbor = mainSwarm.getMemberNeighbor(indy);
        AbstractEAIndividual worst;
        // check if neighbor would "pull the indy away from its niche" because it has a better pbest
        AbstractEAIndividual indypbest = (AbstractEAIndividual) indy.getData("PersonalBestKey");
        AbstractEAIndividual neighpbest = (AbstractEAIndividual) neighbor.getData("PersonalBestKey");

        if (true) {//neighpbest.isDominating(indypbest)) {
            neighbor = generateNeighborFrom(indy, mainSwarm); // neighbor wouldnt help with indies niche
            worst = mainSwarm.getParticleWithWorstPBestButNot(indy); // delete another particle for the gnerated neighbor
        } else {
            worst = neighbor; // use the neighbor particle and delete it from the mainswarm
        }

        Population pop = new Population(2);
        pop.add(indy);
        pop.add(neighbor);
        preparedSubswarm.setPopulation(pop);
        preparedSubswarm.populationSizeHasChanged();

        // remove particles from the main swarm:
        mainSwarm.removeSubIndividual(indy);
        mainSwarm.removeSubIndividual(worst);
        mainSwarm.populationSizeHasChanged();
    }

    private AbstractEAIndividual generateNeighborFrom(AbstractEAIndividual indy, ParticleSubSwarmOptimization mainswarm) {
        // generate a neighbor
        ESIndividualDoubleData neighbor = (ESIndividualDoubleData) indy.clone();
        MutateESFixedStepSize mutator = new MutateESFixedStepSize();
        mutator.setSigma(getMu());
        neighbor.setMutationOperator(mutator);
        neighbor.setMutationProbability(1);
        neighbor.mutate();
        //TODO more stuff: stddev ...  from indy
        //TODO mutate velocity? evaluate?
        neighbor.putData("BestFitness", neighbor.getFitness());
        neighbor.putData("BestPosition", neighbor.getDGenotype());
        return neighbor;
    }

    /**
     * *******************************************************************************************************************
     * getter, setter
     */

    public void setMu(
            double generateNeighborWithMutationStep) {
        this.mu = generateNeighborWithMutationStep;
    }

    public double getMu() {
        return mu;
    }

    public String muTipText() {
        return "mutation step size used to generate a neighbor";
    }


}
