package ec.app.semanticGP;

import ec.EvolutionState;
import ec.Individual;
import ec.app.regression.testcases.TestCaseFactory;
import ec.gp.GPIndividual;
import ec.gp.GPProblem;
import ec.gp.koza.KozaFitness;
import ec.gp.semantic.ByteSemantics;
import ec.gp.semantic.DoubleSemantics;
import ec.gp.semantic.ISemanticProblem;
import ec.gp.semantic.ISemantics;
import ec.gp.semantic.evaluation.EvaluationMode;
import ec.gp.semantic.evaluation.IChangeableEvaluationMode;
import ec.gp.semantic.func.SimpleNodeBase;
import ec.simple.SimpleProblemForm;
import ec.util.Parameter;
import library.INamedElement;
import library.semantics.TestCase;

import java.util.List;

/**
 * Classification problem
 *
 * @author Tomasz Pawlak
 */
public class Classification extends GPProblem implements SimpleProblemForm, ISemanticProblem<Byte>, IChangeableEvaluationMode, INamedElement {
	protected EvolutionState state;

	private EvaluationMode evaluationMode = EvaluationMode.TrainingSet;
	protected List<TestCase<Byte>> trainingCases;
	protected List<TestCase<Byte>> testCases;
	protected String name;

	protected byte[] targetSemantics;
	protected ISemantics targetSemanticsObject;


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

		try {
			this.state = state;

			TestCaseFactory factory = TestCaseFactory.getFactory(state);
			this.trainingCases = factory.generateTraining(state);
			this.testCases = factory.generateTest(state);
			this.name = factory.getClass().getSimpleName();
			if (this.name.endsWith("Factory")) {
				this.name = this.name.substring(0, this.name.length() - 7);
			}

			this.targetSemantics = new byte[this.trainingCases.size()];
			for (int i = 0; i < this.targetSemantics.length; ++i) {
				this.targetSemantics[i] = this.trainingCases.get(i).getValue();
			}
			this.targetSemanticsObject = new ByteSemantics(this.targetSemantics);

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public List<TestCase<Byte>> getFitnessCases() {
		if (this.evaluationMode == EvaluationMode.TrainingSet)
			return this.trainingCases;
		return this.testCases;
	}

	@Override
	public ISemantics getTargetSemantics() {
		return this.targetSemanticsObject;
	}

	@Override
	public Classification clone() {
		Classification cloned = (Classification) super.clone();
		cloned.data = new RegressionData();
		return cloned;
	}

	@Override
	public void evaluate(EvolutionState state, Individual individual, int subpopulation, int threadnum) {
		if (individual.evaluated)
			return;

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

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

		List<TestCase<Byte>> fitnessCases = this.getFitnessCases();
		for (int i = 0; i < fitnessCases.size(); ++i) {

			// no arithmetic operations in programs, double values should be equal to values in constants 0, 1, 2, 3,...

			if (fitnessCases.get(i).getValue() != (byte)semantics.getValue(i)) {
				++fitness;
			} else {
				++hits;
			}
		}

		final KozaFitness f = (KozaFitness) ind.fitness;
		f.setStandardizedFitness(state, (float) fitness / (float) fitnessCases.size());
		f.hits = hits;

		ind.evaluated = true;
	}

	@Override
	public EvaluationMode getEvaluationMode() {
		return this.evaluationMode;
	}

	@Override
	public void setEvaluationMode(EvaluationMode mode) {
		this.evaluationMode = mode;
	}

	@Override
	public String getName() {
		return this.name;
	}

}
