/*
 * Copyright 2016 by Romaric Pighetti, CNRS, I3S
 * Licensed under the Academic Free License version 3.0
 */
package fr.unice.i3s.keia.irWithIga.problem;

import ec.EvolutionState;
import ec.Individual;
import ec.Problem;
import ec.Subpopulation;
import ec.multiobjective.MultiObjectiveFitness;
import ec.simple.SimpleProblemForm;
import ec.util.ParamClassLoadException;
import ec.util.Parameter;
import ec.vector.DoubleVectorIndividual;
import fr.unice.i3s.keia.irWithIga.objective.Objective;

//No MM but stated version of NSGA2 PB
public class NSGA2_Problem extends Problem implements SimpleProblemForm {
    private static final long   serialVersionUID = 1L;

    private static final String P_OBJECTIVE      = "objective";
    private static final String P_NUM_OBJECTIVE  = "multi.fitness.num-objectives";

    private String              modality;

    private Objective[]         objectives       = new Objective[0];

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

        int numObjectives = state.parameters.getInt(new Parameter(
                P_NUM_OBJECTIVE), null);
        objectives = new Objective[numObjectives - 1];
        for (int i = 0; i < numObjectives - 1; i++) {
            Parameter param = base;
            param = param.push(P_OBJECTIVE);
            param = param.push(i + "");
            try {
                @SuppressWarnings("rawtypes")
                Objective obj = (Objective) ((Class) state.parameters
                        .getClassForParameter(param, null, Objective.class))
                        .newInstance();
                obj.setup(state, param);
                objectives[i] = obj;
            } catch (InstantiationException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ParamClassLoadException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        modality = state.parameters.getStringWithDefault(base.push("modality"),
                null, "none");

    }

    // A Parameterless-Niching-Assisted Bi-objective Approach to Multimodal
    // Optimization
    // Page 2 formula 5
    private double getDistAll(final EvolutionState state, final Individual ind,
            final int subpopulation) {
        Subpopulation mypop = state.population.subpops[subpopulation];
        int t1;
        int popsize = mypop.individuals.length;
        double retval = 0;
        for (t1 = 0; t1 < popsize; t1++) {

            retval += distance_Euclide_Individuals(ind,
                    mypop.individuals[t1]);
        }

        return 1.0 / retval;
    }

    /*
     * (non-Javadoc)
     *
     * @see ec.Problem#prepareToEvaluate(ec.EvolutionState, int)
     */
    @Override
    public void prepareToEvaluate(final EvolutionState state,
            final int threadnum) {

        super.prepareToEvaluate(state, threadnum);

        for (int i = 0; i < state.population.subpops.length; i++) {

            // we will set all indvs evaluation state to not_evaluated
            Subpopulation mypop = state.population.subpops[i];
            int t;

            for (t = 0; t < mypop.individuals.length; t++) {
                mypop.individuals[t].evaluated = false;

                // sets a circular reference ind<->fit
                //((NSGA2MM_Fitness) mypop.individuals[t].fitness).individual = mypop.individuals[t];
            }
        }
    }

    public void evaluate(final EvolutionState state, final Individual ind,
            final int subpopulation, final int threadnum) {
        if (!(ind instanceof DoubleVectorIndividual))
            state.output
                    .fatal("The individuals for this problem should be DoubleVectorIndividuals.");

        // DoubleVectorIndividual temp = (DoubleVectorIndividual)ind;
        double[] objectives = ((MultiObjectiveFitness) ind.fitness).getObjectives();

        for (int i = 0; i < objectives.length - 1; i++) {
            objectives[i] = this.objectives[i].evaluate(state, ind,
                    subpopulation, threadnum);
        }

        if (modality.compareTo("mm") != 0) {
            objectives[objectives.length - 1] = 0;
        } else {
            objectives[objectives.length - 1] = getDistAll(state, ind,
                    subpopulation);
        }
        ((MultiObjectiveFitness) ind.fitness).setObjectives(state, objectives);
        ind.evaluated = true;

    }

    /**
     * Donne la distance entre deux individus en utilisant une fonction de
     * distance d'euclide
     *
     * @param ind1
     * @param ind2
     * @return
     */
    private static double distance_Euclide_Individuals(Individual ind1,
                                                       Individual ind2) {
        int tg;
        DoubleVectorIndividual iv1 = (DoubleVectorIndividual) ind1;
        DoubleVectorIndividual iv2 = (DoubleVectorIndividual) ind2;
        double[] g1 = iv1.genome;
        double[] g2 = iv2.genome;
        double tmp;
        int numDecisionVars = g1.length;
        double sum = 0;

        for (tg = 0; tg < numDecisionVars; tg++) {
            tmp = g1[tg] - g2[tg];
            sum += tmp * tmp;
        }
        return Math.sqrt(sum);
    }
}
