package ec.gp.semantic.func;

import java.util.*;

import ec.*;
import ec.gp.*;
import ec.gp.semantic.*;
import ec.util.*;
import experimentdatabase.*;

public abstract class SimpleNodeBase<DataType> extends GPNode {

	private static long totalEvaluationCount = 0L;
	protected static EvolutionState state;

	// #ifndef NO_SEMANTICS_CACHE
	// #ifdef SOFT_REF_SEMANTICS_CACHE
	//@	private SoftReference<ISemantics> semantics = new SoftReference<ISemantics>(null);
	//@
	// #else
	private ISemantics semantics = null;

	// #endif
	// #endif

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

	public ISemantics getSemantics() {
		ISemantics semantics;

		// #ifndef NO_SEMANTICS_CACHE

		// #ifdef SOFT_REF_SEMANTICS_CACHE
		//@		semantics = this.semantics.get();
		// #else
		semantics = this.semantics;
		// #endif

		if (semantics == null) {
			// #endif

			// compute semantics now
			ISemantics[] childSemantics = new ISemantics[this.children.length];
			for (int ch = 0; ch < this.children.length; ++ch) {
				assert this.children[ch] != null;

				childSemantics[ch] = ((SimpleNodeBase<DataType>) this.children[ch]).getSemantics();
			}

			semantics = this.execute(childSemantics);

			//statistics
			DataSet iteration = ((ec.gp.semantic.statistics.ExtendedStatistics) state.statistics).getIteration();
			iteration.put("totalEvaluations", ++totalEvaluationCount);

			// #ifndef NO_SEMANTICS_CACHE
			// #ifdef SOFT_REF_SEMANTICS_CACHE
			//@			this.semantics = new SoftReference<ISemantics>(semantics);
			// #else
			this.semantics = semantics;
			// #endif
		}
		// #endif

		assert semantics != null : this.makeLispTree();
		return semantics;
	}

	public void resetSemantics() {
		// #ifndef NO_SEMANTICS_CACHE
		// #ifdef SOFT_REF_SEMANTICS_CACHE
		//@		this.semantics.clear();
		// #else
		this.semantics = null;
		// #endif
		// #endif
	}

	public void resetSemanticsRecursive() {
		// #ifndef NO_SEMANTICS_CACHE
		resetSemantics();
		for (int i = 0; i < children.length; ++i) {
			assert children[i] != null;
			((SimpleNodeBase<?>) children[i]).resetSemanticsRecursive();
		}
		// #endif
	}

	@Override
	public void resetNode(EvolutionState state, int thread) {
		super.resetNode(state, thread);
		this.resetSemantics();
	}

	public static long getTotalEvaluationCount() {
		return totalEvaluationCount;
	}

	@Override
	public Object clone() {
		SimpleNodeBase<DataType> cloned = (SimpleNodeBase<DataType>) super.clone();
		cloned.resetSemantics(); // recursive call is already made by super.clone()!
		return cloned;
	}

	@Override
	public GPNode lightClone() {
		SimpleNodeBase<DataType> copy = (SimpleNodeBase<DataType>) super.lightClone();
		copy.resetSemantics();
		return copy;
	}

	/**
	 * @param output
	 * @param missingArgIdx   Index of argument to calculate
	 * @param restOfArguments Array of remaining arguments, without the missing one (so, the indexes after the missing one are moved
	 *                        by one).
	 * @return Array of possible values.
	 */
	public abstract DataType[] invert(DataType output, int missingArgIdx, DataType... restOfArguments);

	protected abstract ISemantics execute(ISemantics... arguments);

	public abstract boolean isSymmetric();

	@Override
	public final void eval(final EvolutionState state, final int thread, final GPData input, final ADFStack stack,
						   final GPIndividual individual, final Problem problem) {
		throw new UnsupportedOperationException("Use getSemantics instead.");
	}

	@Override
	public int rootedTreeHashCode() {
		int hash = this.nodeHashCode();

		if (this.isSymmetric()) {
			for (int x = 0; x < this.children.length; x++) {
				hash ^= this.children[x].rootedTreeHashCode();
			}
			hash = (hash << this.children.length | hash >>> (32 - this.children.length));
		} else {
			for (int x = 0; x < this.children.length; x++) {
				// rotate hash and XOR
				hash = (hash << 1 | hash >>> 31) ^ this.children[x].rootedTreeHashCode();
			}
		}

		return hash;
	}

	@Override
	public boolean rootedTreeEquals(GPNode node) {
		if (!this.nodeEquals(node))
			return false;

		if (this.isSymmetric()) {
			byte equalCount = 0;
			byte childrenMask = 0;
			assert node.children.length <= 8 /* number of bits in childrenMask */;

			// order of children is irrelevant
			for (int x = 0; x < children.length; ++x) {
				for (int y = 0; y < node.children.length; ++y) {
					if ((childrenMask & (1 << y)) == 0 && children[x].rootedTreeEquals(node.children[y])) {
						++equalCount;
						childrenMask |= 1 << y;
						break;
					}
				}

				if (equalCount <= x)
					return false;
			}

			assert equalCount == this.children.length;
			//assert this.rootedTreeHashCode() == node.rootedTreeHashCode();
			return true;
		} else {
			for (int x = 0; x < children.length; x++) {
				if (!(children[x].rootedTreeEquals(node.children[x])))
					return false;
			}
		}

		//assert this.rootedTreeHashCode() == node.rootedTreeHashCode();
		return true;
	}

}
