/*
 * Copyright (c) 2008, 2022, 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 java.lang.invoke;

import jdk.internal.vm.annotation.Stable;

import java.util.ArrayList;
import java.util.List;

import static java.lang.invoke.LambdaForm.BasicType;
import static java.lang.invoke.LambdaForm.BasicType.*;
import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
import static java.lang.invoke.MethodHandleNatives.Constants.*;
import static java.lang.invoke.MethodHandleStatics.newInternalError;
import static java.lang.invoke.MethodHandleStatics.uncaughtException;

/**
 * The flavor of method handle which emulates an invoke instruction
 * on a predetermined argument.  The JVM dispatches to the correct method
 * when the handle is created, not when it is invoked.
 *
 * All bound arguments are encapsulated in dedicated species.
 */
/*non-public*/
abstract non-sealed class BoundMethodHandle extends MethodHandle {

    /*non-public*/
    BoundMethodHandle(MethodType type, LambdaForm form) {
        super(type, form);
        assert(speciesData() == speciesDataFor(form));
    }

    //
    // BMH API and internals
    //

    /*non-public*/
    LambdaFormEditor editor() {
        return form.editor();
    }

    static BoundMethodHandle bindSingle(MethodType type, LambdaForm form, Object x) {
        return Species_L.make(type, form, x);
    }

    @Override // there is a default binder in the super class, for 'L' types only
    /*non-public*/
    BoundMethodHandle bindArgumentL(int pos, Object value) {
        return editor().bindArgumentL(this, pos, value);
    }

    /*non-public*/
    BoundMethodHandle bindArgumentI(int pos, int value) {
        return editor().bindArgumentI(this, pos, value);
    }
    /*non-public*/
    BoundMethodHandle bindArgumentJ(int pos, long value) {
        return editor().bindArgumentJ(this, pos, value);
    }
    /*non-public*/
    BoundMethodHandle bindArgumentF(int pos, float value) {
        return editor().bindArgumentF(this, pos, value);
    }
    /*non-public*/
    BoundMethodHandle bindArgumentD(int pos, double value) {
        return editor().bindArgumentD(this, pos, value);
    }
    @Override
    BoundMethodHandle rebind() {
        if (!tooComplex()) {
            return this;
        }
        return makeReinvoker(this);
    }

    private boolean tooComplex() {
        return (fieldCount() > FIELD_COUNT_THRESHOLD ||
                form.expressionCount() > FORM_EXPRESSION_THRESHOLD);
    }
    private static final int FIELD_COUNT_THRESHOLD = 12;      // largest convenient BMH field count
    private static final int FORM_EXPRESSION_THRESHOLD = 24;  // largest convenient BMH expression count

    /**
     * A reinvoker MH has this form:
     * {@code lambda (bmh, arg*) { thismh = bmh[0]; invokeBasic(thismh, arg*) }}
     */
    static BoundMethodHandle makeReinvoker(MethodHandle target) {
        LambdaForm form = DelegatingMethodHandle.makeReinvokerForm(
                target, MethodTypeForm.LF_REBIND,
                Species_L.BMH_SPECIES, Species_L.BMH_SPECIES.getterFunction(0));
        return Species_L.make(target.type(), form, target);
    }

    /**
     * Return the {@link BoundMethodHandle.SpeciesData} instance representing this BMH species. All subclasses must provide a
     * static field containing this value, and they must accordingly implement this method.
     */
    /*non-public*/
    abstract BoundMethodHandle.SpeciesData speciesData();

    /*non-public*/
    static BoundMethodHandle.SpeciesData speciesDataFor(LambdaForm form) {
        Object c = form.names[0].constraint;
        if (c instanceof SpeciesData) {
            return (SpeciesData) c;
        }
        // if there is no BMH constraint, then use the null constraint
        return SPECIALIZER.topSpecies();
    }

    /**
     * Return the number of fields in this BMH.  Equivalent to speciesData().fieldCount().
     */
    /*non-public*/
    final int fieldCount() { return speciesData().fieldCount(); }

    @Override
    Object internalProperties(int indentLevel) {
        return "\n" + debugPrefix(indentLevel) + "& BMH=" + internalValues(indentLevel);
    }

    @Override
    final String internalValues(int indentLevel) {
        String prefix = debugPrefix(indentLevel);
        int count = fieldCount();
        if (count == 1 && indentLevel < 0) {
            return "[" + arg(0) + "]";
        }
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < count; ++i) {
            Object theArg = arg(i);
            sb.append("\n  ").append(prefix).append(i);
            if (indentLevel >= 0 && theArg instanceof MethodHandle) {
                sb.append(": MethodHandle = {").append(((MethodHandle)theArg).debugString(indentLevel+1));
                sb.append("\n  ").append(prefix).append("}");
            } else {
                sb.append(": ( ").append(theArg).append(" )");
            }
        }
        return sb.append("\n").append(prefix).append("]").toString();
    }

    /*non-public*/
    final Object arg(int i) {
        try {
            Class<?> fieldType = speciesData().fieldTypes().get(i);
            switch (BasicType.basicType(fieldType)) {
                case L_TYPE: return          speciesData().getter(i).invokeBasic(this);
                case I_TYPE: return (int)    speciesData().getter(i).invokeBasic(this);
                case J_TYPE: return (long)   speciesData().getter(i).invokeBasic(this);
                case F_TYPE: return (float)  speciesData().getter(i).invokeBasic(this);
                case D_TYPE: return (double) speciesData().getter(i).invokeBasic(this);
            }
        } catch (Throwable ex) {
            throw uncaughtException(ex);
        }
        throw new InternalError("unexpected type: " + speciesData().key()+"."+i);
    }

    //
    // cloning API
    //

    /*non-public*/
    abstract BoundMethodHandle copyWith(MethodType mt, LambdaForm lf);
    /*non-public*/
    abstract BoundMethodHandle copyWithExtendL(MethodType mt, LambdaForm lf, Object narg);
    /*non-public*/
    abstract BoundMethodHandle copyWithExtendI(MethodType mt, LambdaForm lf, int    narg);
    /*non-public*/
    abstract BoundMethodHandle copyWithExtendJ(MethodType mt, LambdaForm lf, long   narg);
    /*non-public*/
    abstract BoundMethodHandle copyWithExtendF(MethodType mt, LambdaForm lf, float  narg);
    /*non-public*/
    abstract BoundMethodHandle copyWithExtendD(MethodType mt, LambdaForm lf, double narg);

    //
    // concrete BMH classes required to close bootstrap loops
    //

    private  // make it private to force users to access the enclosing class first
    static final class Species_L extends BoundMethodHandle {

        final Object argL0;

        private Species_L(MethodType mt, LambdaForm lf, Object argL0) {
            super(mt, lf);
            this.argL0 = argL0;
        }

        @Override
        /*non-public*/
        SpeciesData speciesData() {
            return BMH_SPECIES;
        }

        /*non-public*/
        static @Stable SpeciesData BMH_SPECIES;

        /*non-public*/
        static BoundMethodHandle make(MethodType mt, LambdaForm lf, Object argL0) {
            return new Species_L(mt, lf, argL0);
        }
        @Override
        /*non-public*/
        final BoundMethodHandle copyWith(MethodType mt, LambdaForm lf) {
            return new Species_L(mt, lf, argL0);
        }
        @Override
        /*non-public*/
        final BoundMethodHandle copyWithExtendL(MethodType mt, LambdaForm lf, Object narg) {
            try {
                return (BoundMethodHandle) BMH_SPECIES.extendWith(L_TYPE).factory().invokeBasic(mt, lf, argL0, narg);
            } catch (Throwable ex) {
                throw uncaughtException(ex);
            }
        }
        @Override
        /*non-public*/
        final BoundMethodHandle copyWithExtendI(MethodType mt, LambdaForm lf, int narg) {
            try {
                return (BoundMethodHandle) BMH_SPECIES.extendWith(I_TYPE).factory().invokeBasic(mt, lf, argL0, narg);
            } catch (Throwable ex) {
                throw uncaughtException(ex);
            }
        }
        @Override
        /*non-public*/
        final BoundMethodHandle copyWithExtendJ(MethodType mt, LambdaForm lf, long narg) {
            try {
                return (BoundMethodHandle) BMH_SPECIES.extendWith(J_TYPE).factory().invokeBasic(mt, lf, argL0, narg);
            } catch (Throwable ex) {
                throw uncaughtException(ex);
            }
        }
        @Override
        /*non-public*/
        final BoundMethodHandle copyWithExtendF(MethodType mt, LambdaForm lf, float narg) {
            try {
                return (BoundMethodHandle) BMH_SPECIES.extendWith(F_TYPE).factory().invokeBasic(mt, lf, argL0, narg);
            } catch (Throwable ex) {
                throw uncaughtException(ex);
            }
        }
        @Override
        /*non-public*/
        final BoundMethodHandle copyWithExtendD(MethodType mt, LambdaForm lf, double narg) {
            try {
                return (BoundMethodHandle) BMH_SPECIES.extendWith(D_TYPE).factory().invokeBasic(mt, lf, argL0, narg);
            } catch (Throwable ex) {
                throw uncaughtException(ex);
            }
        }
    }

    //
    // BMH species meta-data
    //

    /*non-public*/
    static final class SpeciesData
            extends ClassSpecializer<BoundMethodHandle, String, SpeciesData>.SpeciesData {
        // This array is filled in lazily, as new species come into being over time.
        @Stable private final SpeciesData[] extensions = new SpeciesData[ARG_TYPE_LIMIT];

        public SpeciesData(Specializer outer, String key) {
            outer.super(key);
        }

        @Override
        protected String deriveClassName() {
            String typeString = deriveTypeString();
            if (typeString.isEmpty()) {
                return SimpleMethodHandle.class.getName();
            }
            return BoundMethodHandle.class.getName() + "$Species_" + typeString;
        }

        @Override
        protected List<Class<?>> deriveFieldTypes(String key) {
            ArrayList<Class<?>> types = new ArrayList<>(key.length());
            for (int i = 0; i < key.length(); i++) {
                types.add(basicType(key.charAt(i)).basicTypeClass());
            }
            return types;
        }

        @Override
        protected String deriveTypeString() {
            // (If/when we have to add nominal types, just inherit the more complex default.)
            return key();
        }

        @Override
        protected MethodHandle deriveTransformHelper(MemberName transform, int whichtm) {
            if (whichtm == Specializer.TN_COPY_NO_EXTEND) {
                return factory();
            } else if (whichtm < ARG_TYPE_LIMIT) {
                return extendWith(BasicType.basicType((byte) whichtm)).factory();
            } else {
                throw newInternalError("bad transform");
            }
        }

        @Override
        protected <X> List<X> deriveTransformHelperArguments(MemberName transform, int whichtm, List<X> args, List<X> fields) {
            assert(verifyTHAargs(transform, whichtm, args, fields));
            // The rule is really simple:  Keep the first two arguments
            // the same, then put in the fields, then put any other argument.
            args.addAll(2, fields);
            return args;
        }

        private boolean verifyTHAargs(MemberName transform, int whichtm, List<?> args, List<?> fields) {
            assert(transform == Specializer.BMH_TRANSFORMS.get(whichtm));
            MethodType tType = transform.getMethodType();
            assert(args.size() == tType.parameterCount());
            assert(fields.size() == this.fieldCount());
            final int MH_AND_LF = 2;
            if (whichtm == Specializer.TN_COPY_NO_EXTEND) {
                assert(tType.parameterCount() == MH_AND_LF);
            } else if (whichtm < ARG_TYPE_LIMIT) {
                assert(tType.parameterCount() == MH_AND_LF+1);
                final BasicType type = basicType((byte) whichtm);
                assert(tType.parameterType(MH_AND_LF) == type.basicTypeClass());
            } else {
                return false;
            }
            return true;
        }

        /*non-public*/
        SpeciesData extendWith(BasicType basicType) {
            int typeNum = basicType.ordinal();
            SpeciesData sd = extensions[typeNum];
            if (sd != null)  return sd;
            sd = SPECIALIZER.findSpecies(key() + basicType.basicTypeChar());
            extensions[typeNum] = sd;
            return sd;
        }
    }

    /*non-public*/
    static final Specializer SPECIALIZER = new Specializer();
    static {
        SimpleMethodHandle.BMH_SPECIES = BoundMethodHandle.SPECIALIZER.findSpecies("");
        Species_L.BMH_SPECIES = BoundMethodHandle.SPECIALIZER.findSpecies("L");
    }

    /*non-public*/
    static final class Specializer
            extends ClassSpecializer<BoundMethodHandle, String, SpeciesData> {

        private static final MemberName SPECIES_DATA_ACCESSOR;

        static {
            try {
                SPECIES_DATA_ACCESSOR = IMPL_LOOKUP.resolveOrFail(REF_invokeVirtual, BoundMethodHandle.class,
                        "speciesData", MethodType.methodType(BoundMethodHandle.SpeciesData.class));
            } catch (ReflectiveOperationException ex) {
                throw newInternalError("Bootstrap link error", ex);
            }
        }

        private Specializer() {
            super(  // Reified type parameters:
                    BoundMethodHandle.class, String.class, BoundMethodHandle.SpeciesData.class,
                    // Principal constructor type:
                    MethodType.methodType(void.class, MethodType.class, LambdaForm.class),
                    // Required linkage between class and species:
                    SPECIES_DATA_ACCESSOR,
                    "BMH_SPECIES",
                    BMH_TRANSFORMS);
        }

        @Override
        protected String topSpeciesKey() {
            return "";
        }

        @Override
        protected BoundMethodHandle.SpeciesData newSpeciesData(String key) {
            return new BoundMethodHandle.SpeciesData(this, key);
        }

        static final List<MemberName> BMH_TRANSFORMS;
        static final int TN_COPY_NO_EXTEND = V_TYPE.ordinal();
        static {
            final Class<BoundMethodHandle> BMH = BoundMethodHandle.class;
            // copyWithExtendLIJFD + copyWith
            try {
                BMH_TRANSFORMS = List.of(
                        IMPL_LOOKUP.resolveOrFail(REF_invokeVirtual, BMH, "copyWithExtendL", MethodType.methodType(BMH, MethodType.class, LambdaForm.class, Object.class)),
                        IMPL_LOOKUP.resolveOrFail(REF_invokeVirtual, BMH, "copyWithExtendI", MethodType.methodType(BMH, MethodType.class, LambdaForm.class, int.class)),
                        IMPL_LOOKUP.resolveOrFail(REF_invokeVirtual, BMH, "copyWithExtendJ", MethodType.methodType(BMH, MethodType.class, LambdaForm.class, long.class)),
                        IMPL_LOOKUP.resolveOrFail(REF_invokeVirtual, BMH, "copyWithExtendF", MethodType.methodType(BMH, MethodType.class, LambdaForm.class, float.class)),
                        IMPL_LOOKUP.resolveOrFail(REF_invokeVirtual, BMH, "copyWithExtendD", MethodType.methodType(BMH, MethodType.class, LambdaForm.class, double.class)),
                        IMPL_LOOKUP.resolveOrFail(REF_invokeVirtual, BMH, "copyWith", MethodType.methodType(BMH, MethodType.class, LambdaForm.class))
                );
            } catch (ReflectiveOperationException ex) {
                throw newInternalError("Failed resolving copyWith methods", ex);
            }

            // as it happens, there is one transform per BasicType including V_TYPE
            assert(BMH_TRANSFORMS.size() == TYPE_LIMIT);
        }

        /**
         * Generation of concrete BMH classes.
         *
         * A concrete BMH species is fit for binding a number of values adhering to a
         * given type pattern. Reference types are erased.
         *
         * BMH species are cached by type pattern.
         *
         * A BMH species has a number of fields with the concrete (possibly erased) types of
         * bound values. Setters are provided as an API in BMH. Getters are exposed as MHs,
         * which can be included as names in lambda forms.
         */
        class Factory extends ClassSpecializer<BoundMethodHandle, String, BoundMethodHandle.SpeciesData>.Factory {
            @Override
            protected String chooseFieldName(Class<?> type, int index) {
                return "arg" + super.chooseFieldName(type, index);
            }
        }

        @Override
        protected Factory makeFactory() {
            return new Factory();
        }
      }

    static SpeciesData speciesData_L()      { return Species_L.BMH_SPECIES; }
    static SpeciesData speciesData_LL()     { return SPECIALIZER.findSpecies("LL"); }
    static SpeciesData speciesData_LLL()    { return SPECIALIZER.findSpecies("LLL"); }
    static SpeciesData speciesData_LLLL()   { return SPECIALIZER.findSpecies("LLLL"); }
    static SpeciesData speciesData_LLLLL()  { return SPECIALIZER.findSpecies("LLLLL"); }
}
