package ac.essex.ooechs.ecj.ecj2java.nodes;

import ec.gp.*;
import ec.EvolutionState;
import ec.Problem;
import ec.util.Code;
import ec.util.DecodeReturn;

import java.io.DataInput;
import java.io.IOException;
import java.io.DataOutput;

// If you have downloaded ECJ2Java, you'll need to change this
// to your own DoubleData class that you use. There is an example
// of such a class in ac.essex.ooechs.ecj.ecj2java.example.data
import ac.essex.ooechs.ecj.commons.data.DoubleData;
//import ac.essex.ooechs.ecj.ecj2java.example.data.DoubleData;


/**
 * <p>
 * ParseableERC is an adapter class that serves two functions:
 * <ol>
 * <li>It allows ECJ2Java to write the ERC into pure java</li>
 * <li>It makes ERCs easier to code</li>
 * </ol>
 * </p>
 *
 * <p>
 * It adapts a number of unpleasant methods in the ERC class that don't
 * need to be altered often (if at all).
 * </p>
 *
 * <p>
 * It implements the functionality required by ParseableNode which allows
 * the node to be converted into pure java.
 * </p>
 *
 * <p>
 * Implementation is easy - just take your existing ERC and extend
 * ParseableERC instead of ERC. The only required methods are setNumber()
 * and name().
 * </p>
 *
 * <p/>
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version,
 * provided that any use properly credits the author.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details at http://www.gnu.org
 * </p>
 *
 * @author Olly Oechsle, University of Essex, Date: 06-Sep-2006
 * @version 1.0
 */
public abstract class ParseableERC extends ERC implements ParseableNode {

    protected int type = ParseableNode.ERC;
    protected double value;

    /**
     * Set the value of the ERC here
     */
    public abstract double setNumber(final EvolutionState state, final int thread);

    /**
     * Mutates the node's "value".  This is called by mutating operators
     * which specifically <i>mutate</i> the "value" of ERCs, as opposed to
     * replacing them with whole new ERCs. The default form of this function
     * simply calls resetNode(state,thread), but you might want to modify
     * this to do a specialized form of mutation, applying gaussian
     * noise for example.
     */
    public void mutateERC(final EvolutionState state, final int thread) {
        // move the value within +- 25% of its original position
        value *= 1 + ((Math.random() * 0.5) - 0.25);
    }

    /**
     * The name of the class that ECK uses.
     */
    public abstract String name();

    public ParseableNode getChild(int index) {
        return (ParseableNode) children[index];
    }

    public int getType() {
        return type;
    }

    public String getValue() {
        return String.valueOf(value);
    }

    public void setValue(double value) {
        this.value = value;
    }

    public int countChildren() { return 0; }

    String variableName;

    /**
     * When the java writer uses this node, it may use a variable name to describe it.
     */
    public String getVariableName() {
        return variableName;
    }
    /**
     * Attaches a variable name to this node, used by JavaWriter
     */
    public void setVariableName(String name) {
        this.variableName = name;
    }

    public String getJavaCode() {
        switch (getObjectType()) {
            case DOUBLE:
                return String.valueOf(value);
            case BOOLEAN:
                return value == 1 ? "true" : "false";
            case INT:
                return String.valueOf((int) value);
            default:
                // should not get here
                System.err.println("Missing type in ParseableERC, getJavaCode: " + getObjectType() + ", " + getClass());
                return getValue();
        }
    }

    public String getLineComment() {
        return null;
    }

    public void setType(int type) {
        this.type = type;
    }

    // ECJ ERC stuff below
    // This is all pretty much boilerplate code and you'll rarely need to look at it,
    // much less repeat it in every ERC class you write!

    public void resetNode(final EvolutionState state, final int thread) {
        value = setNumber(state, thread);
    }

    public boolean nodeEquals(final GPNode node) {
        // check first to see if we're the same kind of ERC --
        // won't work for subclasses; in that case you'll need
        // to change this to isAssignableTo(...)
        if (this.getClass() != node.getClass()) return false;
        // now check to see if the ERCs hold the same value
        return (((ParseableERC) node).value == value);
    }

    public void readNode(final EvolutionState state, final DataInput dataInput) throws IOException {
        value = dataInput.readDouble();
    }

    public void writeNode(final EvolutionState state, final DataOutput dataOutput) throws IOException {
        dataOutput.writeDouble(value);
    }

    public String encode() {
        return Code.encode(value);
    }

    public boolean decode(DecodeReturn dret) {
        // store the position and the string in case they
        // get modified by Code.java
        int pos = dret.pos;
        String data = dret.data;

        // decode
        Code.decode(dret);

        if (dret.type != DecodeReturn.T_DOUBLE) // uh oh!
        {
            // restore the position and the string; it was an error
            dret.data = data;
            dret.pos = pos;
            return false;
        }

        // store the data
        value = dret.d;
        return true;
    }

    public String toStringForHumans() {
        return "" + value;
    }

    public void eval(final EvolutionState state, final int thread, final GPData input, final ADFStack stack, final GPIndividual individual, final Problem problem) {
        DoubleData rd = ((DoubleData) (input));
        rd.x = value;
    }

    public int nodeHashCode() {
        // a reasonable hash code
        return this.getClass().hashCode() + Float.floatToIntBits((float) value);
    }

}
