package ac.essex.gp.params;

import ac.essex.gp.tree.Node;
import ac.essex.gp.nodes.ercs.BasicERC;
import ac.essex.gp.treebuilders.TreeBuilder;
import ac.essex.gp.treebuilders.NodeSizeBuilder;
import ac.essex.gp.treebuilders.TreeConstraint;

import java.util.Vector;

/**
 * A Repository for all the GP parameters, and accessible programmatically.
 *
 * @author Olly Oechsle, University of Essex, Date: 15-Jan-2007
 * @version 1.0
 */
public class GPParams {

    /**
     * The max time that GP is allowed to proceed for.
     */
    protected int maxTime = 500;

    public int getMaxTime() {
        return maxTime;
    }

    public void setMaxTime(int maxTime) {
        this.maxTime = maxTime;
    }

    protected Vector<NodeParams> nodes;
    protected Vector<NodeParams> ercs;

    protected TreeBuilder treeBuilder = new NodeSizeBuilder();

    protected int returnType = NodeParams.NUMBER;

    /**
     * Used by the Tree Builder.
     */
    protected int maxTreeSize = 100;


    /**
     * The number of generation to run the simulation for.
     */
    protected int generations = 250;

    /**
     * The number of individuals in the population. Higher numbers
     * may discover better solutions but the process is more time
     * consuming.
     */
    protected int populationSize = 400;


    /**
     * The size of the tournament from which individuals are
     * selected. The higher the number, the higher the selection
     * pressure
     * Value is a percentage of the total population size.
     * 0.4 Means that 40% of all individuals are included in each
     * tournament.
     * Numbers tending towards 1 will increase the selection pressure
     */
    protected double tournamentSizePercentage = 0.25;

    /**
     * The number of individual selected for breeding, from which
     * the new generation is created.
     * Essentially this indicates how many tournaments are run.
     * Value is a percentage of the total population size.
     */
    protected double breedSizePercentage = 0.10;

    /**
     * How many of the old population do we directly inject into the next
     * generation? This prevents the fitness value from regressing.
     */
    protected int eliteCount = 5;


    /**
     * The probability that two good individuals do crossover.
     * The crossover operation itself may fail so the actual number may
     * be lower.
     */
    protected double crossoverProbability = 0.50;

    /**
     * The probability that each ERC has of being mutated.
     * Mutation in this case means the ERC will be replaced with a completely new value
     * The larger the individual, the more ERCs will be mutated.
     */
    protected double ERCmutateProbability = 0.10;

    /**
     * If an ERC is NOT mutated, it might be jittered, which means
     * changing it randomly, RELATIVE to its original value (within certain boundaries)
     */
    protected double ERCjitterProbability = 0.10;

    /**
     * The probability that an entire branch of an individual will be replaced with another one
     */
    protected double pointMutationProbability = 0.10;


    /**
     * Turns on optimisation. This compacts the tree of the best individuals
     * just as they are about to produce the next generation. This helps
     * keep code bloat down and makes crossover more effective.
     */
    protected boolean optimisationEnabled = true;

    /**
     * Sets whether a node can dictate what kind of children it would like.
     * See Node.getChildType(index) for more details.
     */
    protected boolean nodeChildConstraintsEnabled = false;

    /**
     * The maximum size a originalNode is allowed to be. If it exceeds this size it is
     * automatically removed from the population. Set this value to -1 to allow
     * all sizes to be evaluated on merit.
     */
    protected int cutoffSize = -1;


    /**
     * The probability that an ADF will be swapped for another one. This happens after an
     * individual is selected for mutation.
     */
    protected double ADFSwapProbability = 0.10;

    /**
     * Whether to ignore warnings when trying to get a non terminal. On rare occasions, such as when
     * using numeric ERCs but not wanting to use numeric functions (such as add/subtract) you'll want
     * to enable this.
     */
    protected boolean ignoreNonTerminalWarnings = false;

    /**
     * When terminals are added, they can be analysed to automatically generate typed ERCs. An ERC is created
     * based on the training data and is associated with a given terminal. When comparisons are made the terminal
     * is compared to known values as determined by the range typed ERC.
     */
    protected boolean automaticRangeTyping = false;

    /**
     * When terminals are added, they can be analysed to automatically generate typed ERCs. An ERC is created
     * based on the training data and is associated with a given terminal. When comparisons are made the terminal
     * is compared to known values as determined by the range typed ERC.
     */
    public boolean isAutomaticRangeTypingEnabled() {
        return automaticRangeTyping;
    }

    /**
     * When terminals are added, they can be analysed to automatically generate typed ERCs. An ERC is created
     * based on the training data and is associated with a given terminal. When comparisons are made the terminal
     * is compared to known values as determined by the range typed ERC.
     */    
    public void setAutomaticRangeTypingEnabled(boolean automaticRangeTyping) {
        this.automaticRangeTyping = automaticRangeTyping;
    }



    // TREE FUNCTIONS
    public static final int ANY_RETURN_TYPE = -1;

    public GPParams() {
        nodes = new Vector<NodeParams>(20);
        ercs = new Vector<NodeParams>(10);
    }

