/*
 * 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.util;

import impl.java.dyn.Access;
import impl.java.dyn.AdapterMethodHandle;
import java.dyn.AnonymousClassLoader;
import java.dyn.ConstantPoolParser;
import java.dyn.ConstantPoolPatch;
import java.dyn.ConstantPoolVisitor;
import java.dyn.InvalidConstantPoolFormatException;
import java.dyn.MethodHandle;
import java.dyn.MethodHandles;
import java.dyn.MethodType;
import java.dyn.WrongMethodTypeException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.IdentityHashMap;

/**
 * Emulation of method handle invocation.
 * Not needed if javac supports direct invocation of MethodHandle.invoke.
 * @author jrose
 */
public abstract
class MethodHandleInvoker implements Cloneable {
    private static final Access IMPL_TOKEN = Access.getToken();


    /** Exact type for all handles targeted by this invoker. */
    protected final MethodType exactType;

    /** Condensed information about the return type, one of "VLIJFDZBSC". */
    protected final char rtypec;

    /** Adapter which converts the approximate type to the exact exactType. */
    protected final MethodHandle adapter;

    /** Maximum number of arguments allowed.
     *  This is an implementation limit.
     */
    public static final int ARGUMENT_MAX;
    /** Maximum number of double or long arguments allowed.
     *  This is an implementation limit.
     */
    public static final int LONG_ARGUMENT_MAX;
    static {
        LONG_ARGUMENT_MAX = 3;  // %%% depends on stack headroom
    }

    public MethodType type() { return exactType; }

    protected MethodHandleInvoker(MethodType exactType, MethodHandle adapter) {
        this.exactType = exactType;
        this.rtypec = Wrappers.basicTypeChar(exactType.returnType());
        this.adapter = adapter;
    }

    static MethodHandle makeAdapter(MethodType exactType, MethodType approxType) {
        // For each argument, convert incoming Object to the exact type needed.
        int len = exactType.parameterCount();
        assert(len == approxType.parameterCount());
        if (exactType.parameterSlotCount() > len + LONG_ARGUMENT_MAX)
            throw new IllegalArgumentException("too many long arguments in "+exactType);
        MethodHandle invoker = MethodHandles.findVirtual(MethodHandle.class, "invoke", exactType);
        MethodType adapterType = approxType.insertParameterType(0, MethodHandle.class);
        return AdapterMethodHandle.makePairwiseConversion(IMPL_TOKEN, adapterType, invoker);
    }

    public Object invoke_0(MethodHandle mh)
        {   throw wrongType(mh);  }
    public Object invoke_1(MethodHandle mh, Object a0)
        {   throw wrongType(mh);  }
    public Object invoke_2(MethodHandle mh, Object a0, Object a1)
        {   throw wrongType(mh);  }
    public Object invoke_3(MethodHandle mh, Object a0, Object a1, Object a2)
        {   throw wrongType(mh);  }
    public Object invoke_4(MethodHandle mh, Object a0, Object a1, Object a2, Object a3)
        {   throw wrongType(mh);  }
    public Object invoke_5(MethodHandle mh, Object a0, Object a1, Object a2, Object a3, Object a4)
        {   throw wrongType(mh);  }
    
    /** Reflective style generic invocation.  This always delegates
     *  to one of the invoke_X methods.
     * @param mh method handle to invoke (must be of exactly correct type)
     * @param args array of arguments to send to method (maybe null if empty)
     * @return
     */
    final // try this...
    public Object invoke(MethodHandle mh, Object ... args) {
        switch (args == null ? 0 : args.length) {
        case 0: return invoke_0(mh);
        case 1: return invoke_1(mh, args[0]);
        case 2: return invoke_2(mh, args[0], args[1]);
        case 3: return invoke_3(mh, args[0], args[1], args[2]);
        case 4: return invoke_4(mh, args[0], args[1], args[2], args[3]);
        case 5: return invoke_5(mh, args[0], args[1], args[2], args[3], args[4]);
        }
        throw wrongType(mh);
    }

