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

package Mini;

import org.apache.bcel.Const;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.INVOKESTATIC;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.Type;
import org.apache.commons.lang3.ArrayUtils;

/**
 */
public class ASTFunAppl extends ASTExpr {
    public static Node jjtCreate(final MiniParser p, final int id) {
        return new ASTFunAppl(p, id);
    }

    private ASTIdent name;

    private Function function; // Points to Function in environment

    ASTFunAppl(final ASTIdent name, final Function function, final ASTExpr[] exprs) {
        this(JJTFUNAPPL);

        this.name = name;
        this.function = function;
        this.exprs = exprs;
    }

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

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

    /**
     * Fifth pass, produce Java byte code.
     */
    @Override
    public void byte_code(final InstructionList il, final MethodGen method, final ConstantPoolGen cp) {
        final String fname = name.getName();
//    Function   f     = function;
        // ASTIdent fun = f.getName();
//    ASTIdent[] args  = f.getArgs();
        final String className = method.getClassName();

        if (fname.equals("READ")) {
            il.append(new INVOKESTATIC(cp.addMethodref(className, "_readInt", "()I")));
        } else if (fname.equals("WRITE")) {
            exprs[0].byte_code(il, method, cp);
            ASTFunDecl.pop();
            il.append(new INVOKESTATIC(cp.addMethodref(className, "_writeInt", "(I)I")));
        } else { // Normal function
            final int size = ArrayUtils.getLength(exprs);
            Type[] argv = null;

            if (exprs != null) {
                argv = new Type[size];

                for (int i = 0; i < size; i++) {
                    argv[i] = Type.INT;
                    exprs[i].byte_code(il, method, cp);
                }

                // ASTFunDecl.push(size);
            }

            ASTFunDecl.pop(size);

            // Function call
            il.append(new INVOKESTATIC(cp.addMethodref(className, fname, Type.getMethodSignature(Type.INT, argv))));
        }

        ASTFunDecl.push();
    }

    /**
     * Overrides ASTExpr.closeNode()
     */
    @Override
    public void closeNode() {
        name = (ASTIdent) children[0];

        if (children.length > 1) {
            exprs = new ASTExpr[children.length - 1];
            System.arraycopy(children, 1, exprs, 0, children.length - 1);
        }

        children = null; // Throw away old reference
    }

    /**
     * Fourth pass, produce Java code.
     */
    @Override
    public void code(final StringBuffer buf) {
        final String fname = name.getName();
//    Function   f     = function;
//    ASTIdent[] args  = f.getArgs();

        if (fname.equals("READ")) {
            ASTFunDecl.push(buf, "_readInt()");
        } else if (fname.equals("WRITE")) {
            exprs[0].code(buf);
            ASTFunDecl.push(buf, "_writeInt(" + ASTFunDecl.pop() + ")");
        } else { // Normal function
            if (exprs != null) { // Output in reverse odrder
                for (int i = exprs.length - 1; i >= 0; i--) {
                    exprs[i].code(buf);
                }
            }

            final StringBuilder call = new StringBuilder(fname + "(");
            // Function call

            if (exprs != null) {
                for (int i = 0; i < exprs.length; i++) {
                    call.append(ASTFunDecl.pop());
                    if (i < exprs.length - 1) {
                        call.append(", ");
                    }
                }
            }
            call.append(")");

            ASTFunDecl.push(buf, call.toString());
        }
    }

    /**
     * Second pass Overrides AstExpr.eval()
     *
     * @return type of expression.
     * @param expected type.
     */
    @Override
    public int eval(final int expected) {
        final String fname = name.getName();
        final Function f = function;
        final ASTIdent fun = f.getName();
        final ASTIdent[] args = f.getArgs();
        int t = fun.getType();

        is_simple = true; // Only true if all arguments are simple expressions

        // Check arguments
        if (exprs != null) {
            for (int i = 0; i < exprs.length; i++) { // length match checked in previous pass
                final int expect = args[i].getType(); // May be T_UNKNOWN
                final int t_e = exprs[i].eval(expect); // May be T_UNKNOWN

                if (expect != Const.T_UNKNOWN && t_e != expect) {
                    MiniC.addError(exprs[i].getLine(), exprs[i].getColumn(), "Argument " + (i + 1) + " in application of " + fname + " is not of type "
                            + Const.getTypeName(expect) + " but " + Const.getTypeName(t_e));
                } else {
                    args[i].setType(t_e); // Update, may be identical
                }

                is_simple = is_simple && exprs[i].isSimple(); // Check condition
            }
        }

        if (t == Const.T_UNKNOWN) {
            fun.setType(t = expected); // May be still T_UNKNOWN
        }

        return type = t;
    }

    public Function getFunction() {
        return function;
    }

    // dump() inherited
    public ASTIdent getName() {
        return name;
    }

    @Override
    public String toString() {
        return jjtNodeName[id] + " " + name.getName();
    }

    /**
     * Overrides ASTExpr.traverse()
     */
    @Override
    public ASTExpr traverse(final Environment env) {
        final String fname = name.getName();
        final EnvEntry entry = env.get(fname);

        this.env = env;

        if (entry == null) {
            MiniC.addError(name.getLine(), name.getColumn(), "Applying unknown function " + fname + ".");
        } else if (!(entry instanceof Function)) {
            MiniC.addError(name.getLine(), name.getColumn(), "Applying non-function " + fname + ".");
        } else {
            final int len = exprs != null ? exprs.length : 0;
            final Function fun = (Function) entry;

            if (len != fun.getNoArgs()) {
                MiniC.addError(name.getLine(), name.getColumn(), "Function " + fname + " expects " + fun.getNoArgs() + " arguments, you supplied " + len + ".");
            } else { // Adjust references
                function = fun;
                name = fun.getName();
            }
        }

        if (exprs != null) {
            for (int i = 0; i < exprs.length; i++) {
                exprs[i] = exprs[i].traverse(env);
            }
        }

        return this;
    }
}
