/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
/* Generated By:JJTree: Do not edit this line. ASTFunDecl.java */
/* JJT: 0.3pre1 */

package Mini;

import java.io.PrintWriter;
import java.util.Iterator;

import org.apache.bcel.generic.ALOAD;
import org.apache.bcel.generic.ASTORE;
import org.apache.bcel.generic.ArrayType;
import org.apache.bcel.generic.BranchHandle;
import org.apache.bcel.generic.BranchInstruction;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.GETSTATIC;
import org.apache.bcel.generic.GOTO;
import org.apache.bcel.generic.INVOKEVIRTUAL;
import org.apache.bcel.generic.InstructionConstants;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.InstructionTargeter;
import org.apache.bcel.generic.LocalVariableGen;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.TargetLostException;
import org.apache.bcel.generic.Type;
import org.apache.bcel.util.InstructionFinder;

/**
 *
 */
public class ASTFunDecl extends SimpleNode implements MiniParserTreeConstants, org.apache.bcel.Constants {
    private ASTIdent name;
    private ASTIdent[] argv;
    private ASTExpr body;
    private int type = T_UNKNOWN;
    private int line, column;
    private boolean is_simple; // true, if simple expression like `12 + f(a)'
    private boolean is_recursive; // Not used yet, TODO
//  private int         max_depth; // max. expression tree depth
    private Environment env;

    // Generated methods
    ASTFunDecl(final int id) {
        super(id);
    }

    ASTFunDecl(final MiniParser p, final int id) {
        super(p, id);
    }

    public static Node jjtCreate(final MiniParser p, final int id) {
        return new ASTFunDecl(p, id);
    }

    ASTFunDecl(final ASTIdent name, final ASTIdent[] argv, final ASTExpr body, final int type) {
        this(JJTFUNDECL);

        this.name = name;
        this.argv = argv;
        this.body = body;
        this.type = type;
    }

    /**
     * Overrides SimpleNode.closeNode() Cast children to appropiate type.
     */
    @Override
    public void closeNode() {
        name = (ASTIdent) children[0];
        body = (ASTExpr) children[children.length - 1];

        argv = new ASTIdent[children.length - 2]; // May be 0-size array
        for (int i = 1; i < children.length - 1; i++) {
            argv[i - 1] = (ASTIdent) children[i];
        }

        children = null; // Throw away old reference
    }

    /**
     * First pass of parse tree.
     */
    public ASTFunDecl traverse(final Environment env) {
        this.env = env;

        // Put arguments into hash table aka environment
        for (ASTIdent element : argv) {
            final EnvEntry entry = env.get(element.getName());

            if (entry != null) {
                MiniC.addError(element.getLine(), element.getColumn(), "Redeclaration of " + entry + ".");
            } else {
                env.put(new Variable(element));
            }
        }

        /*
         * Update entry of this function, i.e. set argument references. The entry is already in there by garantuee, but
         * may be of wrong type, i.e. the user defined a function `TRUE', e.g. and `TRUE' is of type `Variable'.
         */
        try {
            final Function fun = (Function) env.get(name.getName());
            fun.setArgs(argv);
        } catch (final ClassCastException e) {
        } // Who cares?

        body = body.traverse(env); // Traverse expression body

        return this;
    }

    /**
     * Second pass
     *
     * @return type of expression
     */
    public int eval(final int pass) {
        final int expected = name.getType(); // Maybe other function has already called us
        type = body.eval(expected); // And updated the env

        if ((expected != T_UNKNOWN) && (type != expected)) {
            MiniC.addError(line, column, "Function f ist not of type " + TYPE_NAMES[expected]
                + " as previously assumed, but " + TYPE_NAMES[type]);
        }

        name.setType(type);

        is_simple = body.isSimple();

        if (pass == 2 && type == T_UNKNOWN) {
            is_recursive = true;
        }

        return type;
    }