    // This class is not used after compile time.
    // It is renamed away to MethodHandle itself, to call the MHI.adapter.
    // TO DO: Update javac so we can call directly to polymorphic MH.invoke.
    private static abstract class FakeMethodHandle extends MethodHandle {
        public FakeMethodHandle() { super(null, null); }
        // here are all the invokes we need to link against:
        public abstract void   fake_invoke_V0(MethodHandle mh);
        public abstract Object fake_invoke_L0(MethodHandle mh);
        public abstract int    fake_invoke_I0(MethodHandle mh);
        public abstract long   fake_invoke_J0(MethodHandle mh);
        public abstract double fake_invoke_F0(MethodHandle mh);
        public abstract double fake_invoke_D0(MethodHandle mh);
        public abstract void   fake_invoke_V1(MethodHandle mh, Object a0);
        public abstract Object fake_invoke_L1(MethodHandle mh, Object a0);
        public abstract int    fake_invoke_I1(MethodHandle mh, Object a0);
        public abstract long   fake_invoke_J1(MethodHandle mh, Object a0);
        public abstract float  fake_invoke_F1(MethodHandle mh, Object a0);
        public abstract double fake_invoke_D1(MethodHandle mh, Object a0);
        public abstract void   fake_invoke_V2(MethodHandle mh, Object a0, Object a1);
        public abstract Object fake_invoke_L2(MethodHandle mh, Object a0, Object a1);
        public abstract int    fake_invoke_I2(MethodHandle mh, Object a0, Object a1);
        public abstract long   fake_invoke_J2(MethodHandle mh, Object a0, Object a1);
        public abstract float  fake_invoke_F2(MethodHandle mh, Object a0, Object a1);
        public abstract double fake_invoke_D2(MethodHandle mh, Object a0, Object a1);
        public abstract void   fake_invoke_V3(MethodHandle mh, Object a0, Object a1, Object a2);
        public abstract Object fake_invoke_L3(MethodHandle mh, Object a0, Object a1, Object a2);
        public abstract int    fake_invoke_I3(MethodHandle mh, Object a0, Object a1, Object a2);
        public abstract long   fake_invoke_J3(MethodHandle mh, Object a0, Object a1, Object a2);
        public abstract float  fake_invoke_F3(MethodHandle mh, Object a0, Object a1, Object a2);
        public abstract double fake_invoke_D3(MethodHandle mh, Object a0, Object a1, Object a2);
        public abstract void   fake_invoke_V4(MethodHandle mh, Object a0, Object a1, Object a2, Object a3);
        public abstract Object fake_invoke_L4(MethodHandle mh, Object a0, Object a1, Object a2, Object a3);
        public abstract int    fake_invoke_I4(MethodHandle mh, Object a0, Object a1, Object a2, Object a3);
        public abstract long   fake_invoke_J4(MethodHandle mh, Object a0, Object a1, Object a2, Object a3);
        public abstract float  fake_invoke_F4(MethodHandle mh, Object a0, Object a1, Object a2, Object a3);
        public abstract double fake_invoke_D4(MethodHandle mh, Object a0, Object a1, Object a2, Object a3);
        public abstract void   fake_invoke_V5(MethodHandle mh, Object a0, Object a1, Object a2, Object a3, Object a4);
        public abstract Object fake_invoke_L5(MethodHandle mh, Object a0, Object a1, Object a2, Object a3, Object a4);
        public abstract int    fake_invoke_I5(MethodHandle mh, Object a0, Object a1, Object a2, Object a3, Object a4);
        public abstract long   fake_invoke_J5(MethodHandle mh, Object a0, Object a1, Object a2, Object a3, Object a4);
        public abstract float  fake_invoke_F5(MethodHandle mh, Object a0, Object a1, Object a2, Object a3, Object a4);
        public abstract double fake_invoke_D5(MethodHandle mh, Object a0, Object a1, Object a2, Object a3, Object a4);
    }
    private static String FMHInvokeName(MethodType approxType) {
        assert(isFMHInvokeType(approxType)) : approxType;
        return "fake_invoke_"
                + Wrappers.basicTypeChar(approxType.returnType())
                + approxType.parameterCount();
    }
    private static boolean isFMHInvokeType(MethodType approxType) {
        Class<?> rtype = approxType.returnType();
        char rtc = Wrappers.basicTypeChar(approxType.returnType());
        if ("VIJFD".indexOf(rtc) < 0 && rtype != Object.class)
            return false;
        int len = approxType.parameterCount();
        for (int i = 0; i < len; i++)
            if (approxType.parameterType(i) != Object.class)
                return false;
        if (approxType.isVarArgs())  return false;
        return true;
    }

