/*
 * Copyright (c) 2008, 2009, 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.dyn;

import java.dyn.*;
import sun.dyn.util.Wrapper;
import static sun.dyn.MemberName.newIllegalArgumentException;

/**
 * Shared information for a group of method types, which differ
 * only by reference types, and therefore share a common erasure
 * and wrapping.
 * <p>
 * For an empirical discussion of the structure of method types,
 * see <a href="http://groups.google.com/group/jvm-languages/browse_thread/thread/ac9308ae74da9b7e/">
 * the thread "Avoiding Boxing" on jvm-languages</a>.
 * There are approximately 2000 distinct erased method types in the JDK.
 * There are a little over 10 times that number of unerased types.
 * No more than half of these are likely to be loaded at once.
 * @author John Rose
 */
public class MethodTypeImpl {
    final int[] argToSlotTable, slotToArgTable;
    final long argCounts;               // packed slot & value counts
    final long primCounts;              // packed prim & double counts
    final int vmslots;                  // total number of parameter slots
    final MethodType erasedType;        // the canonical erasure
    /*lazy*/ MethodType primsAsBoxes;   // replace prims by wrappers
    /*lazy*/ MethodType primArgsAsBoxes; // wrap args only; make raw return
    /*lazy*/ MethodType primsAsInts;    // replace prims by int/long
    /*lazy*/ MethodType primsAsLongs;   // replace prims by long
    /*lazy*/ MethodType primsAtEnd;     // reorder primitives to the end

    // Cached adapter information:
    /*lazy*/ ToGeneric   toGeneric;     // convert cs. with prims to w/o
    /*lazy*/ FromGeneric fromGeneric;   // convert cs. w/o prims to with
    /*lazy*/ SpreadGeneric[] spreadGeneric; // expand one argument to many
    /*lazy*/ FilterGeneric filterGeneric; // convert argument(s) on the fly

    public MethodType erasedType() {
        return erasedType;
    }

    public static MethodTypeImpl of(MethodType type) {
        return METHOD_TYPE_FRIEND.form(type);
    }

    /** Access methods for the internals of MethodType, supplied to
     *  MethodTypeImpl as a trusted agent.
     */
    static public interface MethodTypeFriend {
        Class<?>[]     ptypes(MethodType mt);
        MethodTypeImpl form(MethodType mt);
        void           setForm(MethodType mt, MethodTypeImpl form);
        MethodType     makeImpl(Class<?> rtype, Class<?>[] ptypes, boolean trusted);
        MethodTypeImpl newMethodTypeForm(MethodType mt);
        Invokers       getInvokers(MethodType mt);
        void           setInvokers(MethodType mt, Invokers inv);
    }
    public static void setMethodTypeFriend(Access token, MethodTypeFriend am) {
        Access.check(token);
        if (METHOD_TYPE_FRIEND != null)
            throw new InternalError();  // just once
        METHOD_TYPE_FRIEND = am;
    }
    static private MethodTypeFriend METHOD_TYPE_FRIEND;

    static MethodType makeImpl(Access token, Class<?> rtype, Class<?>[] ptypes, boolean trusted) {
        Access.check(token);
        return METHOD_TYPE_FRIEND.makeImpl(rtype, ptypes, trusted);
    }

