/*
 * Copyright (C) 2025  Tim Zimmermann <tim@aglais.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.aglais.zms.i18n;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * An extension of the {@link ResourceBundle} class that provides additional functionality
 * for resolving placeholders in localized strings. Placeholders take the form of
 * `${key}` or `${index}`, where `key` references other localized strings and `index`
 * corresponds to positional arguments passed during retrieval.
 * <p>
 * This class supports recursive interpolation.
 */
public class InterpolatingResourceBundle extends ResourceBundle {

    /**
     * A custom ResourceBundle.Control implementation that creates org.aglais.zms.util.InterpolatingResourceBundle
     * instances instead of standard ResourceBundle instances. This allows for automatic
     * placeholder resolution in resource strings.
     */
    public static class Control extends ResourceBundle.Control {
        @Override
        public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws IllegalAccessException, InstantiationException, IOException {
            ResourceBundle base = super.newBundle(baseName, locale, format, loader, reload);
            ResourceBundle fallback = super.newBundle(baseName, Locale.ROOT, format, loader, reload);

            return base != null ? new InterpolatingResourceBundle(base, fallback) : null;
        }
    }

    private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{([^}]+)}");

    private final ResourceBundle base;
    private final ResourceBundle fallback;

    /**
     * Constructs a new {@link InterpolatingResourceBundle} wrapping the given base resource bundle.
     *
     * @param base     The underlying {@link ResourceBundle} that this instance wraps. Must not be null.
     * @param fallback A fallback {@link ResourceBundle} to use if the key isn't found in the base bundle.
     */
    public InterpolatingResourceBundle(@NotNull ResourceBundle base, @Nullable ResourceBundle fallback) {
        this.base = base;
        this.fallback = fallback;
    }

    /**
     * Choose the {@link ResourceBundle} to use for the given key
     *
     * @param key The key to check
     * @return Base bundle if it contains the key or fallback bundle is null, fallback bundle otherwise
     */
    private @NotNull ResourceBundle chooseBundle(String key) {
        if (base.containsKey(key) || fallback == null)
            return base;

        return fallback;
    }

    /**
     * Resolves placeholders within a given string using the provided arguments.
     * Placeholders are formatted as ${index} for positional arguments or ${key}
     * for named entries. Positional arguments correspond to the given array
     * index, while named entries are resolved using the resource bundle.
     *
     * @param value The string containing placeholders to be resolved
     * @param args  Optional arguments used to replace placeholders; positional
     *              arguments should match ${index} placeholders
     * @return The given string with all resolvable placeholders substituted, or
     * the original string if no placeholders are resolved
     */
    private String resolvePlaceholders(String value, Object... args) {
        Matcher m = PLACEHOLDER_PATTERN.matcher(value);
        StringBuilder sb = new StringBuilder();

        while (m.find()) {
            String key = m.group(1);
            String replacement;
            if (key.matches("[0-9]+")) {
                int i = Integer.parseInt(key);

                replacement = args != null && args.length > i ? String.valueOf(args[i]) : m.group(0);
            } else {
                ResourceBundle bundle = chooseBundle(key);
                replacement = bundle.containsKey(key) ? getString(key, args) : m.group(0);
            }

            m.appendReplacement(sb, Matcher.quoteReplacement(replacement));
        }

        m.appendTail(sb);

        return sb.toString();
    }

    @Override
    protected Object handleGetObject(@NotNull String key) {
        ResourceBundle bundle = chooseBundle(key);
        Object value = bundle.getObject(key);

        if (value instanceof String) {
            value = resolvePlaceholders((String) value);
        }

        return value;
    }

    /**
     * Retrieves a localized string corresponding to the specified key and resolves any
     * placeholders within the string using the provided arguments.
     *
     * @param key  The key for the desired string in the resource bundle
     * @param args Optional arguments used to replace placeholders within the string
     * @return The resolved localized string with placeholders substituted, or the original string
     * if no placeholders are present
     * @throws NullPointerException If the key is null
     */
    public String getString(@NotNull String key, Object... args) {
        ResourceBundle bundle = chooseBundle(key);

        return resolvePlaceholders(bundle.getString(key), args);
    }

    @Override
    public @NotNull Enumeration<String> getKeys() {
        Set<String> keys = new HashSet<>(Collections.list(base.getKeys()));
        if (fallback != null) keys.addAll(Collections.list(fallback.getKeys()));

        return Collections.enumeration(keys);
    }
}
