package ac.essex.gp.nodes.ercs;

import ac.essex.gp.params.GPParams;
import ac.essex.gp.params.NodeParams;
import ac.essex.gp.params.AutorangeERCNodeParams;

import java.util.Vector;
import java.util.Collections;

/**
 * AN ERC which outputs only specific values. These values are selected based on the training data for the problem.
 * A different autorange ERC is created for each terminal and is then compared ONLY with that terminal. This helps
 * to reduce the search space by ensuring that only meaningful comparison within a specific range of a function are made.
 * This is more accurate than the other custom ERCs which have small ranges but are matched by hand to functions. It is
 * also advantageous because it is generic.
 *
 * @author Olly Oechsle, University of Essex, Date: 11-Apr-2007
 * @version 1.0
 *
 */
public class AutorangeERC extends BasicERC {

    // the outputs that this ERC will outut
    protected Vector<Double> outputs;

    // a unique ID that identifies this range - this allows us to tie this ERC to the terminal that initialised it.
    protected int rangeID;

    /**
     * Constructs the AutoRangeERC with a unique rangeID.
     * @throws An Exception if the rangeID is not unique in the params object.
     */
    public AutorangeERC(GPParams params, int rangeID) {
        outputs = new Vector<Double>(100);
        // make sure this rangeID is not already in use.
        if (params.getERCByRangeType(getReturnType(), rangeID) != null) {
            throw new RuntimeException("Cannot initialise AutorangeERC with rangeID: " + rangeID + ". RangeID already in use.");
        }
        this.rangeID = rangeID;
    }

    /**
     * Blank constructor - do not use this programmatically - it is only used when the ERC is instantiated automatically by SxGp.
     */
    public AutorangeERC() {
        // do nothing
    }

    // do we need to sort the output?
    private boolean unsorted = true;

    /**
     * Records the output of a terminal, it may return this value again
     * later when used as an ERC.
     */
    public void addData(double value) {
        if (!outputs.contains(value)) outputs.add(value);
        unsorted = true;
    }

    /**
     * Returns the unique range ID of this ERC
     */
    public int getRangeID() {
        return rangeID;
    }

    /**
     * The position in the output vector where we're taking data from.
     */
    private int cursor;

    public double setValue() {
        if (outputs == null) {
            throw new RuntimeException("Cannot get value from autorangeERC with rangeID: " + rangeID + ". The output vector is null.");
        }
        if (outputs.size() == 0) {
            throw new RuntimeException("Cannot get value from autorangeERC with rangeID: " + rangeID + ". The ERC has no outputs.");
        }
        // ensure the outputs are in order (needed for jittering)
        if (unsorted) {
            Collections.sort(outputs);
            unsorted = false;
        }
        cursor = (int) Math.random() * outputs.size();
        return outputs.elementAt(cursor);
    }

    public void jitter() {
        // we want to ensure that a jittered value is similar to the original value returned by this
        // ERC. To do this we remember WHERE in the vector the value came from and then jitter the LOCATION
        // and then return the value at the new location.
        if (cursor <= 0) {
            cursor++;
        } else {
            if (cursor >= outputs.size() - 1) {
                cursor--;
            } else {
                if (Math.random() > 0.5) {
                    cursor++;
                } else {
                    cursor--;
                }
            }
        }
        value = outputs.elementAt(cursor);
    }

    public NodeParams createNodeParamsObject() {
        // this Node Params object is able to restore the ERCs runtime data
        return new AutorangeERCNodeParams(getClass().getCanonicalName(), getReturnType(), rangeID, numChildren, outputs, unsorted);
    }

    /**
     * Allows this class to inherit its outputs from another ERC. This happens when the ERC is being created
     * by the NodeParams object
     */
    public void setRuntimeData(int rangeID, Vector<Double> outputs, boolean sorted) {
        this.rangeID = rangeID;
        this.outputs = outputs;
        this.unsorted = sorted;
    }

}