    protected MethodTypeImpl(MethodType erasedType) {
        this.erasedType = erasedType;

        Class<?>[] ptypes = METHOD_TYPE_FRIEND.ptypes(erasedType);
        int ptypeCount = ptypes.length;
        int pslotCount = ptypeCount;            // temp. estimate
        int rtypeCount = 1;                     // temp. estimate
        int rslotCount = 1;                     // temp. estimate

        int[] argToSlotTab = null, slotToArgTab = null;

        // Walk the argument types, looking for primitives.
        int pac = 0, lac = 0, prc = 0, lrc = 0;
        Class<?> epts[] = ptypes;
        for (int i = 0; i < epts.length; i++) {
            Class<?> pt = epts[i];
            if (pt != Object.class) {
                assert(pt.isPrimitive());
                ++pac;
                if (hasTwoArgSlots(pt))  ++lac;
            }
        }
        pslotCount += lac;                  // #slots = #args + #longs
        Class<?> rt = erasedType.returnType();
        if (rt != Object.class) {
            ++prc;          // even void.class counts as a prim here
            if (hasTwoArgSlots(rt))  ++lrc;
            // adjust #slots, #args
            if (rt == void.class)
                rtypeCount = rslotCount = 0;
            else
                rslotCount += lrc;
        }
        if (lac != 0) {
            int slot = ptypeCount + lac;
            slotToArgTab = new int[slot+1];
            argToSlotTab = new int[1+ptypeCount];
            argToSlotTab[0] = slot;  // argument "-1" is past end of slots
            for (int i = 0; i < epts.length; i++) {
                Class<?> pt = epts[i];
                if (hasTwoArgSlots(pt))  --slot;
                --slot;
                slotToArgTab[slot] = i+1; // "+1" see argSlotToParameter note
                argToSlotTab[1+i]  = slot;
            }
            assert(slot == 0);  // filled the table
        }
        this.primCounts = pack(lrc, prc, lac, pac);
        this.argCounts = pack(rslotCount, rtypeCount, pslotCount, ptypeCount);
        if (slotToArgTab == null) {
            int slot = ptypeCount; // first arg is deepest in stack
            slotToArgTab = new int[slot+1];
            argToSlotTab = new int[1+ptypeCount];
            argToSlotTab[0] = slot;  // argument "-1" is past end of slots
            for (int i = 0; i < ptypeCount; i++) {
                --slot;
                slotToArgTab[slot] = i+1; // "+1" see argSlotToParameter note
                argToSlotTab[1+i]  = slot;
            }
        }
        this.argToSlotTable = argToSlotTab;
        this.slotToArgTable = slotToArgTab;

        if (pslotCount >= 256)  throw newIllegalArgumentException("too many arguments");

        // send a few bits down to the JVM:
        this.vmslots = parameterSlotCount();

        // short circuit some no-op canonicalizations:
        if (!hasPrimitives()) {
            primsAsBoxes = erasedType;
            primArgsAsBoxes = erasedType;
            primsAsInts  = erasedType;
            primsAsLongs = erasedType;
            primsAtEnd   = erasedType;
        }
    }

    /** Turn all primitive types to corresponding wrapper types.
     */
    public MethodType primsAsBoxes() {
        MethodType ct = primsAsBoxes;
        if (ct != null)  return ct;
        MethodType t = erasedType;
        ct = canonicalize(erasedType, WRAP, WRAP);
        if (ct == null)  ct = t;  // no prims to box
        return primsAsBoxes = ct;
    }

    /** Turn all primitive argument types to corresponding wrapper types.
     *  Subword and void return types are promoted to int.
     */
    public MethodType primArgsAsBoxes() {
        MethodType ct = primArgsAsBoxes;
        if (ct != null)  return ct;
        MethodType t = erasedType;
        ct = canonicalize(erasedType, RAW_RETURN, WRAP);
        if (ct == null)  ct = t;  // no prims to box
        return primArgsAsBoxes = ct;
    }

    /** Turn all primitive types to either int or long.
     *  Floating point return types are not changed, because
     *  they may require special calling sequences.
     *  A void return value is turned to int.
     */
    public MethodType primsAsInts() {
        MethodType ct = primsAsInts;
        if (ct != null)  return ct;
        MethodType t = erasedType;
        ct = canonicalize(t, RAW_RETURN, INTS);
        if (ct == null)  ct = t;  // no prims to int-ify
        return primsAsInts = ct;
    }

    /** Turn all primitive types to either int or long.
     *  Floating point return types are not changed, because
     *  they may require special calling sequences.
     *  A void return value is turned to int.
     */
    public MethodType primsAsLongs() {
        MethodType ct = primsAsLongs;
        if (ct != null)  return ct;
        MethodType t = erasedType;
        ct = canonicalize(t, RAW_RETURN, LONGS);
        if (ct == null)  ct = t;  // no prims to int-ify
        return primsAsLongs = ct;
    }

    /** Stably sort parameters into 3 buckets: ref, int, long. */
    public MethodType primsAtEnd() {
        MethodType ct = primsAtEnd;
        if (ct != null)  return ct;
        MethodType t = erasedType;

        int pac = primitiveParameterCount();
        if (pac == 0)
            return primsAtEnd = t;

        int argc = parameterCount();
        int lac = longPrimitiveParameterCount();
        if (pac == argc && (lac == 0 || lac == argc))
            return primsAtEnd = t;

        // known to have a mix of 2 or 3 of ref, int, long
        int[] reorder = primsAtEndOrder(t);
        ct = reorderParameters(t, reorder, null);
        //System.out.println("t="+t+" / reorder="+java.util.Arrays.toString(reorder)+" => "+ct);
        return primsAtEnd = ct;
    }

