package ac.essex.gp.tree;

import ac.essex.gp.individuals.Individual;
import ac.essex.gp.params.GPParams;
import ac.essex.gp.params.NodeParams;
import ac.essex.gp.nodes.ercs.BasicERC;

/**
 * <p>
 * Optimises individuals' trees. It is quite simple. If a subtree always returns
 * the same value (which is recorded by a node's debugger) then that tree can be replaced
 * with a single ERC containing that value. This helps to eliminate much of the spurious
 * arithmetic that can arise in certain situations.
 * </p>
 * <p>
 * At the same time it is possible to see if the same value is returned by a <i>terminal<i>
 * this indicates that the terminal is no use on this particular training data and should be removed.
 * </p>
 * @author Olly Oechsle, University of Essex, Date: 16-Jan-2007
 * @version 1.0
 */
public class TreeOptimiser {

    //public static int nodesSaved = 0;

    public static void optimise(Individual ind, GPParams params) {

        // as each originalNode is executed, basic usage statistics are gathered
        // about what it has been returning. We can use this information to
        // delete parts of a tree, or whether to use it in crossover.
        //int beforeSize = ind.getTreeSize();
        optimise(ind.getTree(), params);
        //int afterSize = ind.getTreeSize();

        //nodesSaved += (beforeSize - afterSize);

        //System.out.println("OPTIMISE: " + beforeSize + " -> " + afterSize);

    }

    private static void optimise(Node node, GPParams params) {

        // only optimise nodes that allow optimisation.
        if (node.isOptimisable()) {

            // see if the node returns different values (ie does it classify anything?)
            if (node.debugger.alwaysTheSame()) {

                // replace it with a constant instead - see if there is one available
                NodeParams suitableERC = params.getERCByType(node.getReturnType());
                if (suitableERC != null) {
                    ac.essex.gp.nodes.ercs.BasicERC basicErc = (BasicERC) suitableERC.getInstance();
                    basicErc.setValue(node.debugger.getLastValue());
                    // okay - now replace this originalNode with the erc
                    Node parent = node.getParent();
                    if (parent != null) {
                        parent.replaceChild(node, basicErc);
                        return;
                    }
                }

                // if this node is a terminal, then this is an interesting situation, because the terminal
                // is not providing any useful data.
                if (node instanceof Terminal) {
                    System.err.println("Terminal " + (node.toString()) + " is not useful for this training data.\nsxGP recommends it be removed.");
                }

            }

            if (node.debugger.neverExecuted()) {
                // TODO: Implement this                
            }

            // run the originalNode's own optimisation routines too
            node = node.optimise();

        }

        for (int i = 0; i < node.countChildren(); i++) {
            optimise(node.child[i], params);
        }

    }

}
