/*
 * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package vm.runtime.defmeth.shared.data.method.body;

import vm.runtime.defmeth.shared.data.method.param.Param;
import jdk.internal.org.objectweb.asm.Opcodes;
import vm.runtime.defmeth.shared.data.Visitor;
import vm.runtime.defmeth.shared.data.Clazz;
import vm.runtime.defmeth.shared.data.Interface;

/**
 * Represents arbitrary method call (invoke*).
 */
public class CallMethod implements MethodBody {

    /** Invocation byte code instruction */
    public static enum Invoke {
        VIRTUAL(   Opcodes.INVOKEVIRTUAL,   "INVOKEVIRTUAL",   5),
        INTERFACE( Opcodes.INVOKEINTERFACE, "INVOKEINTERFACE", 9),
        SPECIAL(   Opcodes.INVOKESPECIAL,   "INVOKESPECIAL",   7),
        STATIC(    Opcodes.INVOKESTATIC,    "INVOKESTATIC",    6);

        private final int opcode;
        private final String name;

        // Kind  Description           Interpretation
        //    1   REF_getField,         getfield C.f:T
        //    2   REF_getStatic         getstatic C.f:T
        //    3   REF_putField          putfield C.f:T
        //    4   REF_putStatic         putstatic C.f:T
        //    5   REF_invokeVirtual     invokevirtual C.m:(A*)T
        //    6   REF_invokeStatic      invokestatic C.m:(A*)T
        //    7   REF_invokeSpecial     invokespecial C.m:(A*)T
        //    8   REF_newInvokeSpecial  new C; dup; invokespecial C.<init>:(A*)void
        //    9   REF_invokeInterface   invokeinterface C.m:(A*)T
        private final int tag;

        Invoke(int opcode, String name, int tag) {
            this.opcode = opcode;
            this.name = name;
            this.tag = tag;
        }

        public int opcode() {
            return opcode;
        }

        @Override
        public String toString() {
            return name;
        }

        public int tag() {
            return tag;
        }
    }

    /** At callsite direct visitMethodInsn() to issue a CONSTANT_Methodref,
        a CONSTANT_InterfaceMethodref or let it be determined at the
        callsite if the callee is an instance of an Interface */
    public static enum IndexbyteOp {
        METHODREF,
        INTERFACEMETHODREF,
        CALLSITE
    }

    /** Invoke instruction which should be used for the call */
    final Invoke invokeInsn;

    /** Static receiver class */
    final Clazz staticClass;

    /** Dynamic receiver class */
    final Clazz receiverClass;

    /** Name of the method to be invoked*/
    final String methodName;

    /** Descriptor of the method to be invoked */
    final String methodDesc;

    /** Parameter values */
    final Param[] params;

    /** Name of method's return type */
    final String returnType;

    /** Should return value be popped off the stack after the call */
    final boolean popReturnValue;

    /** Indexbyte operand to generate at call site */
    final IndexbyteOp generateIndexbyteOp;

    public CallMethod(Invoke invokeInsn, Clazz staticClass, Clazz receiverClass,
                      String methodName, String methodDesc, Param[] params,
                      String returnType, boolean popReturnValue,
                      IndexbyteOp generateIndexbyteOp) {
        this.invokeInsn = invokeInsn;
        this.staticClass = staticClass;
        this.receiverClass = receiverClass;
        this.methodName = methodName;
        this.methodDesc = methodDesc;
        this.params = params;
        this.returnType = returnType;
        this.popReturnValue = popReturnValue;
        this.generateIndexbyteOp = generateIndexbyteOp;
    }

    public boolean popReturnValue() {
        return popReturnValue;
    }

    public IndexbyteOp generateIndexbyteOp() {
        return generateIndexbyteOp;
    }

    public Invoke invokeInsn() {
        return invokeInsn;
    }

    public Clazz staticClass() {
        return staticClass;
    }

    public Clazz receiverClass() {
        return receiverClass;
    }

    public String methodName() {
        return methodName;
    }

    public String methodDesc() {
        return methodDesc;
    }

    public Param[] params() {
        return params;
    }

    public String returnType() {
        return returnType;
    }

    public boolean isInterface() {
        return generateIndexbyteOp() == IndexbyteOp.METHODREF ?
                     false :
                     (generateIndexbyteOp() == IndexbyteOp.INTERFACEMETHODREF ?
                         true :
                         staticClass() instanceof Interface);
    }

    @Override
    public void visit(Visitor v) {
        v.visitCallMethod(this);
    }
}
