/*
 * Copyright 2008-2009 Sun Microsystems, Inc.  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.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package impl.java.dyn;

import java.dyn.MethodHandle;
import java.dyn.MethodHandles;
import java.dyn.MethodType;
import impl.java.dyn.JavaMethodHandle;
import impl.java.dyn.util.MethodHandleInvoker;
import java.dyn.NoAccessException;
import static impl.java.dyn.MemberName.newIllegalArgumentException;
import static impl.java.dyn.MemberName.newNoAccessException;

/**
 * Base class for method handles which are known to the Hotspot JVM.
 * @author jrose
 */
public abstract class MethodHandleImpl {

    // Fields in MethodHandle:
    private byte       vmentry;    // adapter stub or method entry point
    //private int      vmslots;    // optionally, hoist type.form.vmslots
    protected Object   vmtarget;   // VM-specific, class-specific target value
    //MethodType       type;       // defined in MethodHandle

    // These two dummy fields are present to force 'I' and 'J' signatures
    // into this class's constant pool, so they can be transferred
    // to vmentry when this class is loaded.
    static final int  INT_FIELD = 0;
    static final long LONG_FIELD = 0;

    // type is defined in java.dyn.MethodHandle, which is platform-independent

    // vmentry (a void* field) is used *only* by by the JVM.
    // The JVM adjusts its type to int or long depending on system wordsize.
    // Since it is statically typed as neither int nor long, it is impossible
    // to use this field from Java bytecode.  (Please don't try to, either.)

    // The vmentry is an assembly-language stub which is jumped to
    // immediately after the method type is verified.
    // For a direct MH, this stub loads the vmtarget's entry point
    // and jumps to it.

    /**
     * VM-based method handles must have a security token.
     * This security token can only be obtained by trusted code.
     * Do not create method handles directly; use factory methods.
     */
    public MethodHandleImpl(Access token) {
        Access.check(token);
    }

    /** Initialize the method type form to participate in JVM calls.
     *  This is done once for each erased type.
     */
    public static void init(Access token, MethodType self) {
        Access.check(token);
        if (MethodHandleNatives.JVM_SUPPORT)
            MethodHandleNatives.init(self);
    }

    /// Factory methods to create method handles:

    private static final MemberName.Factory LOOKUP = MemberName.Factory.INSTANCE;


    /** Look up a given method.
     * Callable only from java.dyn and related packages.
     * <p>
     * The resulting method handle type will be of the given type,
     * with a receiver type {@code rcvc} prepended if the member is not static.
     * <p>
     * Access checks are made as of the given caller.
     * In particular, if the method is protected and {@code defc} is in a
     * different package from the caller, then {@code rcvc} must be
     * caller or a subclass.
     * @param token Proof that the caller has access to this package.
     * @param member Resolved method or constructor to call.
     * @param name Name of the desired method.
     * @param rcvc Receiver type of desired non-static method (else null)
     * @param doDispatch whether the method handle will test the receiver type
     * @param caller if not null, access-check relative to this class ????
     * @return a direct handle to the matching method
     * @throws NoAccessException if the given method cannot be accessed by caller
     */
    public static
    MethodHandle findMethod(Access token, MemberName method,
            boolean doDispatch, Class<?> caller) {
        Access.check(token);  // only trusted calls
        MethodType mtype = method.getMethodType();
        if (method.isStatic()) {
            doDispatch = false;
        } else {
            // adjust the advertised receiver type to be exactly the one requested
            // (in the case of invokespecial, this will be the calling class)
            mtype = mtype.insertParameterType(0, method.getDeclaringClass());
            if (method.isConstructor())
                doDispatch = true;
        }
        DirectMethodHandle mh = new DirectMethodHandle(mtype, method, doDispatch, caller);
        if (!mh.isValid())
            throw newNoAccessException(method, caller);
        return mh;
    }

