package ec.app.semanticGP;

import ec.EvolutionState;
import ec.Individual;
import ec.gp.GPIndividual;
import ec.gp.koza.KozaFitness;
import ec.gp.semantic.ISemantics;
import ec.gp.semantic.evaluation.EvaluationMode;
import ec.gp.semantic.func.SimpleNodeBase;
import library.semantics.TestCase;

import java.util.List;

/**
 * Uses SMAPE as fitness function in test set mode (training set mode uses standard L2 metric like the base class)
 */
public class SMAPERegression extends Regression {
	@Override
	public void evaluate(EvolutionState state, Individual individual, int subpopulation, int threadnum) {
		if (this.getEvaluationMode() == EvaluationMode.TrainingSet) {
			super.evaluate(state, individual, subpopulation, threadnum);
			return;
		}

		if (individual.evaluated)
			return;

		final GPIndividual ind = (GPIndividual) individual;
		double fitness = 0.0;
		double error;
		int hits = 0;

		ISemantics semantics = ((SimpleNodeBase<?>) ind.trees[0].child).getSemantics();

		List<TestCase<Double>> fitnessCases = this.getFitnessCases();
		for (int i = 0; i < fitnessCases.size(); ++i) {
			double x = (Double) fitnessCases.get(i).getValue();
			double forecast = (Double) semantics.getValue(i);

			assert Double.NEGATIVE_INFINITY < -1E6;

			if (!(Math.abs(x - forecast) < 1E6)) {
				error = 1E6;
			} else {
				error = Math.abs(x - forecast) / ((x + forecast) / 2.0);
			}


			if (!(error < BIG_NUMBER)) // *NOT* (input.x >= BIG_NUMBER)
				error = BIG_NUMBER;
			else if (error < PROBABLY_ZERO)
				error = 0.0;

			if (error <= HIT_LEVEL)
				hits++;

			fitness += error;
		}

		double adjustedFitness = fitness / fitnessCases.size() * 100.0;

		//fitness /= fitnessCases.size();
		//fitness *= 100.0;

		final KozaFitness f = (KozaFitness) ind.fitness;
		f.setStandardizedFitness(state, (float) adjustedFitness);
		f.hits = hits;

		ind.evaluated = true;
	}
}
