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

package java.util;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.StringTemplate.Processor;
import java.lang.StringTemplate.Processor.Linkage;
import java.util.regex.Matcher;

import jdk.internal.javac.PreviewFeature;

/**
 * This {@link Processor} constructs a {@link String} result using
 * {@link Formatter} specifications and values found in the {@link StringTemplate}.
 * Unlike {@link Formatter}, {@link FormatProcessor} uses the value from the
 * embedded expression that immediately follows, without whitespace, the
 * <a href="../util/Formatter.html#syntax">format specifier</a>.
 * For example:
 * {@snippet :
 * FormatProcessor fmt = FormatProcessor.create(Locale.ROOT);
 * int x = 10;
 * int y = 20;
 * String result = fmt."%05d\{x} + %05d\{y} = %05d\{x + y}";
 * }
 * In the above example, the value of {@code result} will be {@code "00010 + 00020 = 00030"}.
 * <p>
 * Embedded expressions without a preceeding format specifier, use {@code %s}
 * by default.
 * {@snippet :
 * FormatProcessor fmt = FormatProcessor.create(Locale.ROOT);
 * int x = 10;
 * int y = 20;
 * String result1 = fmt."\{x} + \{y} = \{x + y}";
 * String result2 = fmt."%s\{x} + %s\{y} = %s\{x + y}";
 * }
 * In the above example, the value of {@code result1} and {@code result2} will
 * both be {@code "10 + 20 = 30"}.
 * <p>
 * The {@link FormatProcessor} format specification used and exceptions thrown are the
 * same as those of {@link Formatter}.
 * <p>
 * However, there are two significant differences related to the position of arguments.
 * An explict {@code n$} and relative {@code <} index will cause an exception due to
 * a missing argument list.
 * Whitespace appearing between the specification and the embedded expression will
 * also cause an exception.
 * <p>
 * {@link FormatProcessor} allows the use of different locales. For example:
 * {@snippet :
 * Locale locale = Locale.forLanguageTag("th-TH-u-nu-thai");
 * FormatProcessor thaiFMT = FormatProcessor.create(locale);
 * int x = 10;
 * int y = 20;
 * String result = thaiFMT."%4d\{x} + %4d\{y} = %5d\{x + y}";
 * }
 * In the above example, the value of {@code result} will be
 * {@code "  \u0E51\u0E50 +   \u0E52\u0E50 =    \u0E53\u0E50"}.
 * <p>
 * For day to day use, the predefined {@link FormatProcessor#FMT} {@link FormatProcessor}
 * is available. {@link FormatProcessor#FMT} is defined using the {@link Locale#ROOT}.
 * Example: {@snippet :
 * int x = 10;
 * int y = 20;
 * String result = FMT."0x%04x\{x} + 0x%04x\{y} = 0x%04x\{x + y}"; // @highlight substring="FMT"
 * }
 * In the above example, the value of {@code result} will be {@code "0x000a + 0x0014 = 0x001E"}.
 *
 * @since 21
 *
 * @see Processor
 */
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
public final class FormatProcessor implements Processor<String, RuntimeException>, Linkage {
    /**
     * {@link Locale} used to format
     */
    private final Locale locale;

    /**
     * Constructor.
     *
     * @param locale  {@link Locale} used to format
     */
    private FormatProcessor(Locale locale) {
        this.locale = locale;
    }

    /**
     * Create a new {@link FormatProcessor} using the specified locale.
     *
     * @param locale {@link Locale} used to format
     *
     * @return a new instance of {@link FormatProcessor}
     *
     * @throws java.lang.NullPointerException if locale is null
     */
    public static FormatProcessor create(Locale locale) {
        Objects.requireNonNull(locale);
        return new FormatProcessor(locale);
    }

    /**
     * Constructs a {@link String} based on the fragments, format
     * specifications found in the fragments and values in the
     * supplied {@link StringTemplate} object. This method constructs a
     * format string from the fragments, gathers up the values and
     * evaluates the expression asif evaulating
     * {@code new Formatter(locale).format(format, values).toString()}.
     * <p>
     * If an embedded expression is not immediately preceded by a
     * specifier then a {@code %s} is inserted in the format.
     *
     * @param stringTemplate  a {@link StringTemplate} instance
     *
     * @return constructed {@link String}

     * @throws  IllegalFormatException
     *          If a format specifier contains an illegal syntax, a format
     *          specifier that is incompatible with the given arguments,
     *          a specifier not followed immediately by an embedded expression or
     *          other illegal conditions. For specification of all possible
     *          formatting errors, see the
     *          <a href="../util/Formatter.html#detail">details</a>
     *          section of the formatter class specification.
     * @throws NullPointerException if stringTemplate is null
     *
     * @see java.util.Formatter
     */
    @Override
    public final String process(StringTemplate stringTemplate) {
        Objects.requireNonNull(stringTemplate);
        String format = stringTemplateFormat(stringTemplate.fragments());
        Object[] values = stringTemplate.values().toArray();

        return new Formatter(locale).format(format, values).toString();
    }

