/*
 * 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
 *
 *   https://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. ASTProgram.java */
/* JJT: 0.3pre1 */

package Mini;

import java.io.PrintWriter;

import org.apache.bcel.Const;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.generic.ALOAD;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.GETSTATIC;
import org.apache.bcel.generic.ILOAD;
import org.apache.bcel.generic.INVOKESPECIAL;
import org.apache.bcel.generic.INVOKESTATIC;
import org.apache.bcel.generic.INVOKEVIRTUAL;
import org.apache.bcel.generic.InstructionConst;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.NEW;
import org.apache.bcel.generic.PUSH;
import org.apache.bcel.generic.PUTSTATIC;
import org.apache.bcel.generic.RETURN;
import org.apache.bcel.generic.Type;

/**
 * Root node of everything, direct children are nodes of type FunDecl.
 */
public class ASTProgram extends SimpleNode implements MiniParserConstants, MiniParserTreeConstants {
    public static Node jjtCreate(final MiniParser p, final int id) {
        return new ASTProgram(p, id);
    }

    private ASTFunDecl[] fun_decls; // Children: Function declarations

    private Environment env; // Environment contains variables and functions

    ASTProgram(final int id) {
        super(id);

        env = new Environment();

        /*
         * Add predefined functions WRITE/READ. WRITE has one arg of type T_INT, both return T_INT.
         */
        ASTIdent ident = new ASTIdent("WRITE", Const.T_INT, -1, -1);
        ASTIdent[] args = {new ASTIdent("", Const.T_INT, -1, -1)};
        Function fun = new Function(ident, args, true);
        env.put(fun);

        ident = new ASTIdent("READ", Const.T_INT, -1, -1);
        args = new ASTIdent[0];
        fun = new Function(ident, args, true);
        env.put(fun);

        /*
         * Add predefined idents TRUE/FALSE of type T_BOOLEAN
         */
        ident = new ASTIdent("TRUE", Const.T_BOOLEAN, -1, -1);
        Variable var = new Variable(ident, true);
        env.put(var);

        ident = new ASTIdent("FALSE", Const.T_BOOLEAN, -1, -1);
        var = new Variable(ident, true);
        env.put(var);
    }

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

    /**
     * Fifth pass, produce Java byte code.
     */
    public void byte_code(final ClassGen classGen, final ConstantPoolGen cp) {
        /*
         * private static BufferedReader _in;
         */
        classGen.addField(
                new Field(Const.ACC_PRIVATE | Const.ACC_STATIC, cp.addUtf8("_in"), cp.addUtf8("Ljava/io/BufferedReader;"), null, cp.getConstantPool()));

        MethodGen method;
        InstructionList il = new InstructionList();
        final String className = classGen.getClassName();

        /*
         * Often used constant pool entries
         */
        final int _in = cp.addFieldref(className, "_in", "Ljava/io/BufferedReader;");

        final int out = cp.addFieldref("java.lang.System", "out", "Ljava/io/PrintStream;");

        il.append(new GETSTATIC(out));
        il.append(new PUSH(cp, "Please enter a number> "));
        il.append(new INVOKEVIRTUAL(cp.addMethodref("java.io.PrintStream", "print", "(Ljava/lang/String;)V")));
        il.append(new GETSTATIC(_in));
        il.append(new INVOKEVIRTUAL(cp.addMethodref("java.io.BufferedReader", "readLine", "()Ljava/lang/String;")));
        il.append(new INVOKESTATIC(cp.addMethodref("java.lang.Integer", "parseInt", "(Ljava/lang/String;)I")));
        il.append(InstructionConst.IRETURN);

        /*
         * private static int _readInt() throws IOException
         */
        method = new MethodGen(Const.ACC_STATIC | Const.ACC_PRIVATE | Const.ACC_FINAL, Type.INT, Type.NO_ARGS, null, "_readInt", className, il, cp);

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

        method.setMaxStack(2);
        classGen.addMethod(method.getMethod());

        /*
         * private static int _writeInt(int i) throws IOException
         */
        final Type[] args = {Type.INT};
        final String[] argv = {"i"};
        il = new InstructionList();
        il.append(new GETSTATIC(out));
        il.append(new NEW(cp.addClass("java.lang.StringBuffer")));
        il.append(InstructionConst.DUP);
        il.append(new PUSH(cp, "Result: "));
        il.append(new INVOKESPECIAL(cp.addMethodref("java.lang.StringBuffer", "<init>", "(Ljava/lang/String;)V")));

        il.append(new ILOAD(0));
        il.append(new INVOKEVIRTUAL(cp.addMethodref("java.lang.StringBuffer", "append", "(I)Ljava/lang/StringBuffer;")));

        il.append(new INVOKEVIRTUAL(cp.addMethodref("java.lang.StringBuffer", "toString", "()Ljava/lang/String;")));

        il.append(new INVOKEVIRTUAL(cp.addMethodref("java.io.PrintStream", "println", "(Ljava/lang/String;)V")));
        il.append(new PUSH(cp, 0));
        il.append(InstructionConst.IRETURN); // Reuse objects, if possible

        method = new MethodGen(Const.ACC_STATIC | Const.ACC_PRIVATE | Const.ACC_FINAL, Type.INT, args, argv, "_writeInt", className, il, cp);

        method.setMaxStack(4);
        classGen.addMethod(method.getMethod());

        /*
         * public <init> -- constructor
         */
        il.dispose(); // Dispose instruction handles for better memory utilization

        il = new InstructionList();
        il.append(new ALOAD(0)); // Push 'this'
        il.append(new INVOKESPECIAL(cp.addMethodref("java.lang.Object", "<init>", "()V")));
        il.append(new RETURN());

        method = new MethodGen(Const.ACC_PUBLIC, Type.VOID, Type.NO_ARGS, null, "<init>", className, il, cp);

        method.setMaxStack(1);
        classGen.addMethod(method.getMethod());

        /*
         * class initializer
         */
        il.dispose(); // Dispose instruction handles for better memory utilization
        il = new InstructionList();
        il.append(new NEW(cp.addClass("java.io.BufferedReader")));
        il.append(InstructionConst.DUP);
        il.append(new NEW(cp.addClass("java.io.InputStreamReader")));
        il.append(InstructionConst.DUP);
        il.append(new GETSTATIC(cp.addFieldref("java.lang.System", "in", "Ljava/io/InputStream;")));
        il.append(new INVOKESPECIAL(cp.addMethodref("java.io.InputStreamReader", "<init>", "(Ljava/io/InputStream;)V")));
        il.append(new INVOKESPECIAL(cp.addMethodref("java.io.BufferedReader", "<init>", "(Ljava/io/Reader;)V")));
        il.append(new PUTSTATIC(_in));
        il.append(InstructionConst.RETURN); // Reuse instruction constants

        method = new MethodGen(Const.ACC_STATIC, Type.VOID, Type.NO_ARGS, null, "<clinit>", className, il, cp);

        method.setMaxStack(5);
        classGen.addMethod(method.getMethod());

        for (final ASTFunDecl fun_decl : fun_decls) {
            fun_decl.byte_code(classGen, cp);
        }
    }