    private long uniqueIDCounter = 0;

    /**
     * Registers a node with the GP Params object. A node params object is taken from the node
     * which allows the node to be instantiated over and over by the tree builder.
     * @param n An instance of the node to register
     */
    public void registerNode(Node n) {
        registerNode(n, 0);
    }

    /**
     * Registers a node with the GP Params object. A node params object is taken from the node
     * which allows the node to be instantiated over and over by the tree builder. This overload
     * offers the additional constraint of specifying how many instances of this node must be included
     * in each individual.
     * @param n An instance of the node to register
     */
    public void registerNode(Node n, int minimumInstances) {
        uniqueIDCounter++;
        NodeParams params = n.createNodeParamsObject();
        params.minimumNodesRequired = minimumInstances;
        params.registerUniqueID(uniqueIDCounter);
        nodes.add(params);
        if (n instanceof BasicERC) {
            ercs.add(n.createNodeParamsObject());
        }
    }

    /**
     * Registers an ADF node. Since ADF nodes are created at runtime and not compile time they can't be
     * instantiated in the same way and so are registered using their ADFNodeParams object.
     */
    public void registerNode(ADFNodeParams anp) {
        nodes.add(anp);
    }

    /**
     * Removes all the ADF definitions from the GP Params object. This allows the ADFs to be replaced
     * with new ones. ADFs may be made programmatically, as an alternative to hard coding, or they may be
     * created automatically (see the Coevolution Problem for more specific detail)
     */
    public void clearADFs() {
        for (int i = 0; i < nodes.size(); i++) {
            NodeParams nodeParams = nodes.elementAt(i);
            if (nodeParams instanceof ADFNodeParams) {
                nodes.removeElementAt(i);
                i--;
            }
        }
    }

    public NodeParams getERCByType(int returnType) {
        Vector<NodeParams> foundParams = new Vector<NodeParams>(10);
        for (int i = 0; i < ercs.size(); i++) {
            NodeParams nodeParams = ercs.elementAt(i);
            if (nodeParams.returntype == returnType) foundParams.add(nodeParams);
        }

        if (foundParams.size() == 0)  {
            // its okay - for instance there are no ERCs that match the substatement type
            // which is sometimes requested by the tree optmiser.
            return null;
        }

        // now select a foundnode at random
        int index = (int) (Math.random() * foundParams.size());
        return foundParams.elementAt(index);
    }
        

    public NodeParams getERCByRangeType(int returnType, int rangeType) {
        Vector<NodeParams> foundParams = new Vector<NodeParams>(10);
        for (int i = 0; i < ercs.size(); i++) {
            NodeParams nodeParams = ercs.elementAt(i);
            if (nodeParams.returntype == returnType && (rangeType == RangeTypes.DONT_CARE || nodeParams.rangeType == rangeType)) foundParams.add(nodeParams);
        }

        if (foundParams.size() == 0)  {
            // its okay - for instance there are no ERCs that match the substatement type
            // which is sometimes requested by the tree optmiser.
            return null;
        }

        // now select a foundnode at random
        int index = (int) (Math.random() * foundParams.size());
        return foundParams.elementAt(index);
    }

    public NodeParams getNodeByType(int type) {

        Vector<NodeParams> foundNodes = new Vector<NodeParams>(10);

        for (int i = 0; i < nodes.size(); i++) {
            NodeParams nodeParams = nodes.elementAt(i);
            if (nodeParams.returntype == type) foundNodes.add(nodeParams);
        }

        if (foundNodes.size() == 0)  {
            throw new RuntimeException("No nodes with return type: " + NodeParams.returnTypeToString(type) + " have been registered.");
        }

        // now select a foundnode at random
        int index = (int) (Math.random() * foundNodes.size());
        return foundNodes.elementAt(index);

    }

    /**
     * Finds a terminal NodeParams object
     * @param returnType The return type that the Terminal must have
     * @param rangeType The range type that the terminal must have (use RangeTypes.DONT_CARE if you don't care)
     */
    public NodeParams getTerminalNodeByType(int returnType, int rangeType) {

        Vector<NodeParams> foundNodes = new Vector<NodeParams>(10);

        for (int i = 0; i < nodes.size(); i++) {
            NodeParams nodeParams = nodes.elementAt(i);
            if (nodeParams.returntype == returnType && (rangeType == RangeTypes.DONT_CARE || nodeParams.rangeType == rangeType) && nodeParams.childCount == 0) foundNodes.add(nodeParams);
        }

        if (foundNodes.size() == 0)  {
            throw new RuntimeException("No *terminal* nodes with return type: " + NodeParams.returnTypeToString(returnType) + " and range type: " + RangeTypes.rangeTypeToString(rangeType) + " have been registered. Consider removing ERCs which use this Range type");
        }

        // now select a foundnode at random
        int index = (int) (Math.random() * foundNodes.size());
        return foundNodes.elementAt(index);

    }