    /** Compute a new ordering of parameters so that all references
     *  are before all ints or longs, and all ints are before all longs.
     *  For this ordering, doubles count as longs, and all other primitive
     *  values count as ints.
     *  As a special case, if the parameters are already in the specified
     *  order, this method returns a null reference, rather than an array
     *  specifying a null permutation.
     *  <p>
     *  For example, the type {@code (int,boolean,int,Object,String)void}
     *  produces the order {@code {3,4,0,1,2}}, the type
     *  {@code (long,int,String)void} produces {@code {2,1,2}}, and
     *  the type {@code (Object,int)Object} produces {@code null}.
     */
    public static int[] primsAtEndOrder(MethodType mt) {
        MethodTypeImpl form = METHOD_TYPE_FRIEND.form(mt);
        if (form.primsAtEnd == form.erasedType)
            // quick check shows no reordering is necessary
            return null;

        int argc = form.parameterCount();
        int[] paramOrder = new int[argc];

        // 3-way bucket sort:
        int pac = form.primitiveParameterCount();
        int lac = form.longPrimitiveParameterCount();
        int rfill = 0, ifill = argc - pac, lfill = argc - lac;

        Class<?>[] ptypes = METHOD_TYPE_FRIEND.ptypes(mt);
        boolean changed = false;
        for (int i = 0; i < ptypes.length; i++) {
            Class<?> pt = ptypes[i];
            int ord;
            if (!pt.isPrimitive())             ord = rfill++;
            else if (!hasTwoArgSlots(pt))      ord = ifill++;
            else                               ord = lfill++;
            if (ord != i)  changed = true;
            assert(paramOrder[ord] == 0);
            paramOrder[ord] = i;
        }
        assert(rfill == argc - pac && ifill == argc - lac && lfill == argc);
        if (!changed) {
            form.primsAtEnd = form.erasedType;
            return null;
        }
        return paramOrder;
    }

    /** Put the existing parameters of mt into a new order, given by newParamOrder.
     *  The third argument is logically appended to mt.parameterArray,
     *  so that elements of newParamOrder can index either pre-existing or
     *  new parameter types.
     */
    public static MethodType reorderParameters(MethodType mt, int[] newParamOrder, Class<?>[] moreParams) {
        if (newParamOrder == null)  return mt;  // no-op reordering
        Class<?>[] ptypes = METHOD_TYPE_FRIEND.ptypes(mt);
        Class<?>[] ntypes = new Class<?>[newParamOrder.length];
        int maxParam = ptypes.length + (moreParams == null ? 0 : moreParams.length);
        boolean changed = (ntypes.length != ptypes.length);
        for (int i = 0; i < newParamOrder.length; i++) {
            int param = newParamOrder[i];
            if (param != i)  changed = true;
            Class<?> nt;
            if (param < ptypes.length)   nt = ptypes[param];
            else if (param == maxParam)  nt = mt.returnType();
            else                         nt = moreParams[param - ptypes.length];
            ntypes[i] = nt;
        }
        if (!changed)  return mt;
        return METHOD_TYPE_FRIEND.makeImpl(mt.returnType(), ntypes, true);
    }

    private static boolean hasTwoArgSlots(Class<?> type) {
        return type == long.class || type == double.class;
    }

    private static long pack(int a, int b, int c, int d) {
        assert(((a|b|c|d) & ~0xFFFF) == 0);
        long hw = ((a << 16) | b), lw = ((c << 16) | d);
        return (hw << 32) | lw;
    }
    private static char unpack(long packed, int word) { // word==0 => return a, ==3 => return d
        assert(word <= 3);
        return (char)(packed >> ((3-word) * 16));
    }

    public int parameterCount() {                      // # outgoing values
        return unpack(argCounts, 3);
    }
    public int parameterSlotCount() {                  // # outgoing interpreter slots
        return unpack(argCounts, 2);
    }
    public int returnCount() {                         // = 0 (V), or 1
        return unpack(argCounts, 1);
    }
    public int returnSlotCount() {                     // = 0 (V), 2 (J/D), or 1
        return unpack(argCounts, 0);
    }
    public int primitiveParameterCount() {
        return unpack(primCounts, 3);
    }
    public int longPrimitiveParameterCount() {
        return unpack(primCounts, 2);
    }
    public int primitiveReturnCount() {                // = 0 (obj), or 1
        return unpack(primCounts, 1);
    }
    public int longPrimitiveReturnCount() {            // = 1 (J/D), or 0
        return unpack(primCounts, 0);
    }
    public boolean hasPrimitives() {
        return primCounts != 0;
    }
//    public boolean hasNonVoidPrimitives() {
//        if (primCounts == 0)  return false;
//        if (primitiveParameterCount() != 0)  return true;
//        return (primitiveReturnCount() != 0 && returnCount() != 0);
//    }
    public boolean hasLongPrimitives() {
        return (longPrimitiveParameterCount() | longPrimitiveReturnCount()) != 0;
    }
    public int parameterToArgSlot(int i) {
        return argToSlotTable[1+i];
    }
    public int argSlotToParameter(int argSlot) {
        // Note:  Empty slots are represented by zero in this table.
        // Valid arguments slots contain incremented entries, so as to be non-zero.
        // We return -1 the caller to mean an empty slot.
        return slotToArgTable[argSlot] - 1;
    }

