/*
 * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.lang.runtime;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.StringConcatException;
import java.lang.invoke.StringConcatFactory;
import java.util.Arrays;
import java.util.List;

/**
 * This class synthesizes {@link StringTemplate StringTemplates} based on
 * fragments and bootstrap method type. Usage is primarily from
 * {@link java.lang.runtime.TemplateRuntime}.
 *
 * @since 21
 *
 * Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
 *          Do not rely on its availability.
 */
final class StringTemplateImplFactory {

    /**
     * Private constructor.
     */
    StringTemplateImplFactory() {
        throw new AssertionError("private constructor");
    }

    /*
     * {@link StringTemplateImpl} constructor MethodHandle.
     */
    private static final MethodHandle CONSTRUCTOR;


    /*
     * Frequently used method types.
     */
    private static final MethodType MT_STRING_STIMPL =
            MethodType.methodType(String.class, StringTemplateImpl.class);
    private static final MethodType MT_LIST_STIMPL =
            MethodType.methodType(List.class, StringTemplateImpl.class);

    /**
     * List (for nullable) of MethodHandle;
     */
    private static final MethodHandle TO_LIST;

    static {
        try {
            MethodHandles.Lookup lookup = MethodHandles.lookup();

            MethodType mt = MethodType.methodType(void.class, int.class, int.class, List.class,
                    MethodHandle.class, MethodHandle.class);
            CONSTRUCTOR = lookup.findConstructor(StringTemplateImpl.class, mt)
                    .asType(mt.changeReturnType(Carriers.CarrierObject.class));

            mt = MethodType.methodType(List.class, Object[].class);
            TO_LIST = lookup.findStatic(StringTemplateImplFactory.class, "toList", mt);
        } catch(ReflectiveOperationException ex) {
            throw new AssertionError("carrier static init fail", ex);
        }
    }

    /**
     * Create a new {@link StringTemplateImpl} constructor.
     *
     * @param fragments  string template fragments
     * @param type       values types with StringTemplate return
     *
     * @return {@link MethodHandle} that can construct a {@link StringTemplateImpl} with arguments
     * used as values.
     */
    static MethodHandle createStringTemplateImplMH(List<String> fragments, MethodType type) {
        Carriers.CarrierElements elements = Carriers.CarrierFactory.of(type);
        MethodHandle[] components = elements
                .components()
                .stream()
                .map(c -> c.asType(c.type().changeParameterType(0, StringTemplateImpl.class)))
                .toArray(MethodHandle[]::new);
        Class<?>[] ptypes = elements
                .components()
                .stream()
                .map(c -> c.type().returnType())
                .toArray(Class<?>[]::new);
        int[] permute = new int[ptypes.length];

        MethodHandle interpolateMH;
        MethodType mt;
        try {
            interpolateMH = StringConcatFactory.makeConcatWithTemplate(fragments, List.of(ptypes));
        } catch (StringConcatException ex) {
            throw new RuntimeException("constructing internal string template", ex);
        }
        interpolateMH = MethodHandles.filterArguments(interpolateMH, 0, components);
        interpolateMH = MethodHandles.permuteArguments(interpolateMH, MT_STRING_STIMPL, permute);

        mt = MethodType.methodType(List.class, ptypes);
        MethodHandle valuesMH = TO_LIST.asCollector(Object[].class, components.length).asType(mt);
        valuesMH = MethodHandles.filterArguments(valuesMH, 0, components);
        valuesMH = MethodHandles.permuteArguments(valuesMH, MT_LIST_STIMPL, permute);

        MethodHandle constructor = MethodHandles.insertArguments(CONSTRUCTOR, 0,
                elements.primitiveCount(), elements.objectCount(),
                fragments, valuesMH, interpolateMH);
        constructor = MethodHandles.foldArguments(elements.initializer(), 0, constructor);

        mt = MethodType.methodType(StringTemplate.class, ptypes);
        constructor = constructor.asType(mt);

        return constructor;
    }

    /**
     * Generic {@link StringTemplate}.
     *
     * @param fragments  immutable list of string fragments from string template
     * @param values     immutable list of expression values
     */
    private record SimpleStringTemplate(List<String> fragments, List<Object> values)
            implements StringTemplate {
        @Override
        public String toString() {
            return StringTemplate.toString(this);
        }
    }

    /**
     * Returns a new StringTemplate composed from fragments and values.
     *
     * @param fragments array of string fragments
     * @param values    array of expression values
     *
     * @return StringTemplate composed from fragments and values
     */
    static StringTemplate newTrustedStringTemplate(String[] fragments, Object[] values) {
        return new SimpleStringTemplate(List.of(fragments), toList(values));
    }

    /**
     * Returns a new StringTemplate composed from fragments and values.
     *
     * @param fragments list of string fragments
     * @param values    array of expression values
     *
     * @return StringTemplate composed from fragments and values
     */
    static StringTemplate newTrustedStringTemplate(List<String> fragments, Object[] values) {
        return new SimpleStringTemplate(List.copyOf(fragments), toList(values));
    }

    /**
     * Returns a new StringTemplate composed from fragments and values.
     *
     * @param fragments list of string fragments
     * @param values    list of expression values
     *
     * @return StringTemplate composed from fragments and values
     */

    static StringTemplate newStringTemplate(List<String> fragments, List<?> values) {
        @SuppressWarnings("unchecked")
        List<Object> copy = (List<Object>)values.stream().toList();
        return new SimpleStringTemplate(List.copyOf(fragments), copy);
    }

    /**
     * Collect nullable elements from an array into a unmodifiable list.
     * Elements are guaranteed to be safe.
     *
     * @param elements  elements to place in list
     *
     * @return unmodifiable list.
     */
    private static List<Object> toList(Object[] elements) {
        return Arrays.stream(elements).toList();
    }

}
