package ec.gp.semantic.geometry;

import ec.EvolutionState;
import ec.app.semanticGP.func.numeric.*;
import ec.gp.GPNode;
import ec.gp.semantic.ISemantics;
import ec.gp.semantic.func.SimpleNodeBase;

public class DoubleGeometryProvider implements IGeometryProvider {

	/**
	 * Semantically combines the behavior of parent1 with behavior of parent2 using random tree. The semantics of random
	 * tree is calculated, and the entire tree is scaled and summed with two constants that guarantee that the tree
	 * returns value from range [0, 0.5].
	 *
	 * @param parent1
	 * @param parent2
	 * @param randomTree the tree that returns values from range [0, 1]
	 * @return
	 */
	@Override
	public GPNode combine(EvolutionState state, GPNode parent1, GPNode parent2, GPNode randomTree) {

		randomTree = this.transformTree(state, randomTree);

		/*Sub oneMinusRand = new Sub();
		oneMinusRand.children = new GPNode[] { new Constant(state, 1.0), (GPNode) randomTree.clone() };

		Mul times1 = new Mul();
		times1.children = new GPNode[] { randomTree, parent1 };

		Mul times2 = new Mul();
		times2.children = new GPNode[] { oneMinusRand, parent2 };

		Sum addition = new Sum();
		addition.children = new GPNode[] { times1, times2 };

		return addition;*/

		return new Combiner(state,
				(SimpleNodeBase<Double>) parent1,
				(SimpleNodeBase<Double>) parent2,
				(SimpleNodeBase<Double>) randomTree);

		/*Averager avg = new Averager((SimpleNodeBase<Double>) parent1, (SimpleNodeBase<Double>) parent2, coefficient);
		return avg;*/

		//return new AveragerSlim(state, (SimpleNodeBase<Double>) parent1, (SimpleNodeBase<Double>) parent2, coefficient);
	}

	@Override
	public GPNode combine(EvolutionState state, GPNode[] parents, GPNode[] randomTrees) {
		return new Combiner(state,
				(SimpleNodeBase[]) parents,
				(SimpleNodeBase[]) randomTrees);
	}

	@Override
	public GPNode differencingCombine(EvolutionState state, GPNode parent, GPNode random1, GPNode random2) {

		Sub sub = new Sub();
		sub.children = new GPNode[]{random1, random2};

		//GPNode transformed = this.transformTree(state, sub);

		Constant mutationRate = new Constant(state.random[0].nextDouble() * 0.02);
		Mul mul = new Mul();
		mul.children = new GPNode[]{mutationRate, sub};

		Sum sum = new Sum();
		sum.children = new GPNode[]{parent, mul};

		return sum;
	}

	/**
	 * Wraps given tree randomTree into sigmoid function 1/(1+e^{-randomTree})
	 *
	 * @param state
	 * @param randomTree
	 * @return
	 */
	private GPNode transformTree(EvolutionState state, GPNode randomTree) {
		// we intentionally omit subtraction from the formula - it only changes direction
		// of asymptotic convergence to either 0 or 1
		Exp exp = new Exp();
		exp.children = new GPNode[]{randomTree};

		Constant one = Constant.ONE;

		Sum sum = new Sum();
		sum.children = new GPNode[]{one, exp};

		Div div = new Div();
		div.children = new GPNode[]{one, sum};

		/*ISemantics s = div.getSemantics();
		double[] d = (double[])s.getValue();
		for (int i=0; i< d.length; ++i) {
			System.out.println(d[i]);
			assert 0.0 <= d[i] && d[i] <= 1.0 : d[i];
		}*/

		return div;
	}

	private GPNode transformTree_old(EvolutionState state, GPNode randomTree) {
		double[] semantics = (double[]) ((SimpleNodeBase<Double>) randomTree).getSemantics().getValue();
		double min = Double.MAX_VALUE;
		double max = -Double.MAX_VALUE;

		for (int i = 0; i < semantics.length; ++i) {
			if (Double.isInfinite(semantics[i]) || Double.isNaN(semantics[i]))
				continue;

			if (semantics[i] < min) {
				min = semantics[i];
			}
			if (semantics[i] > max) {
				max = semantics[i];
			}
		}

		GPNode output = randomTree;

		if (max < min) {
			// it can happen if all components of semantics are NaNs or Infinite
			return output;
		}

		if (0.0 <= min && max <= 1.0) {
			// nothing to scale
			return output;
		}

		if (Math.abs(min) > 1.0E-300) {
			Constant b = new Constant(-min);
			Sum sum = new Sum();
			sum.children = new GPNode[]{output, b};

			output = sum;
		}

		if (min < max - 1.0E-300) {
			Constant a = new Constant(1.0 / (max - min));

			Mul mul = new Mul();
			mul.children = new GPNode[]{a, output};

			output = mul;
		}
	
		/*ISemantics outputSemantics = ((SimpleNodeBase<?>) output).getSemantics();
		double[] outputSemanticsData = (double[]) outputSemantics.getValue();
		for (int i = 0; i < outputSemanticsData.length; ++i) {
			assert (0.0 <= outputSemanticsData[i] && outputSemanticsData[i] <= 1.0)
					|| (Double.isInfinite(outputSemanticsData[i]) && Double.isInfinite(semantics[i]))
					|| (Double.isNaN(outputSemanticsData[i]) && Double.isNaN(semantics[i]));
		}*/

		return output;
	}
}