    public static void initForm(Access token, MethodType mt) {
        Access.check(token);
        MethodTypeImpl form = findForm(mt);
        METHOD_TYPE_FRIEND.setForm(mt, form);
        if (form.erasedType == mt) {
            // This is a principal (erased) type; show it to the JVM.
            MethodHandleImpl.init(token, mt);
        }
    }

    static MethodTypeImpl findForm(MethodType mt) {
        MethodType erased = canonicalize(mt, ERASE, ERASE);
        if (erased == null) {
            // It is already erased.  Make a new MethodTypeImpl.
            return METHOD_TYPE_FRIEND.newMethodTypeForm(mt);
        } else {
            // Share the MethodTypeImpl with the erased version.
            return METHOD_TYPE_FRIEND.form(erased);
        }
    }

    /** Codes for {@link #canonicalize(java.lang.Class, int).
     * ERASE means change every reference to {@code Object}.
     * WRAP means convert primitives (including {@code void} to their
     * corresponding wrapper types.  UNWRAP means the reverse of WRAP.
     * INTS means convert all non-void primitive types to int or long,
     * according to size.  LONGS means convert all non-void primitives
     * to long, regardless of size.  RAW_RETURN means convert a type
     * (assumed to be a return type) to int if it is smaller than an int,
     * or if it is void.
     */
    public static final int NO_CHANGE = 0, ERASE = 1, WRAP = 2, UNWRAP = 3, INTS = 4, LONGS = 5, RAW_RETURN = 6;

    /** Canonicalize the types in the given method type.
     * If any types change, intern the new type, and return it.
     * Otherwise return null.
     */
    public static MethodType canonicalize(MethodType mt, int howRet, int howArgs) {
        Class<?>[] ptypes = METHOD_TYPE_FRIEND.ptypes(mt);
        Class<?>[] ptc = MethodTypeImpl.canonicalizes(ptypes, howArgs);
        Class<?> rtype = mt.returnType();
        Class<?> rtc = MethodTypeImpl.canonicalize(rtype, howRet);
        if (ptc == null && rtc == null) {
            // It is already canonical.
            return null;
        }
        // Find the erased version of the method type:
        if (rtc == null)  rtc = rtype;
        if (ptc == null)  ptc = ptypes;
        return METHOD_TYPE_FRIEND.makeImpl(rtc, ptc, true);
    }

    /** Canonicalize the given return or param type.
     *  Return null if the type is already canonicalized.
     */
    static Class<?> canonicalize(Class<?> t, int how) {
        Class<?> ct;
        if (t == Object.class) {
            // no change, ever
        } else if (!t.isPrimitive()) {
            switch (how) {
                case UNWRAP:
                    ct = Wrapper.asPrimitiveType(t);
                    if (ct != t)  return ct;
                    break;
                case RAW_RETURN:
                case ERASE:
                    return Object.class;
            }
        } else if (t == void.class) {
            // no change, usually
            switch (how) {
                case RAW_RETURN:
                    return int.class;
                case WRAP:
                    return Void.class;
            }
        } else {
            // non-void primitive
            switch (how) {
                case WRAP:
                    return Wrapper.asWrapperType(t);
                case INTS:
                    if (t == int.class || t == long.class)
                        return null;  // no change
                    if (t == double.class)
                        return long.class;
                    return int.class;
                case LONGS:
                    if (t == long.class)
                        return null;  // no change
                    return long.class;
                case RAW_RETURN:
                    if (t == int.class || t == long.class ||
                        t == float.class || t == double.class)
                        return null;  // no change
                    // everything else returns as an int
                    return int.class;
            }
        }
        // no change; return null to signify
        return null;
    }

    /** Canonicalize each param type in the given array.
     *  Return null if all types are already canonicalized.
     */
    static Class<?>[] canonicalizes(Class<?>[] ts, int how) {
        Class<?>[] cs = null;
        for (int imax = ts.length, i = 0; i < imax; i++) {
            Class<?> c = canonicalize(ts[i], how);
            if (c != null) {
                if (cs == null)
                    cs = ts.clone();
                cs[i] = c;
            }
        }
        return cs;
    }

    public static Invokers invokers(Access token, MethodType type) {
        Access.check(token);
        Invokers inv = METHOD_TYPE_FRIEND.getInvokers(type);
        if (inv != null)  return inv;
        inv = new Invokers(token, type);
        METHOD_TYPE_FRIEND.setInvokers(type, inv);
        return inv;
    }

    @Override
    public String toString() {
        return "Form"+erasedType;
    }

}
