package ec.gp.semantic.breed;

import java.util.*;

import ec.*;
import ec.gp.*;
import ec.util.*;

public class RandomTreeProvider implements Prototype {

	private static final Parameter DEFAULT_BASE = new Parameter("RandomTreeProvider");
	private static final String RETURN = "return";
	private static final String P_BUILDER = "build";
	private static final String FUNCTION_SET = "fset";

	private EvolutionState state;
	private GPNodeBuilder builder;
	private GPFunctionSet functionSet;
	private GPType returnType;
	private HashMap<Wrapper, Wrapper> randomTrees = new HashMap<Wrapper, Wrapper>();

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

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

		//this is copied from MutationPipeline
		Parameter p = base.push(P_BUILDER);
		Parameter d = defaultBase().push(P_BUILDER);

		this.builder = (GPNodeBuilder) (state.parameters.getInstanceForParameter(p, d, GPNodeBuilder.class));
		this.builder.setup(state, p);
		String functionSetName = state.parameters.getString(base.push(FUNCTION_SET), defaultBase().push(FUNCTION_SET));
		this.functionSet = GPFunctionSet.functionSetFor(functionSetName, state);

		this.returnType = GPType.typeFor(state.parameters.getString(base.push(RETURN), defaultBase().push(RETURN)), state);
	}

	@Override
	public Object clone() {
		try {
			RandomTreeProvider cloned = (RandomTreeProvider) super.clone();
			return cloned;
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
	}

	public GPNode getRandomTree(int thread, GPTree t1, int requestedSize) {
		GPNode randomTree = builder.newRootedTree(state,
				//t1.child.parentType((GPInitializer) state.initializer),
				this.returnType,
				thread,
				t1,
				this.functionSet,
				0,
				requestedSize);

		Wrapper wrapped = new Wrapper(randomTree);
		Wrapper inCache = this.randomTrees.get(randomTree);

		if (inCache != null)
			return inCache.getNode();

		this.randomTrees.put(wrapped, wrapped);

		return randomTree;
	}

	final class Wrapper {
		private final GPNode node;

		public Wrapper(GPNode node) {
			this.node = node;
		}

		public final GPNode getNode() {
			return this.node;
		}

		@Override
		public final boolean equals(Object obj) {
			assert obj instanceof Wrapper;

			return this.node.rootedTreeEquals(((Wrapper) obj).getNode());
		}

		public final int hashCode() {
			return this.node.rootedTreeHashCode();
		}
	}

}