    /**
     * Used to find only non terminal nodes. This is used by the FullBuilder which likes to
     * create trees to their maximum possible extent.
     */
    public NodeParams getNonTerminalNodeByType(int type) {

        Vector<NodeParams> foundNodes = new Vector<NodeParams>(10);

        for (int i = 0; i < nodes.size(); i++) {
            NodeParams nodeParams = nodes.elementAt(i);
            if (nodeParams.returntype == type && (nodeParams.childCount > 0 || nodeParams instanceof ADFNodeParams)) foundNodes.add(nodeParams);
        }

        if (foundNodes.size() == 0)  {
            if (ignoreNonTerminalWarnings() ) {
                return getTerminalNodeByType(type, RangeTypes.DONT_CARE);
            } else {
                throw new RuntimeException("No *non terminal* nodes with return type: " + NodeParams.returnTypeToString(type) + " have been registered.");
            }
        }

        // now select a foundnode at random
        int index = (int) (Math.random() * foundNodes.size());
        return foundNodes.elementAt(index);

    }

    /**
     * Creates tree constraints objects from the nodes with constraints on them
     */
    public Vector<TreeConstraint> getTreeConstraints() {
        Vector<TreeConstraint> constraints = new Vector<TreeConstraint>(10);
        for (int i = 0; i < nodes.size(); i++) {
            NodeParams nodeParams = nodes.elementAt(i);
            if (nodeParams.minimumNodesRequired > 0) {
                constraints.add(new TreeConstraint(nodeParams, nodeParams.minimumNodesRequired));
            }
        }
        return constraints;
    }

    public boolean isNodeChildConstraintsEnabled() {
        return nodeChildConstraintsEnabled;
    }

    public void setNodeChildConstraintsEnabled(boolean nodeChildConstraintsEnabled) {
        this.nodeChildConstraintsEnabled = nodeChildConstraintsEnabled;
    }

    public int getCutoffSize() {
        return cutoffSize;
    }

    public void setCutoffSize(int cutoffSize) {
        this.cutoffSize = cutoffSize;
    }

    public int getReturnType() {
        return returnType;
    }

    public void setReturnType(int returnType) {
        this.returnType = returnType;
    }

    public int getMaxTreeSize() {
        return maxTreeSize;
    }

    public void setMaxTreeSize(int maxTreeSize) {
        this.maxTreeSize = maxTreeSize;
    }

    public int getPopulationSize() {
        return populationSize;
    }

    public void setPopulationSize(int populationSize) {
        this.populationSize = populationSize;
    }

    public boolean ignoreNonTerminalWarnings() {
        return ignoreNonTerminalWarnings;
    }

    public void setIgnoreNonTerminalWarnings(boolean ignoreNonTerminalWarnings) {
        this.ignoreNonTerminalWarnings = ignoreNonTerminalWarnings;
    }

    public int getTournamentSize() {
        return (int) (populationSize * tournamentSizePercentage);
    }

    public double getTournamentSizePercentage() {
        return tournamentSizePercentage;
    }

    public void setTournamentSizePercentage(double tournamentSizePercentage) {
        this.tournamentSizePercentage = tournamentSizePercentage;
    }

    public int getBreedSize() {
        return (int) (populationSize * breedSizePercentage);
    }

    public double getBreedSizePercentage() {
        return breedSizePercentage;
    }

    public void setBreedSizePercentage(double breedSizePercentage) {
        this.breedSizePercentage = breedSizePercentage;
    }

    public int getGenerations() {
        return generations;
    }

    public void setGenerations(int generations) {
        this.generations = generations;
    }

    public double getCrossoverProbability() {
        return crossoverProbability;
    }

    public void setCrossoverProbability(double crossoverProbability) {
        this.crossoverProbability = crossoverProbability;
    }

    public double getERCmutateProbability() {
        return ERCmutateProbability;
    }

    public void setERCmutateProbability(double ERCmutateProbability) {
        this.ERCmutateProbability = ERCmutateProbability;
    }


    public Vector<NodeParams> getNodes() {
        return nodes;
    }

    public void setNodes(Vector<NodeParams> nodes) {
        this.nodes = nodes;
    }

    public int getEliteCount() {
        return eliteCount;
    }

    public void setEliteCount(int eliteCount) {
        this.eliteCount = eliteCount;
    }

    public boolean isOptimisationEnabled() {
        return optimisationEnabled;
    }

    public void setOptimisationEnabled(boolean optimisationEnabled) {
        this.optimisationEnabled = optimisationEnabled;
    }

    public double getERCjitterProbability() {
        return ERCjitterProbability;
    }

    public void setERCjitterProbability(double ERCjitterProbability) {
        this.ERCjitterProbability = ERCjitterProbability;
    }

    public double getPointMutationProbability() {
        return pointMutationProbability;
    }

    public void setPointMutationProbability(double pointMutationProbability) {
        this.pointMutationProbability = pointMutationProbability;
    }

    public TreeBuilder getTreeBuilder() {
        return treeBuilder;
    }

    public void setTreeBuilder(TreeBuilder treeBuilder) {
        this.treeBuilder = treeBuilder;
    }

    public double getADFSwapProbability() {
        return ADFSwapProbability;
    }

    public void setADFSwapProbability(double ADFSwapProbability) {
        this.ADFSwapProbability = ADFSwapProbability;
    }
}