    /**
     * Fourth pass, produce Java code.
     */
    public void code(final PrintWriter out) {
        String expr;
        boolean main = false, ignore = false;

        final String fname = name.getName();

        if (fname.equals("main")) {
            out.println("  public static void main(String[] _argv) {");
            main = true;
        } else if (fname.equals("READ") || fname.equals("WRITE")) { // Do nothing
            ignore = true;
        } else {
            out.print("  public static final " + "int" + // type_names[type] +
                " " + fname + "(");

            for (int i = 0; i < argv.length; i++) {
                out.print("int " + argv[i].getName());

                if (i < argv.length - 1) {
                    out.print(", ");
                }
            }

            out.println(")\n    throws IOException\n  {");
        }

        if (!ignore) {
            final StringBuffer buf = new StringBuffer();

            body.code(buf);
            out.println(getVarDecls());

            expr = buf.toString();

            if (main) {
                out.println("    try {");
            }

            out.println(expr);

            if (main) {
                out.println("    } catch(Exception e) { System.err.println(e); }\n  }\n");
            } else {
                out.println("\n    return " + pop() + ";\n  }\n");
            }
        }

        reset();
    }

    /**
     * Fifth pass, produce Java byte code.
     */
    public void byte_code(final ClassGen class_gen, final ConstantPoolGen cp) {
        MethodGen method = null;
        boolean main = false, ignore = false;
        final String class_name = class_gen.getClassName();
        final String fname = name.getName();
        final InstructionList il = new InstructionList();

        Type[] args = { new ArrayType(Type.STRING, 1) }; // default for `main'
        String[] arg_names = { "$argv" };

        if (fname.equals("main")) {
            method = new MethodGen(ACC_STATIC | ACC_PUBLIC, Type.VOID, args, arg_names, "main", class_name, il, cp);

            main = true;
        } else if (fname.equals("READ") || fname.equals("WRITE")) { // Do nothing
            ignore = true;
        } else {
            final int size = argv.length;

            arg_names = new String[size];
            args = new Type[size];

            for (int i = 0; i < size; i++) {
                args[i] = Type.INT;
                arg_names[i] = argv[i].getName();
            }

            method = new MethodGen(ACC_STATIC | ACC_PRIVATE | ACC_FINAL, Type.INT, args, arg_names, fname, class_name,
                il, cp);

            final LocalVariableGen[] lv = method.getLocalVariables();
            for (int i = 0; i < size; i++) {
                final Variable entry = (Variable) env.get(arg_names[i]);
                entry.setLocalVariable(lv[i]);
            }

            method.addException("java.io.IOException");
        }

        if (!ignore) {
            body.byte_code(il, method, cp);

            if (main) {
                final ObjectType e_type = new ObjectType("java.lang.Exception");
                final InstructionHandle start = il.getStart();
                InstructionHandle end, handler, end_handler;
                final LocalVariableGen exc = method.addLocalVariable("$e", e_type, null, null);
                final int slot = exc.getIndex();

                il.append(InstructionConstants.POP);
                pop(); // Remove last element on stack
                end = il.append(InstructionConstants.RETURN); // Use instruction constants, if possible

                // catch
                handler = il.append(new ASTORE(slot)); // save exception object
                il.append(new GETSTATIC(cp.addFieldref("java.lang.System", "err", "Ljava/io/PrintStream;")));
                il.append(new ALOAD(slot));
                push(2);
                il.append(
                    new INVOKEVIRTUAL(cp.addMethodref("java.io.PrintStream", "println", "(Ljava/lang/Object;)V")));
                pop(2);
                end_handler = il.append(InstructionConstants.RETURN);
                method.addExceptionHandler(start, end, handler, e_type);
                exc.setStart(handler);
                exc.setEnd(end_handler);
            } else {
                il.append(InstructionConstants.IRETURN); // Reuse object to save memory
            }

            method.removeNOPs(); // First optimization pass, provided by MethodGen
            optimizeIFs(il); // Second optimization pass, application-specific
            method.setMaxStack(max_size);
            class_gen.addMethod(method.getMethod());
        }

        il.dispose(); // Dispose instruction handles for better memory utilization

        reset();
    }