    /**
     * Overrides SimpleNode.closeNode(). Cast children to appropriate type.
     */
    @Override
    public void closeNode() {
        if (children != null) { // Non-empty program ?
            fun_decls = new ASTFunDecl[children.length];
            System.arraycopy(children, 0, fun_decls, 0, children.length);
            children = null; // Throw away old reference
        }
    }

    /**
     * Fifth pass, produce Java code.
     */
    public void code(final PrintWriter out, final String name) {
        out.println("import java.io.BufferedReader;");
        out.println("import java.io.InputStreamReader;");
        out.println("import java.io.IOException;\n");

        out.println("public final class " + name + " {");
        out.println("  private static BufferedReader _in = new BufferedReader" + "(new InputStreamReader(System.in));\n");

        out.println("  private static int _readInt() throws IOException {\n" + "    System.out.print(\"Please enter a number> \");\n"
            + "    return Integer.parseInt(_in.readLine());\n  }\n");

        out.println("  private static int _writeInt(int n) {\n" + "    System.out.println(\"Result: \" + n);\n    return 0;\n  }\n");

        for (final ASTFunDecl fun_decl : fun_decls) {
            fun_decl.code(out);
        }

        out.println("}");
    }

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

        for (final ASTFunDecl fun_decl : fun_decls) {
            fun_decl.dump(prefix + " ");
        }
    }

    /**
     * Second pass, determine type of each node, if possible.
     */
    public void eval(final int pass) {

        for (final ASTFunDecl fun_decl : fun_decls) {
            fun_decl.eval(pass);

            if (pass == 3) { // Final check for unresolved types
                final ASTIdent name = fun_decl.getName();

                if (name.getType() == Const.T_UNKNOWN) {
                    MiniC.addError(name.getColumn(), name.getLine(), "Type of function " + name.getName() + " cannot be determined (infinite recursion?).");
                }
            }
        }
    }

    /**
     * First pass of parse tree.
     *
     * Put everything into the environment, which is copied appropriately to each recursion level, for example each FunDecl gets its
     * own copy that it can further manipulate.
     *
     * Checks for name clashes of function declarations.
     */
    public ASTProgram traverse() {
        ASTFunDecl f;
        ASTIdent name;
        String fname;
        EnvEntry fun;
        Function main = null;

        if (fun_decls != null) {
            // Put function names into hash table aka. environment
            for (final ASTFunDecl fun_decl : fun_decls) {
                f = fun_decl;
                name = f.getName();
                fname = name.getName();
                fun = env.get(fname); // Lookup in env

                if (fun != null) {
                    MiniC.addError(f.getLine(), f.getColumn(), "Redeclaration of " + fun + ".");
                } else {
                    env.put(new Function(name, null)); // 'args' will be set by FunDecl.traverse()
                }

            }

            // Go for it
            for (int i = 0; i < fun_decls.length; i++) {
                fun_decls[i] = fun_decls[i].traverse((Environment) env.clone());

                // Look for 'main' routine
                fname = fun_decls[i].getName().getName();
                if (fname.equals("main")) {
                    main = (Function) env.get(fname);
                }
            }

            if (main == null) {
                MiniC.addError(0, 0, "You didn't declare a 'main' function.");
            } else if (main.getNoArgs() != 0) {
                MiniC.addError(main.getLine(), main.getColumn(), "Main function has too many arguments declared.");
            }
        }

        return this;
    }
}