    protected Object wrap(int value) {
        switch (rtypec) {
        case 'Z': return (value != 0);
        case 'B': return (byte)value;
        case 'S': return (short)value;
        case 'C': return (char)value;
        }
        return value;
    }

    static class L0 extends MethodHandleInvoker {
        public L0(MethodType type, MethodHandle adapter) { super(type, adapter); }
        @Override public Object invoke_0(MethodHandle mh) {
            checkType(mh);
            switch (rtypec) {
            // Note: Could unswitch this into 5 classes, but too messy.
            case 'L': return      ((FakeMethodHandle)adapter).fake_invoke_L0(mh);
            default:  return wrap(((FakeMethodHandle)adapter).fake_invoke_I0(mh));
            case 'J': return      ((FakeMethodHandle)adapter).fake_invoke_J0(mh);
            case 'F': return      ((FakeMethodHandle)adapter).fake_invoke_F0(mh);
            case 'D': return      ((FakeMethodHandle)adapter).fake_invoke_D0(mh);
            case 'V':             ((FakeMethodHandle)adapter).fake_invoke_V0(mh);
            /* Here is the sort of code we will use when we get javac support:
            case 'L': return      adapter.invoke(mh);
            default:  return wrap(adapter.<int>invoke(mh));
            case 'J': return      adapter.<long>invoke(mh);
            case 'F': return      adapter.<float>invoke(mh);
            case 'D': return      adapter.<double>invoke(mh);
            case 'V':             adapter.<void>invoke(mh);
             */
            }
            return null;
        }
    }

    static class L1 extends MethodHandleInvoker {
        public L1(MethodType type, MethodHandle adapter) { super(type, adapter); }
        @Override public Object invoke_1(MethodHandle mh, Object a0) {
            checkType(mh);
            switch (rtypec) {
            case 'L': return      ((FakeMethodHandle)adapter).fake_invoke_L1(mh, a0);
            default:  return wrap(((FakeMethodHandle)adapter).fake_invoke_I1(mh, a0));
            case 'J': return      ((FakeMethodHandle)adapter).fake_invoke_J1(mh, a0);
            case 'F': return      ((FakeMethodHandle)adapter).fake_invoke_F1(mh, a0);
            case 'D': return      ((FakeMethodHandle)adapter).fake_invoke_D1(mh, a0);
            case 'V':             ((FakeMethodHandle)adapter).fake_invoke_V1(mh, a0);
            }
            return null;
        }
    }

    static class L2 extends MethodHandleInvoker {
        public L2(MethodType type, MethodHandle adapter) { super(type, adapter); }
        @Override public Object invoke_2(MethodHandle mh, Object a0, Object a1) {
            checkType(mh);
            switch (rtypec) {
            case 'L': return      ((FakeMethodHandle)adapter).fake_invoke_L2(mh, a0, a1);
            default:  return wrap(((FakeMethodHandle)adapter).fake_invoke_I2(mh, a0, a1));
            case 'J': return      ((FakeMethodHandle)adapter).fake_invoke_J2(mh, a0, a1);
            case 'F': return      ((FakeMethodHandle)adapter).fake_invoke_F2(mh, a0, a1);
            case 'D': return      ((FakeMethodHandle)adapter).fake_invoke_D2(mh, a0, a1);
            case 'V':             ((FakeMethodHandle)adapter).fake_invoke_V2(mh, a0, a1);
            }
            return null;
        }
    }

    static class L3 extends MethodHandleInvoker {
        public L3(MethodType type, MethodHandle adapter) { super(type, adapter); }
        @Override public Object invoke_3(MethodHandle mh, Object a0, Object a1, Object a2) {
            checkType(mh);
            switch (rtypec) {
            case 'L': return      ((FakeMethodHandle)adapter).fake_invoke_L3(mh, a0, a1, a2);
            default:  return wrap(((FakeMethodHandle)adapter).fake_invoke_I3(mh, a0, a1, a2));
            case 'J': return      ((FakeMethodHandle)adapter).fake_invoke_J3(mh, a0, a1, a2);
            case 'F': return      ((FakeMethodHandle)adapter).fake_invoke_F3(mh, a0, a1, a2);
            case 'D': return      ((FakeMethodHandle)adapter).fake_invoke_D3(mh, a0, a1, a2);
            case 'V':             ((FakeMethodHandle)adapter).fake_invoke_V3(mh, a0, a1, a2);
            }
            return null;
        }
    }

