package ac.essex.gp.tree;

import ac.essex.gp.params.NodeParams;
import ac.essex.gp.params.RangeTypes;
import ac.essex.gp.params.ChildConstraints;
import ac.essex.gp.util.DataStack;
import ac.essex.gp.individuals.Individual;

import java.io.Serializable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Vector;

/**
 * Nodes allow programmatic structures to be simulated by producing
 * tree like structures.
 *
 * @author Olly Oechsle, University of Essex, Date: 15-Jan-2007
 * @version 1.0
 */
public abstract class Node implements Cloneable, Serializable {

    public Node[] child;

    protected int numChildren;

    protected Node parent;

    public Individual root;

    protected String name;

    public transient Debugger debugger;

    public Node(int size) {
        this.numChildren = size;
        this.child = new Node[size];
        this.parent = null;
        this.debugger = new Debugger();
    }

    public abstract int getReturnType();
    public abstract double execute(DataStack data);
    public abstract String toJava();
    public abstract int getChildType(int index);

    protected long id;

    public void setID(long id) {
        this.id = id;
    }

    public long getID() {
        return id;
    }

    public int countChildren() {
        return numChildren;
    }

    /**
     * Should this child (at position denoted by index)  be anything, a terminal or an ERC?
     * @param index Which child is it?
     */
    public int getChildConstraint(int index) {
        return ChildConstraints.NO_CONSTRAINT;
    }

    /**
     * Allows the node to suggest what range ERCs should use.
     * @see Less.java / More.java
     * @param index Which child is it?
     */
    public int getChildRangeType(int index) {
        return RangeTypes.DONT_CARE;
    }

    /**
     * Reteurns the children of this node as a Node array.
     */
    public Node[] getChildren() {
        return child;
    }

    /**
     * Returns a reference to the node's parent
     */
    public Node getParent() {
        return parent;
    }

    /**
     * Inserts a child at the given index point (zero indexed)
     */
    public void addChild(Node child, int index) {
        this.child[index] = child;
        child.parent = this;
    }

    /**
     * Replaces one child with another. Used by the mutation and crossover operators.
     */
    public void replaceChild(Node oldChild, Node newChild) {
        for (int i = 0; i < numChildren; i++) {
            Node node = child[i];
            if (node == oldChild)  {
                child[i] = newChild;
                newChild.parent = this;
            }
        }
    }

    /**
     * Allows a node to swap <i>itselt</i> for something else. This is used by the tree
     * optimiser which can replace silly mathematics with ERCs to reduce tree size.
     */
    public void replaceMyselfWith(Node newChild) {
        if (parent != null) {
            parent.replaceChild(this, newChild);
        } else {
            root.setTree(newChild);
        }
    }

    /**
     * Gets the Node Params object which contains metadata about this node and allows this
     * node to be copied.
     */
    public NodeParams createNodeParamsObject() {
        return new NodeParams(getClass().getCanonicalName(), getReturnType(), RangeTypes.DONT_CARE, numChildren);
    }

    /**
     * The name of the Node. This is used by the Java Writer to assign variable names to nodes
     * as they are converted into code.
     */
    public String getName() {
        return numChildren == 0? toJava() : name;
    }

    /**
     * Sets the name of the node. This is used by the Java Writer to assign variable names to nodes
     * as they are converted into code.
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Called after a node has been copied by deep cloning. The debugger is transient and needs to
     * be instantiated.
     */
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        debugger = new Debugger();
    }

    /**
     * Calculates the size of the tree
     * @return The number of nodes in the tree, including this (parent) originalNode.
     */
    public int getTreeSize() {
        int treesize = 1;
        for (int i = 0; i < numChildren; i++) {
            treesize += child[i].getTreeSize();
        }
        return treesize;
    }

    /**
     * Calculates how deep this node is in the tree
     * @return The depth of the node in the tree. The root originalNode is at depth 1.
     */
    public int getDepthFromTop() {
        if (parent != null) {
            return 1 + parent.getDepthFromTop();
        } else {
            return 1;
        }
    }

    /**
     * Calculates how many levels this node is from the bottom of the tree.
     * @return
     */
    public int getDepthToBottom() {
        if (numChildren == 0) return 0;
        int deepestChild = 1;
        for (int i = 0; i < numChildren; i++) {
            int childDepth = child[i].getDepthToBottom();
            if (childDepth > deepestChild) deepestChild = childDepth;
        }
        return deepestChild;
    }

    /**
     * Counts the number of nodes as defined by the node params
     * which exist in a particular tree.
     */
    public int countNodes(NodeParams p) {
        Vector<Node> foundNodes = new Vector<Node>(10);
        findNodes(this, p.getID(), foundNodes);
        return foundNodes.size();
    }

    private void findNodes(Node parent, long nodeID, Vector<Node> foundNodes) {
        if (parent.getID() == nodeID) {
            foundNodes.add(parent);
        }
        for (int i = 0; i < parent.child.length; i++) {
            Node child = parent.child[i];
            if (child != null) findNodes(child, nodeID, foundNodes);
        }
    }

    /**
     * All items are optimisable by default. Unless you override this method to return false.
     */
    public boolean isOptimisable() {
        return true;
    }

    /**
     * Gives nodes the opportunity to run their own optimisation routines.
     * @return The originalNode that has been optimised. 90% of the time, this relates to the originalNode
     * itself, so just return using <code>this</code>. However, sometimes a originalNode may choose to delete
     * itself and pass focus to its replacement. It has to be run this way otherwise the TreeOptimiser
     * would attempt to follow along branches that no longer existed.
     */
    public Node optimise() {
        // do nothing
        return this;
    }
        
}
