package org.oakgp;

import static org.oakgp.Arguments.createArguments;
import static org.oakgp.util.Utils.assertEvaluateToSameResult;

import java.util.Optional;

import org.oakgp.node.ConstantNode;
import org.oakgp.node.FunctionNode;
import org.oakgp.node.Node;

/**
 * Simplifies tree structures by replacing expressions with their values.
 * <p>
 * e.g. The expression:
 *
 * <pre>
 * (+ 7 (* 3 6))
 * </pre>
 *
 * can be simplified to the value:
 *
 * <pre>
 * 25
 * </pre>
 *
 * </p>
 * <b>Note:</b> relies on {@code Operator} implementations being referentially transparent.
 */
public final class NodeSimplifier {
	public Node simplify(Node input) {
		int ctr = 0;
		Node previous;
		Node output = input;
		do {
			previous = output;
			output = _simplify(output);
			if (ctr++ > 1001) { // TODO
				return output;
			}
		} while (output instanceof FunctionNode && !output.equals(previous));
		return output;
	}

	private Node _simplify(Node input) {
		if (input instanceof FunctionNode) {
			Node output = simplifyFunctionNode((FunctionNode) input);
			assertEvaluateToSameResult(input, output);
			return output;
		} else {
			return input;
		}
	}

	private Node simplifyFunctionNode(final FunctionNode input) {
		// try to simplify each of the arguments
		Arguments inputArgs = input.getArguments();
		Node[] simplifiedArgs = new Node[inputArgs.length()];
		boolean modified = false;
		boolean constants = true;
		for (int i = 0; i < simplifiedArgs.length; i++) {
			Node originalArg = inputArgs.get(i);
			simplifiedArgs[i] = _simplify(originalArg);
			if (originalArg != simplifiedArgs[i]) {
				modified = true;
			}
			if (!(simplifiedArgs[i] instanceof ConstantNode)) {
				constants = false;
			}
		}

		// if could simplify arguments then use simplified version to create new FunctionNode
		Arguments arguments;
		FunctionNode output;
		if (modified) {
			arguments = createArguments(simplifiedArgs);
			output = new FunctionNode(input.getOperator(), arguments);
		} else {
			arguments = inputArgs;
			output = input;
		}

		// if all arguments are constants then return result of evaluating them
		if (constants) {
			return new ConstantNode(output.evaluate(null), output.getType());
		}

		// try to simplify using operator
		// return input.getOperator().simplify(arguments).orElse(output);
		Optional<Node> o = input.getOperator().simplify(arguments);
		if (o.isPresent()) {
			assertEvaluateToSameResult(input, o.get());
			return o.get();
		} else {
			return output;
		}
	}
}

@@ -145,24 +145,72 @@
 va.util.
-Optional
+HashSet;%0Aimport java.util.Optional;%0Aimport java.util.Set
 ;%0A%0Aimpor
@@ -303,16 +303,55 @@
 de.Node;
+%0Aimport org.oakgp.serialize.NodeWriter;
 %0A%0A/**%0A *
@@ -752,16 +752,51 @@
 tr = 0;%0A
+%09%09Set%3CString%3E s = new HashSet%3C%3E();%0A
 %09%09Node p
@@ -887,16 +887,253 @@
 utput);%0A
+%09%09%09if (!output.equals(previous) && !s.add(new NodeWriter().writeNode(output))) %7B // TODO%0A%09%09%09%09// throw new IllegalArgumentException(new NodeWriter().writeNode(output) + %22 %22 + new NodeWriter().writeNode(previous));%0A%09%09%09%09return output;%0A%09%09%09%7D%0A
 %09%09%09if (c
@@ -1142,17 +1142,18 @@
 ++ %3E 100
-1
+00
 ) %7B // T
@@ -1156,37 +1156,93 @@
 // TODO%0A%09%09%09%09
-return out
+throw new IllegalArgumentException(new NodeWriter().writeNode(in
 put
+))
 ;%0A%09%09%09%7D%0A%09%09%7D w

stop simplifying if found a previous solution