    static class L4 extends MethodHandleInvoker {
        public L4(MethodType type, MethodHandle adapter) { super(type, adapter); }
        @Override public Object invoke_4(MethodHandle mh, Object a0, Object a1, Object a2, Object a3) {
            checkType(mh);
            switch (rtypec) {
            case 'L': return      ((FakeMethodHandle)adapter).fake_invoke_L4(mh, a0, a1, a2, a3);
            default:  return wrap(((FakeMethodHandle)adapter).fake_invoke_I4(mh, a0, a1, a2, a3));
            case 'J': return      ((FakeMethodHandle)adapter).fake_invoke_J4(mh, a0, a1, a2, a3);
            case 'F': return      ((FakeMethodHandle)adapter).fake_invoke_F4(mh, a0, a1, a2, a3);
            case 'D': return      ((FakeMethodHandle)adapter).fake_invoke_D4(mh, a0, a1, a2, a3);
            case 'V':             ((FakeMethodHandle)adapter).fake_invoke_V4(mh, a0, a1, a2, a3);
            }
            return null;
        }
    }

    static class L5 extends MethodHandleInvoker {
        public L5(MethodType type, MethodHandle adapter) { super(type, adapter); }
        @Override public Object invoke_5(MethodHandle mh, Object a0, Object a1, Object a2, Object a3, Object a4) {
            checkType(mh);
            switch (rtypec) {
            case 'L': return      ((FakeMethodHandle)adapter).fake_invoke_L5(mh, a0, a1, a2, a3, a4);
            default:  return wrap(((FakeMethodHandle)adapter).fake_invoke_I5(mh, a0, a1, a2, a3, a4));
            case 'J': return      ((FakeMethodHandle)adapter).fake_invoke_J5(mh, a0, a1, a2, a3, a4);
            case 'F': return      ((FakeMethodHandle)adapter).fake_invoke_F5(mh, a0, a1, a2, a3, a4);
            case 'D': return      ((FakeMethodHandle)adapter).fake_invoke_D5(mh, a0, a1, a2, a3, a4);
            case 'V':             ((FakeMethodHandle)adapter).fake_invoke_V5(mh, a0, a1, a2, a3, a4);
            }
            return null;
        }
    }

    private static Class<?>[] L_CLASSES
        = { L0.class, L1.class, L2.class, L3.class, L4.class, L5.class };
    static { ARGUMENT_MAX = L_CLASSES.length + 1; }

    public static MethodHandleInvoker make(MethodType type) {
        MethodHandleInvoker inv = null;
        synchronized (invokers) {
            inv = invokers.get(type);
        }
        if (inv != null)  return inv;
        inv = makeNew(type);
        synchronized (invokers) {
            MethodHandleInvoker inv2 = invokers.get(type);
            if (inv2 == null)
                invokers.put(type, inv);
            else
                inv = inv2;
        }
        System.out.println("new invoker: "+inv);
        return inv;
    }

    static MethodType approxType(MethodType exactType) {
        int len = exactType.parameterCount();
        if (len > ARGUMENT_MAX || exactType.isVarArgs())
            throw new IllegalArgumentException("too many arguments for invoker: "+exactType);
        // The JVM can insert casts and unboxing for us in a native adapter.
        MethodType approxType = MethodType.makeGeneric(len);
        // But the return type must be exact, except for subword types.
        // Convert subwords to int, since the JVM an narrow them back down.
        Class<?> rtype = exactType.returnType();
        switch (Wrappers.basicTypeChar(rtype)) {
        case 'L':
            rtype = Object.class; break;
        case 'Z': case 'B': case 'S': case 'C':
            rtype = int.class; break;
        }
        approxType = approxType.changeReturnType(rtype);
        return approxType;
    }

