package ac.essex.ooechs.ecj.ecj2java.example.problems;

import ac.essex.ooechs.ecj.ecj2java.JavaWriter;
import ac.essex.ooechs.ecj.ecj2java.example.data.DoubleData;
import ec.EvolutionState;
import ec.Individual;
import ec.gp.GPIndividual;
import ec.gp.GPProblem;
import ec.gp.koza.KozaFitness;
import ec.simple.SimpleProblemForm;
import ec.util.Parameter;

import java.io.File;
import java.io.IOException;

/**
 * Uses GP to solve simple maths problems.
 * Note that you will have to change the paths specified so the Java
 * file can be saved properly.
 * <p/>
 * <p/>
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version,
 * provided that any use properly credits the author.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details at http://www.gnu.org
 * </p>
 *
 * @author Olly Oechsle, University of Essex, Date: 05-Sep-2006
 * @version 1.0
 */
public class MathsProblem extends GPProblem implements SimpleProblemForm {

    // CHANGE THESE SETTINGS ACCORDING TO YOUR PREFERENCES
    public static final String classPackage = "ac.essex.ooechs.ecj.ecj2java.example.individuals";
    public static final String outputDirectory = "/home/ooechs/Ecj2Java/src/ac/essex/ooechs/ecj/ecj2java/example/individuals/";
    // END SETTINGS

    public DoubleData input;

    public double x;
    public double y;

    public Object protoClone() throws CloneNotSupportedException {
        MathsProblem newobj = (MathsProblem) (super.protoClone());
        newobj.input = (DoubleData) (input.protoClone());
        return newobj;
    }

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

        // very important, remember this
        super.setup(state, base);

        // set up the input ( equally important if you don't want
        input = (DoubleData) state.parameters.getInstanceForParameterEq(base.push(P_DATA), null, DoubleData.class);
        input.setup(state, base.push(P_DATA));

    }

    int dotCount = 0;

    public void evaluate(final EvolutionState state, final Individual ind, final int threadnum) {

        // ensure we only evaluate the individual once
        if (!ind.evaluated) {

            float fitness = 0.0f;

            int TP = 0;

            // x and y are public variables so the GP nodes
            // have access to them outside this method.
            for (y = 0; y <= 50; y+=1) {
                for (x = 0; x <= 50; x+=1) {

                    // lets get the problem to work out the
                    // pythagoras equation. The code in this file
                    // may seem a little contrived - we already know
                    // the pythagoras equation! But if you were
                    // to feed your program results from the real world
                    // it may be able to find an equation to describe them
                    // that you didn't already know.
                    double expected = Math.sqrt((x * x) + (y * y));

                    // this is the code to run the individual
                    ((GPIndividual) ind).trees[0].child.eval(state, threadnum, input, stack, ((GPIndividual) ind), this);

                    // and get the individual's result
                    double received = input.x;

                    // add the difference to the expected value. Fitness of 0 is perfect.
                    fitness += Math.abs(expected - received);

                    // work out if it is "close enough" for a TP
                    if (isSimilarTo(expected, received, 0.05)) TP++;

                }
            }

            // convert to KozaFitness, which expects 0 as perfect fitness
            KozaFitness f = ((KozaFitness) ind.fitness);
            if (fitness == 1.0f) fitness = 0.0f;
            f.setStandardizedFitness(state, fitness);

            f.hits = TP;
            ind.evaluated = true;

            dotCount++;
            // only print out every 20th dot (save filling the screen with dots!)
            if (dotCount % 20 == 0) System.out.print(".");

        }
    }

    public boolean isSimilarTo(double number1, double number2, double threshold) {

        double difference = Math.abs(number1 - number2);

        return (difference / number1) < threshold;

    }

    static int counter = 0;

    public void describe(final Individual ind, final EvolutionState state, final int threadnum, final int log, final int verbosity) {

        state.output.println("\n\nBest Individual", verbosity, log);

        KozaFitness f = ((KozaFitness) ind.fitness);

        counter++;

        /**
         * Create a ECJ2Java Writer here, with the following parameters:
         */

        String className = "MathsSolution" + counter;
        String functionSignature = "public double calculate(double x, double y)";
        String comment = "Fitness: " + f.hits;
        JavaWriter writer = new JavaWriter(className, functionSignature, comment, classPackage);

        /**
         * Decide where the Java file is to be saved:
         */
        File saveTo = new File(outputDirectory);

        /**
         * Generate and save the file
         */
        try {
            writer.saveJavaCode((GPIndividual) ind, saveTo);
        } catch (IOException e) {
            e.printStackTrace();
        }

        /**
         * And that's all there is to it!
         */
        System.out.println("TP: " + f.hits);
        System.out.println("Total Error: " + f.standardizedFitness());

    }


}