    /**
     * Constructs a {@link MethodHandle} that when supplied with the values from
     * a {@link StringTemplate} will produce a result equivalent to that provided by
     * {@link FormatProcessor#process(StringTemplate)}. This {@link MethodHandle}
     * is used by {@link FormatProcessor#FMT} and the ilk to perform a more
     * specialized composition of a result. This specialization is done by
     * prescanning the fragments and value types of a {@link StringTemplate}.
     * <p>
     * Process template expressions can be specialized  when the processor is
     * of type {@link Linkage} and fetched from a static constant as is
     * {@link FormatProcessor#FMT} ({@code static final FormatProcessor}).
     * <p>
     * Other {@link FormatProcessor FormatProcessors} can be specialized when stored in a static
     * final.
     * For example:
     * {@snippet :
     * FormatProcessor THAI_FMT = FormatProcessor.create(Locale.forLanguageTag("th-TH-u-nu-thai"));
     * }
     * {@code THAI_FMT} will now produce specialized {@link MethodHandle MethodHandles} by way
     * of {@link FormatProcessor#linkage(List, MethodType)}.
     *
     * See {@link FormatProcessor#process(StringTemplate)} for more information.
     *
     * @throws  IllegalFormatException
     *          If a format specifier contains an illegal syntax, a format
     *          specifier that is incompatible with the given arguments,
     *          a specifier not followed immediately by an embedded expression or
     *          other illegal conditions. For specification of all possible
     *          formatting errors, see the
     *          <a href="../util/Formatter.html#detail">details</a>
     *          section of the formatter class specification.
     * @throws NullPointerException if fragments or type is null
     *
     * @see java.util.Formatter
     */
    @Override
    public MethodHandle linkage(List<String> fragments, MethodType type) {
        Objects.requireNonNull(fragments);
        Objects.requireNonNull(type);
        String format = stringTemplateFormat(fragments);
        Class<?>[] ptypes = type.dropParameterTypes(0, 1).parameterArray();
        MethodHandle mh = new FormatterBuilder(format, locale, ptypes).build();
        mh = MethodHandles.dropArguments(mh, 0, type.parameterType(0));

        return mh;
    }

    /**
     * Find a format specification at the end of a fragment.
     *
     * @param fragment  fragment to check
     * @param needed    if the specification is needed
     *
     * @return true if the specification is found and needed
     *
     * @throws MissingFormatArgumentException if not at end or found and not needed
     */
    private static boolean findFormat(String fragment, boolean needed) {
        Matcher matcher = Formatter.FORMAT_SPECIFIER_PATTERN.matcher(fragment);
        String group;

        while (matcher.find()) {
            group = matcher.group();

            if (!group.equals("%%") && !group.equals("%n")) {
                if (matcher.end() == fragment.length() && needed) {
                    return true;
                }

                throw new MissingFormatArgumentException(group +
                        " is not immediately followed by an embedded expression");
            }
        }

        return false;
    }

    /**
     * Convert {@link StringTemplate} fragments, containing format specifications,
     * to a form that can be passed on to {@link Formatter}. The method scans each fragment,
     * matching up formatter specifications with the following expression. If no
     * specification is found, the method inserts "%s".
     *
     * @param fragments  string template fragments
     *
     * @return  format string
     */
    private static String stringTemplateFormat(List<String> fragments) {
        StringBuilder sb = new StringBuilder();
        int lastIndex = fragments.size() - 1;
        List<String> formats = fragments.subList(0, lastIndex);
        String last = fragments.get(lastIndex);

        for (String format : formats) {
            if (findFormat(format, true)) {
                sb.append(format);
            } else {
                sb.append(format);
                sb.append("%s");
            }
        }

        if (!findFormat(last, false)) {
            sb.append(last);
        }

        return sb.toString();
    }

    /**
     * This predefined {@link FormatProcessor} instance constructs a {@link String} result using
     * the Locale.ROOT {@link Locale}. See {@link FormatProcessor} for more details.
     * Example: {@snippet :
     * int x = 10;
     * int y = 20;
     * String result = FMT."0x%04x\{x} + 0x%04x\{y} = 0x%04x\{x + y}"; // @highlight substring="FMT"
     * }
     * In the above example, the value of {@code result} will be {@code "0x000a + 0x0014 = 0x001E"}.
     *
     * @see java.util.FormatProcessor
     */
    public static final FormatProcessor FMT = FormatProcessor.create(Locale.ROOT);

}