    private static MethodHandleInvoker makeNew(MethodType exactType) {
        MethodHandleInvoker inv = null;
        Exception ex1 = null;
        MethodType approxType = approxType(exactType);
        MethodHandle adapter = makeAdapter(exactType, approxType);
        Class<? extends MethodHandleInvoker> template = null;
        Class<? extends MethodHandleInvoker> instance = null;
        Constructor<? extends MethodHandleInvoker> constr = null;
        template = L_CLASSES[approxType.parameterCount()].asSubclass(MethodHandleInvoker.class);
        {
            try {
                instance = expandTemplate(template, approxType, exactType);
                // When we get rid of the fakery, it will be just
                // constr = template.getConstructor
                constr = instance.getConstructor(MethodType.class, MethodHandle.class);
                inv = constr.newInstance(exactType, adapter);
            } catch (IOException ex) {
                ex1 = ex;
            } catch (InvalidConstantPoolFormatException ex) {
                ex1 = ex;
            } catch (InstantiationException ex) {
                ex1 = ex;
            } catch (IllegalAccessException ex) {
                ex1 = ex;
            } catch (NoSuchMethodException ex) {
                ex1 = ex;
            } catch (IllegalArgumentException ex) {
                ex1 = ex;
            } catch (InvocationTargetException ex) {
                ex1 = ex;
            }
        }
        if (inv == null) {
            printex(ex1);
            throw new InternalError();
        }
        return inv;
    }
    private static void printex(Exception ex) {
        System.out.println("*** Unexpected exception in "+MethodHandleInvoker.class);
        System.out.println(ex);
        ex.printStackTrace(System.out);
    }

    private static final AnonymousClassLoader LOADER
            = new AnonymousClassLoader(MethodHandleInvoker.class);

    private static String utf8Name(Class<?> cls) {
        return cls.getName().replace('.', '/');
    }

    private static class TemplateExpander extends ConstantPoolVisitor {
        ConstantPoolParser cp;
        ConstantPoolPatch patch;

        // Pairs of strings to be rewritten:
        String fakeMHName = utf8Name(FakeMethodHandle.class);
        String realMHName = utf8Name(MethodHandle.class);
        boolean didMHName;

        String fakeInvokeName, realInvokeName = "invoke";
        boolean didInvokeName;

        @Override
        public void visitUTF8(int index, byte tag, String utf8) {
            String orig = utf8;
            if (utf8.equals(fakeMHName)) {
                utf8 = realMHName; didMHName = true;
            }
            if (utf8.equals(fakeInvokeName)) {
                utf8 = realInvokeName; didInvokeName = true;
            }
            if ((Object)utf8 != orig)
                patch.putUTF8(index, utf8);
        }

        public TemplateExpander(Class<? extends MethodHandleInvoker> template,
                                MethodType approxType, MethodType exactType)
                throws IOException, InvalidConstantPoolFormatException {
            // construct a descriptor of something like:
            //   int fake_invoke_I2(MethodHandle, Object, Object);
            fakeInvokeName = FMHInvokeName(approxType);
            cp = new ConstantPoolParser(template);
            patch = cp.createPatch();
            cp.parse(this);
            if (!(didMHName && didInvokeName))
                throw new RuntimeException("utf8 rewrites failed: "
                    +(!didMHName?"":fakeMHName)+(!didInvokeName?"":fakeInvokeName));
        }
    }

    static final IdentityHashMap<MethodType,MethodHandleInvoker> invokers
            = new IdentityHashMap<MethodType, MethodHandleInvoker>();

    private static Class<? extends MethodHandleInvoker>
            expandTemplate(Class<? extends MethodHandleInvoker> template,
                           MethodType approxType, MethodType exactType)
                           throws IOException, InvalidConstantPoolFormatException {
        TemplateExpander tex = new TemplateExpander(template, approxType, exactType);
        return LOADER.loadClass(tex.patch).asSubclass(MethodHandleInvoker.class);
    }

    /** Throw this if a bad entry point is taken. */
    protected RuntimeException wrongType(MethodHandle mh) {
        return new WrongMethodTypeException("wrong call type for "+mh+
                " should be "+exactType+" in "+this);
    }
    protected void checkType(MethodHandle mh) {
        if (mh.type() != exactType)
            throw wrongType(mh);
    }
}