    private static final InstructionFinder.CodeConstraint my_constraint = match -> {
        final BranchInstruction if_icmp = (BranchInstruction) match[0].getInstruction();
        final GOTO goto_ = (GOTO) match[2].getInstruction();
        return (if_icmp.getTarget() == match[3]) && (goto_.getTarget() == match[4]);
    };

    /**
     * Replaces instruction sequences (typically generated by ASTIfExpr) of the form
     *
     * IF_ICMP__, ICONST_1, GOTO, ICONST_0, IFEQ, Instruction
     *
     * where the IF_ICMP__ branches to the ICONST_0 (else part) and the GOTO branches to the IFEQ with the simpler
     * expression
     *
     * IF_ICMP__, Instruction
     *
     * where the IF_ICMP__ now branches to the target of the previous IFEQ instruction.
     */
    private static void optimizeIFs(final InstructionList il) {
        final InstructionFinder f = new InstructionFinder(il);
        final String pat = "IF_ICMP ICONST_1 GOTO ICONST_0 IFEQ Instruction";

        for (final Iterator<InstructionHandle[]> it = f.search(pat, my_constraint); it.hasNext();) {
            final InstructionHandle[] match = it.next();
            // Everything ok, update code
            final BranchInstruction ifeq = (BranchInstruction) (match[4].getInstruction());
            final BranchHandle if_icmp = (BranchHandle) match[0];

            if_icmp.setTarget(ifeq.getTarget());

            try {
                il.delete(match[1], match[4]);
            } catch (final TargetLostException e) {
                final InstructionHandle[] targets = e.getTargets();

                System.err.println(targets[0]);

                for (InstructionHandle target : targets) {
                    final InstructionTargeter[] targeters = target.getTargeters();

                    for (InstructionTargeter targeter : targeters) {
                        if ((target != match[4]) || (targeter != match[2])) {
                            System.err.println("Unexpected: " + e);
                        }
                    }
                }
            }
        }
    }

    /**
     * Overrides SimpleNode.toString()
     */
    @Override
    public String toString() {
        final StringBuffer buf = new StringBuffer();
        buf.append(jjtNodeName[id] + " " + name + "(");

        for (int i = 0; i < argv.length; i++) {
            buf.append(argv[i].getName());
            if (i < argv.length - 1) {
                buf.append(", ");
            }
        }

        buf.append(")");
        return buf.toString();
    }

    public boolean isrecursive() {
        return is_recursive;
    }

    public boolean isSimple() {
        return is_simple;
    }

    public ASTIdent getName() {
        return name;
    }

    public int getNoArgs() {
        return argv.length;
    }

    public ASTIdent[] getArgs() {
        return argv;
    }

    public int getType() {
        return type;
    }

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

    public void setLine(final int line) {
        this.line = line;
    }

    public int getLine() {
        return line;
    }

    public void setColumn(final int column) {
        this.column = column;
    }

    public int getColumn() {
        return column;
    }

    public void setPosition(final int line, final int column) {
        this.line = line;
        this.column = column;
    }

    /**
     * Overrides SimpleNode.dump()
     */
    @Override
    public void dump(final String prefix) {
        System.out.println(toString(prefix));

        for (ASTIdent element : argv) {
            element.dump(prefix + " ");
        }

        body.dump(prefix + " ");
    }

    /*
     * Used to simpulate stack with local vars and compute maximum stack size.
     */
    static int size, max_size;

    static void reset() {
        size = max_size = 0;
    }

    private static String getVarDecls() {
        final StringBuffer buf = new StringBuffer("    int ");

        for (int i = 0; i < max_size; i++) {
            buf.append("_s" + i);

            if (i < max_size - 1) {
                buf.append(", ");
            }
        }

        buf.append(";\n");
        return buf.toString();
    }

    /**
     * Used by byte_code()
     */
    static void pop(final int s) {
        size -= s;
    }

    static void push(final int s) {
        size += s;

        if (size > max_size) {
            max_size = size;
        }
    }

    static void push() {
        push(1);
    }

    /**
     * Used byte code()
     */
    static void push(final StringBuffer buf, final String str) {
        buf.append("    _s" + size + " = " + str + ";\n");
        push(1);
    }

    static String pop() {
        return "_s" + (--size);
    }
}
