package ac.essex.gp.util;

import ac.essex.gp.individuals.Individual;
import ac.essex.gp.tree.Node;
import ac.essex.gp.params.NodeParams;
import ac.essex.gp.nodes.ADFNode;

/**
 * Converts a GP individual, that is a tree of executable nodes into
 * Java code that can be written out as a class. As well as having obvious
 * speed benefits over running a virtualised programm as a tree, it allows
 * individuals to be easily used in applications outside a Genetic Programming
 * context.
 *
 * @author Olly Oechsle, University of Essex, Date: 15-Jan-2007
 * @version 1.0
 */
public class JavaWriter {

    public static final int RETURN_VALUE = 1;
    public static final int STATEMENT = 2;
    public static final int EXPRESSION = 3;

    private static int nameCounter;

    /**
     * Prints the individual out as a java method.
     */
    public static String toJava(Individual ind) {
        return toJava(ind, "eval");
    }

    /**
     * Converts the individual into a java method. You may optionally set the method name
     */
    public static String toJava(Individual ind, String methodName) {
        // assign variable names to each originalNode
        nameCounter = 0;
        name(ind.getTree());
        StringBuffer buffer = new StringBuffer(1024);
        // find any ADFs in the individual
        for (int i = 0; i < ind.getADFNodes().size(); i++) {
            ADFNode adfNode = ind.getADFNodes().elementAt(i);
            buffer.append("protected ");
            buffer.append(returnTypeToJava(adfNode.getReturnType()));
            buffer.append(" method");
            buffer.append(adfNode.getID());
            buffer.append("()");
            buffer.append(" {\n");
            buffer.append(toJava(adfNode.getTree(), RETURN_VALUE));
            buffer.append("\n}\n\n");
        }
        // print java code
        buffer.append("public ");
        buffer.append(returnTypeToJava(ind.getReturnType()));
        buffer.append(" ");
        buffer.append(methodName);
        buffer.append("() {\n");
        buffer.append(toJava(ind.getTree(), RETURN_VALUE));
        buffer.append("\n}");

        return buffer.toString();

    }

    /**
     * Prints out an ADF Node as a method
     * @return
     */
    public static String toJava(ADFNode adfNode, String comment) {
        // find any ADFs in the individual
        nameCounter = 0;
        name(adfNode.getTree());
        StringBuffer buffer = new StringBuffer(1024);
        if (comment != null) {
            buffer.append("/**\n");
            buffer.append(" * ");
            buffer.append(comment);
            buffer.append("\n */\n");
        }
        buffer.append("protected ");
        buffer.append(returnTypeToJava(adfNode.getReturnType()));
        buffer.append(" method");
        buffer.append(adfNode.getID());
        buffer.append("()");
        buffer.append(" {\n");
        buffer.append(toJava(adfNode.getTree(), RETURN_VALUE));
        buffer.append("\n}\n\n");
        return buffer.toString();
    }

    private static String toJava(Node n, int type) {

        StringBuilder buffer = new StringBuilder(1024);

        // print children first
        for (int i = 0; i < n.countChildren(); i++) {
            buffer.append(toJava(n.child[i], type == EXPRESSION ? EXPRESSION : STATEMENT));
        }

        switch (type) {
            case RETURN_VALUE:
                // insert a return statement
                if (n.getReturnType() != NodeParams.VOID) {
                    if (n.getReturnType() == NodeParams.SUBSTATEMENT) {
                        buffer.append("    return (int) (");
                    } else {
                        buffer.append("    return ");
                    }
                }
                buffer.append(n.toJava());
                if (n.getReturnType() == NodeParams.SUBSTATEMENT) {
                    buffer.append(")");
                }
                buffer.append(";");
                break;
            case STATEMENT:
                // insert as a statement (var = val)
                if (n.countChildren() > 0) {
                    if (n.getReturnType() != NodeParams.VOID) {
                        buffer.append("    ");
                        buffer.append(returnTypeToJava(n.getReturnType()));
                        buffer.append(' ');
                        if (n.getName() == null) {
                            n.setName("val" + nameCounter);
                            nameCounter++;
                        }
                        buffer.append(n.getName());
                        buffer.append(" = ");
                    }
                    buffer.append(n.toJava());
                    buffer.append(";\n");
                }
                break;
            case EXPRESSION:
                // expression only, without setting a variable
                buffer.append(n.toJava());
        }

        return buffer.toString();

    }

    public static String returnTypeToJava(int returnType) {
        switch (returnType) {
            case NodeParams.BOOLEAN:
                return "boolean";
            default:
                return "double";
        }
    }

    private static void name(Node n) {
        n.setName("node" + nameCounter);
        nameCounter++;
        for (int i = 0; i < n.countChildren(); i++) {
            name(n.child[i]);
        }
    }

}
