package ac.essex.gp.individuals;

import ac.essex.gp.tree.Node;
import ac.essex.gp.util.DataStack;
import ac.essex.gp.util.JavaWriter;
import ac.essex.gp.params.GPParams;
import ac.essex.gp.nodes.ADFNode;

import java.io.*;
import java.util.Vector;

/**
 * Represents an individual in GP. This is the combination of an
 * execution tree and other associated characteristics, such as
 * the fitness of the individul (if it has been evaluated). It also
 * contains a few utility methods that allow the tree to be manipulated.
 *
 * @author Olly Oechsle, University of Essex, Date: 15-Jan-2007
 * @version 1.0 Initial Version
 *
 */
public class Individual implements Comparable, Serializable {

    protected Node tree;
    protected int size;

    protected transient double fitness;
    protected transient int hits;

    protected int returnType;

    public Individual(Node tree, int returnType) {
        if (tree != null) setTree(tree);
        this.fitness = 0;
        this.hits = 0;
        this.size = 0;
        this.returnType = returnType;
    }

    /**
     * Gets the individual's return type which is usually that defined by the appropriate GP Params object.
     */
    public int getReturnType() {
        return returnType;
    }

    /**
     * Parses the individual's tree to count how many nodes it has. Note that ADFs count as one node.
     */
    public int getTreeSize() {
        return tree.getTreeSize();
    }

    /**
     * Gets the individual's execution tree
     */
    public Node getTree() {
        return tree;
    }

    /**
     * Sets the individual's execution tree.
     */
    public void setTree(Node tree) {
        this.tree = tree;
        // give the top level originalNode a reference to this individual.
        // this is for tree optimisation purposes if that originalNode chooses
        // to replace itself - it then has a reference to a parent
        // to do the swapping.
        this.tree.root = this;
    }

    /**
     * Hits is related to fitness but only measures the successes of the individual (the
     * true positive (TP) results). As fitness tends toward zero (perfect), hits will tend
     * to increase.
     */
    public int getHits() {
        return hits;
    }

    /**
     * Hits is related to fitness but only measures the successes of the individual (the
     * true positive (TP) results). As fitness tends toward zero (perfect), hits will tend
     * to increase.
     * @param hits An int value counting the number of instances where this individual returns the correct answer.
     */
    public void setHits(int hits) {
        this.hits = hits;
    }

    int mistakes = 0;

    public int getMistakes() {
        return mistakes;
    }

    public void setMistakes(int mistakes) {
        this.mistakes = mistakes;
    }

    /**
     * Gets the fitness of the individual. This being Koza fitness, the closer
     * to zero the number is the better the individual.
     * @return
     */
    public double getKozaFitness() {
        return fitness;
    }

    /**
     * Sets the individuals fitness. This being Koza fitness, the closer
     * to zero the number is the better the individual.
     * @param fitness A float value representing fitness. 0 is ideal.
     */
    public void setKozaFitness(double fitness) {
        this.fitness = fitness;
    }

    /**
     * Finds all the ADF nodes in this individual
     * @return A vector of the ADF nodes in the individual
     */
    public Vector<ADFNode> getADFNodes() {
        Vector<ADFNode> foundNodes = new Vector<ADFNode>(10);
        searchForADFs(tree, foundNodes);
        return foundNodes;
    }

    private void searchForADFs(Node tree, Vector<ADFNode> foundNodes) {
        if (tree instanceof ADFNode) {
            foundNodes.add((ADFNode) tree);
        } 
        for (int i = 0; i < tree.countChildren(); i++) {
            searchForADFs(tree.child[i], foundNodes);
        }
    }

    /**
     * Executes the individual's tree
     * @param stack A stack which contains information that some of the nodes need to know, such as a reference to the current image.
     */
    public double execute(DataStack stack) {
        this.tree.execute(stack);
        return stack.value;
    }

    /**
     * Compares this individual to another individual based on fitness, allows the individuals
     * to be ordered using Collections.Sort() so that the best individual is at the top.
     */
    public int compareTo(Object o) {
        if (o instanceof Individual)  {
            double otherFitness = ((Individual) o).getKozaFitness();
            if (otherFitness > fitness) return -1; // I am better
            if (otherFitness < fitness) return +1; // I am worse
            // if it has the same fitness, then take into account the size of each individual
            int mySize = getTreeSize();
            int otherSize = ((Individual) o).getTreeSize();
            if (otherSize > mySize) return -1; // I am better
            if (otherSize < mySize) return +1;
            // there's nothing significantly different about us
            return 0;
        } throw new RuntimeException("Non individual in population");
    }

    public String toString() {
        return "sxGP Individual, fitness: " + fitness + ", hits: " + hits + ", size: " + getTreeSize();
    }

    /**
     * Converts the individual into Java Code
     * @return
     */
    public String toJava() {
        return JavaWriter.toJava(this);
    }

    /**
     * If the individual is to be completely removed from the population, it can
     * be assigned the worst possible fitness, which is Integer.MAX_VALUE
     */
    public void setWorstFitness() {
        setKozaFitness(Integer.MAX_VALUE);        
    }


    /**
     * @param bias Between 1-0. High values favours any subtree, lower values favour the
     * larger nodes closer to the root of the tree
     */
    public Node getRandomSubtree(int returnType, double bias) {
        return getRandomNode(getNodes(returnType, Integer.MAX_VALUE), bias);
    }

    public Node getRandomSubtree(int returnType, double bias, int maxSize) {
        return getRandomNode(getNodes(returnType, maxSize), bias);
    }

    /**
     * Used by the mutation operator.
     * @param bias Between 1-0. High values favours any subtree, lower values favour the
     * larger nodes closer to the root of the tree
     */
    private Node getRandomNode(Vector<Node> nodes, double bias) {
        if (nodes.size() == 0) return null;
        double randomness = (Math.random() * bias);
        return nodes.elementAt((int) (nodes.size() * randomness));
    }

    private Vector<Node> getNodes(int returnType, int maxSize) {
        Vector<Node> nodes = new Vector<Node>(10);
        getNodes(getTree(), nodes, 0, returnType, maxSize);
        return nodes;
    }

    private void getNodes(Node parent, Vector<Node> nodes, int depth, int returnType, int maxSize)  {

      // don't add the root originalNode, otherwise crossover would just be swapping the individuals over
      if (depth > 0) {
          if (returnType == GPParams.ANY_RETURN_TYPE || parent.getReturnType() == returnType)
          if (parent.getTreeSize() < maxSize) nodes.add(parent);
      }

      for (int i = 0; i < parent.countChildren(); i++) {
          Node child = parent.child[i];
          getNodes(child, nodes, depth + 1, returnType, maxSize);
      }

    }


    /**
     * Saves the individual to disk in serialised form. This is primarily
     * to allow the JavaWriter output to be compared with the actual individual
     * in a debugging environment.
     */
    public void save(File f){
        try {
            System.out.println("Saving File");
            FileOutputStream fos = new FileOutputStream(f);
            ObjectOutputStream out = new ObjectOutputStream(fos);
            out.writeObject(this);
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Loads a serialised individual from disk. Mainly used for debugging the
     * Java Writer.
     */
    public static Individual load(File f) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(f);
        ObjectInputStream in = new ObjectInputStream(fis);
        Individual individual = (Individual) in.readObject();
        in.close();
        return individual;
    }

}