    public static
    MethodHandle accessField(Access token,
                           MemberName member, boolean isSetter,
                           Class<?> caller) {
        Access.check(token);
        // FIXME: Use sun.misc.Unsafe to dig up the dirt on the field.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    public static
    MethodHandle accessArrayElement(Access token,
                           Class<?> arrayClass, boolean isSetter,
                           Class<?> caller) {
        Access.check(token);
        if (!arrayClass.isArray())
            throw newIllegalArgumentException("not an array: "+arrayClass);
        // FIXME: Use sun.misc.Unsafe to dig up the dirt on the array.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    /** Bind a predetermined first argument to the given direct method handle.
     * Callable only from MethodHandles.
     * @param token Proof that the caller has access to this package.
     * @param target Any direct method handle.
     * @param receiver Receiver (or first static method argument) to pre-bind.
     * @return a BoundMethodHandle for the given DirectMethodHandle, or null if it does not exist
     */
    public static
    MethodHandle bindReceiver(Access token,
                              MethodHandle target, Object receiver) {
        Access.check(token);
        if (target instanceof DirectMethodHandle)
            return new BoundMethodHandle((DirectMethodHandle)target, receiver, 0);
        return null;   // let caller try something else
    }

    /** Bind a predetermined argument to the given arbitrary method handle.
     * Callable only from MethodHandles.
     * @param token Proof that the caller has access to this package.
     * @param target Any method handle.
     * @param receiver Argument (which can be a boxed primitive) to pre-bind.
     * @return a suitable BoundMethodHandle
     */
    public static
    MethodHandle bindArgument(Access token,
                              MethodHandle target, int argnum, Object receiver) {
        Access.check(token);
        throw new UnsupportedOperationException("NYI");
    }

    public static MethodHandle convertArguments(Access token,
                                                MethodHandle target,
                                                MethodType newType, boolean newVarargs,
                                                MethodType oldType, boolean oldVarargs,
                                                String permutationOrNull) {
        Access.check(token);
        throw new UnsupportedOperationException("Not yet implemented");
    }

    public static
    MethodHandle dropArguments(Access token, MethodHandle target,
                               MethodType newType, int argnum) {
        Access.check(token);
        throw new UnsupportedOperationException("NYI");
    }

    public static
    MethodHandle makeGuardWithTest(Access token,
                                   final MethodHandle test,
                                   final MethodHandle target,
                                   final MethodHandle fallback) {
        Access.check(token);
        // %%% This is just a sketch.  It needs to be de-boxed.
        // Adjust the handles to accept varargs lists.
        MethodType type = target.type();
        Class<?>  rtype = type.returnType();
        if (type.parameterCount() != 1 || type.parameterType(0).isPrimitive()) {
            MethodType vatestType   = MethodType.make(boolean.class, Object[].class);
            MethodType vatargetType = MethodType.make(rtype, Object[].class);
            MethodHandle vaguard = makeGuardWithTest(token,
                    MethodHandles.spreadArguments(test, vatestType),
                    MethodHandles.spreadArguments(target, vatargetType),
                    MethodHandles.spreadArguments(fallback, vatargetType));
            return MethodHandles.collectArguments(vaguard, type);
        }
        if (rtype.isPrimitive()) {
            MethodType boxtype = type.changeReturnType(Object.class);
            MethodHandle boxguard = makeGuardWithTest(token,
                    test,
                    MethodHandles.convertArguments(target, boxtype),
                    MethodHandles.convertArguments(fallback, boxtype));
            return MethodHandles.convertArguments(boxguard, type);
        }
        // Got here?  Reduced calling sequence to Object(Object).
        final MethodHandleInvoker invoke1
                = MethodHandleInvoker.make(test.type());
        final MethodHandleInvoker invoke2
                = MethodHandleInvoker.make(target.type());
        class Guarder {
            Object invoke(Object x) {
                // If javac supports MethodHandle.invoke directly:
                //z = vatest.invoke<boolean>(arguments);
                // If javac does not support direct MH.invoke calls:
                boolean z = (Boolean) invoke1.invoke_1(test, x);
                MethodHandle mh = (z ? target : fallback);
                return invoke2.invoke_1(mh, x);
            }
            MethodHandle handle() {
                MethodType invokeType = MethodType.makeGeneric(0, true);
                MethodHandle vh = MethodHandles.bind(this, "invoke", invokeType);
                return MethodHandles.collectArguments(vh, target.type());
            }
        }
        return new Guarder().handle();
    }

    public static
    MethodHandle checkArguments(Access token, MethodHandle target, MethodHandle checker, int pos) {
        Access.check(token);
        throw new UnsupportedOperationException("Not yet implemented");
    }

    protected static String basicToString(MethodHandle target) {
        MemberName name = MethodHandleNatives.getMethodName(target);
        if (name == null)
            name = new MemberName(null, "<unknown>", target.type());
        return name.toString();
    }

    static RuntimeException newIllegalArgumentException(String string) {
        return new IllegalArgumentException(string);
    }

    @Override
    public String toString() {
        return basicToString((MethodHandle)this);
    }
}
