package ec.gp.semantic.initialization;

import Jama.Matrix;
import ec.EvolutionState;
import ec.gp.*;
import ec.gp.semantic.ISemanticProblem;
import ec.gp.semantic.ISemantics;
import ec.gp.semantic.func.SimpleNodeBase;
import ec.gp.semantic.geometry.IBasisProcessor;
import ec.gp.semantic.statistics.ExtendedStatistics;
import ec.gp.semantic.statistics.Stopwatch;
import ec.util.Parameter;
import ec.util.ParameterDatabase;
import experimentdatabase.DataSet;

import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;

/**
 * Linear Combiner
 *
 * @author Tomasz Pawlak
 */
public class LC extends GPNodeBuilder {

	private final static Parameter DEFAULT_BASE = new Parameter("LC");
	private final static String BASE_BUILDER = "baseBuilder";
	private final static String BASIS_PROCESSOR = "basisProcessor";

	private GPNodeBuilder baseBuilder;
	private IBasisProcessor basisProcessor;
	private GPNode bestTree = null;
	protected EvolutionState state;

	@Override
	public Parameter defaultBase() {
		return DEFAULT_BASE;
	}

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

		ParameterDatabase db = state.parameters;

		this.state = state;
		this.baseBuilder = (GPNodeBuilder) db.getInstanceForParameter(base.push(BASE_BUILDER), defaultBase().push(BASE_BUILDER), GPNodeBuilder.class);
		this.baseBuilder.setup(state, base.push(BASE_BUILDER));

		this.basisProcessor = (IBasisProcessor)db.getInstanceForParameter(base.push(BASIS_PROCESSOR), defaultBase().push(BASIS_PROCESSOR), IBasisProcessor.class);
		this.basisProcessor.setup(state, base.push(BASIS_PROCESSOR));
	}

	@Override
	public GPNode newRootedTree(EvolutionState state, GPType type, int thread, GPNodeParent parent, GPFunctionSet set, int argposition, int requestedSize) {

		Stopwatch basisBuildTime = new Stopwatch();
		Stopwatch linearCombinationTime = new Stopwatch();
		int basisBuildAttempts = 0;

		if (this.bestTree != null) {
			return this.bestTree;
		}

		basisBuildTime.start();

		ISemanticProblem<?> problem = (ISemanticProblem) state.evaluator.p_problem;
		ISemantics target = problem.getTargetSemantics();

		List<SimpleNodeBase<?>> programs = new ArrayList<SimpleNodeBase<?>>();
		do {
			state.output.message(String.format("Attempting to build basis %d time...", ++basisBuildAttempts));

			do {
				SimpleNodeBase<?> candidate = (SimpleNodeBase<?>) this.baseBuilder.newRootedTree(state, type, thread, parent, set, argposition, requestedSize);
				ISemantics candidateSemantics = candidate.getSemantics();
				if (candidateSemantics.isWellDefined() && this.basisProcessor.isInFeasibleRange(candidateSemantics)) {
					programs.add(candidate);
				}
			} while (programs.size() == 0 || programs.get(0).getSemantics().size() > programs.size());

			this.basisProcessor.extractBasis(programs);
		} while (programs.get(0).getSemantics().size() > programs.size());

		basisBuildTime.stop();


		this.state.output.message("Attempting to make linear combination of programs, such that the final semantics is equal to target");
		linearCombinationTime.start();
		this.bestTree = this.basisProcessor.makeLinearCombination(target, programs);
		linearCombinationTime.stop();

		DataSet stats = ((ExtendedStatistics) state.statistics).getIteration();
		stats.put("basisBuildAttempts", basisBuildAttempts);
		stats.put("basisBuildTime", basisBuildTime.getTimeElapsed() * 1.0E-9);
		stats.put("linearCombinationTime", linearCombinationTime.getTimeElapsed() * 1.0E-9);

		return this.bestTree;
	}
}
