package ec.gp.semantic.initialization;

import ec.EvolutionState;
import ec.gp.*;
import ec.gp.semantic.ISemantics;
import ec.gp.semantic.func.SimpleNodeBase;
import ec.gp.semantic.geometry.GeometricHelpers;
import ec.gp.semantic.statistics.ExtendedStatistics;
import ec.util.MersenneTwisterFast;
import ec.util.Parameter;
import ec.util.ParameterDatabase;

import java.util.ArrayList;

/**
 * Semantically Driven Initialization by Beadle and Johnson, as discussed in
 * "Semantic analysis of program initialization in genetic programming".
 *
 * @author Tomasz Pawlak
 */
public class SDI extends GPNodeBuilder {

	private final static Parameter DEFAULT_BASE = new Parameter("SDI");
	private final static String SENSITIVITY = "sensitivity";
	private final static String MAX_DEPTH = "max-depth";

	private ArrayList<SimpleNodeBase<?>> existingSemantics = new ArrayList<SimpleNodeBase<?>>(1024);

	// 2^-52 = 0x3FF0000000000000L - 0x0340000000000000L
	protected double sensitivity = Double.longBitsToDouble(0x3CB0000000000000L);
	protected int maxDepth = 17;


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

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

		ParameterDatabase db = state.parameters;

		this.sensitivity = db.getDoubleWithDefault(base.push(SENSITIVITY), defaultBase().push(SENSITIVITY), this.sensitivity);
		this.maxDepth = db.getIntWithDefault(base.push(MAX_DEPTH), defaultBase().push(MAX_DEPTH), this.maxDepth);

		//ExtendedStatistics stats = (ExtendedStatistics) state.statistics;
		//stats.getExperiment().put("initializationSensitivity", this.sensitivity);

		assert this.sensitivity <= 1;
	}

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

		// We use here sampling semantics instead of ROBDDs, however these representations are
		// equivalent, since sampling semantics incorporates all program inputs.


		// Phase 1
		if (this.existingSemantics.size() == 0) {
			for (GPNode[] subset : set.terminals) {
				for (GPNode terminal : subset) {
					this.existingSemantics.add((SimpleNodeBase<?>) terminal);
				}
			}
		}

		// Phase 2
		MersenneTwisterFast rand = state.random[thread];
		SimpleNodeBase<?> randomRoot;
		SimpleNodeBase<?> child;
		int nodeType;

		do {
			nodeType = rand.nextInt(set.nonterminals.length);
			randomRoot = (SimpleNodeBase<?>) set.nonterminals[nodeType][rand.nextInt(set.nonterminals[nodeType].length)].lightClone();
			randomRoot.argposition = (byte) argposition;
			randomRoot.parent = parent;

			for (int ch = 0; ch < randomRoot.children.length; ++ch) {
				do {
					child = this.existingSemantics.get(rand.nextInt(this.existingSemantics.size()));
				} while (child.depth() >= this.maxDepth);

				child = (SimpleNodeBase<?>) child.clone();
				child.argposition = (byte) ch;
				child.parent = randomRoot;

				randomRoot.children[ch] = child;
			}

		} while (randomRoot.getSemantics().isConstant() || this.isKnown(randomRoot.getSemantics()));

		this.existingSemantics.add(randomRoot);

		// Phase 3
		return randomRoot;
	}

	private boolean isKnown(ISemantics semantics) {
		ISemantics existing;

		for (int i = 0; i < this.existingSemantics.size(); ++i) {
			existing = this.existingSemantics.get(i).getSemantics();
			assert existing != null;
			if (semantics.equals(existing) || semantics.distanceTo(existing) < this.sensitivity) {
				return true;
			}
		}

		return false;
	}

	protected void retract(SimpleNodeBase<?> program) {
		int index = this.existingSemantics.lastIndexOf(program);
		if (index >= 0) {
			this.existingSemantics.remove(index);
		}
	}
}
