package ec.app.semanticGP.func;

import ec.EvolutionState;
import ec.app.semanticGP.func.logic.*;
import ec.app.semanticGP.func.numeric.*;
import ec.app.semanticGP.func.numeric.ERC;
import ec.app.semanticGP.func.numeric.Inv;
import ec.gp.GPFunctionSet;
import ec.gp.GPInitializer;
import ec.gp.GPNode;
import ec.gp.semantic.func.SimpleNodeBase;
import library.generator.TreeNode;
import library.instructions.InstructionBase;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 * Converter from Generator's TreeNode-based program to ECJ's GPNode-based program.
 *
 * @author Tomasz Pawlak
 */
public class TreeConverter implements Serializable {

    private Map<Class<?>, SimpleNodeBase<?>> mapping = new HashMap<Class<?>, SimpleNodeBase<?>>();

    public TreeConverter(final EvolutionState state) {

		/*
         * GPFunctionSet set = new GPFunctionSet(); set.setup(state, new Parameter("gp.fs.0"));
		 */
        Object[] sets = ((GPInitializer) state.initializer).functionSetRepository.values().toArray();
        GPFunctionSet set = (GPFunctionSet) sets[0];

        for (GPNode[] array : set.nodes) {
            for (GPNode gpNode : array) {

                // caution! this ugly if makes mapping between GPNode types and TreeNode types
                if (gpNode instanceof Sum) {
                    this.mapping.put(library.instructions.numeric.Sum.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Sub) {
                    this.mapping.put(library.instructions.numeric.Sub.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Mul) {
                    this.mapping.put(library.instructions.numeric.Mul.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Div) {
                    this.mapping.put(library.instructions.numeric.Div.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Sin) {
                    this.mapping.put(library.instructions.numeric.Sin.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Cos) {
                    this.mapping.put(library.instructions.numeric.Cos.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Exp) {
                    this.mapping.put(library.instructions.numeric.Exp.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Log) {
                    this.mapping.put(library.instructions.numeric.Log.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Inv) {
                    this.mapping.put(library.instructions.numeric.Inv.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Neg) {
                    this.mapping.put(library.instructions.numeric.Neg.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Sqrt) {
                    this.mapping.put(library.instructions.numeric.Sqrt.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Zero) {
                    this.mapping.put(library.instructions.numeric.Zero.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof One) {
                    this.mapping.put(library.instructions.numeric.One.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof ERC) {
                    //just ignore, there is no ERC in library
                } else if (gpNode instanceof X0) {
                    this.mapping.put(library.instructions.numeric.X0.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof X1) {
                    this.mapping.put(library.instructions.numeric.X1.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof X2) {
                    this.mapping.put(library.instructions.numeric.X2.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof X3) {
                    this.mapping.put(library.instructions.numeric.X3.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof X4) {
                    this.mapping.put(library.instructions.numeric.X4.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof X5) {
                    this.mapping.put(library.instructions.numeric.X5.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof X6) {
                    this.mapping.put(library.instructions.numeric.X6.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof X7) {
                    this.mapping.put(library.instructions.numeric.X7.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof And) {
                    this.mapping.put(library.instructions.logic.And.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Or) {
                    this.mapping.put(library.instructions.logic.Or.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Nand) {
                    this.mapping.put(library.instructions.logic.Nand.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Nor) {
                    this.mapping.put(library.instructions.logic.Nor.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Xor) {
                    this.mapping.put(library.instructions.logic.Xor.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof ec.app.semanticGP.func.logic.Inv) {
                    this.mapping.put(library.instructions.logic.Inv.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof True) {
                    this.mapping.put(library.instructions.logic.True.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof False) {
                    this.mapping.put(library.instructions.logic.False.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof ec.app.semanticGP.func.logic.ERC) {
                    //just ignore, there is no ERC in library
                } else if (gpNode instanceof Input0) {
                    this.mapping.put(library.instructions.logic.Input0.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input1) {
                    this.mapping.put(library.instructions.logic.Input1.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input2) {
                    this.mapping.put(library.instructions.logic.Input2.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input3) {
                    this.mapping.put(library.instructions.logic.Input3.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input4) {
                    this.mapping.put(library.instructions.logic.Input4.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input5) {
                    this.mapping.put(library.instructions.logic.Input5.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input6) {
                    this.mapping.put(library.instructions.logic.Input6.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input7) {
                    this.mapping.put(library.instructions.logic.Input7.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input8) {
                    this.mapping.put(library.instructions.logic.Input8.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input9) {
                    this.mapping.put(library.instructions.logic.Input9.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input10) {
                    this.mapping.put(library.instructions.logic.Input10.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input11) {
                    this.mapping.put(library.instructions.logic.Input11.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input12) {
                    this.mapping.put(library.instructions.logic.Input12.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input13) {
                    this.mapping.put(library.instructions.logic.Input13.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input14) {
                    this.mapping.put(library.instructions.logic.Input14.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input15) {
                    this.mapping.put(library.instructions.logic.Input15.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input16) {
                    this.mapping.put(library.instructions.logic.Input16.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input17) {
                    this.mapping.put(library.instructions.logic.Input17.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input18) {
                    this.mapping.put(library.instructions.logic.Input18.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input19) {
                    this.mapping.put(library.instructions.logic.Input19.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input20) {
                    this.mapping.put(library.instructions.logic.Input20.class, (SimpleNodeBase<?>) gpNode);
                } else if (gpNode instanceof Input21) {
                    this.mapping.put(library.instructions.logic.Input21.class, (SimpleNodeBase<?>) gpNode);
                } else {
                    throw new RuntimeException("Unknown instruction type: " + gpNode);
                }
            }
        }
    }

    public SimpleNodeBase<?> convert(final TreeNode source) {
        // map current TreeNode to GPNode
        InstructionBase<?> instruction = source.getInstruction();
        SimpleNodeBase<?> copy = this.mapping.get(instruction.getClass());
        copy = (SimpleNodeBase<?>) copy.lightClone();
        copy.resetSemantics();

        TreeNode[] children = source.getChildren();

        assert copy.children.length == instruction.getNumberOfArguments();
        assert children.length == copy.children.length;

        for (int i = 0; i < children.length; ++i) {
            copy.children[i] = this.convert(children[i]);
            copy.children[i].argposition = (byte) i;
            copy.children[i].parent = copy;
        }

        return copy;
    }

}
