/*
 * Copyright (c) 2005, 2015, 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 com.sun.tools.javac.util;

import com.sun.tools.javac.api.Messages;
import java.lang.ref.SoftReference;
import java.util.ResourceBundle;
import java.util.MissingResourceException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

/**
 *  Support for formatted localized messages.
 *
 *  <p><b>This is NOT part of any supported API.
 *  If you write code that depends on this, you do so at your own risk.
 *  This code and its internal interfaces are subject to change or
 *  deletion without notice.</b>
 */
public class JavacMessages implements Messages {
    /** The context key for the JavacMessages object. */
    public static final Context.Key<JavacMessages> messagesKey = new Context.Key<>();

    /** Get the JavacMessages instance for this context. */
    public static JavacMessages instance(Context context) {
        JavacMessages instance = context.get(messagesKey);
        if (instance == null)
            instance = new JavacMessages(context);
        return instance;
    }

    private Map<Locale, SoftReference<List<ResourceBundle>>> bundleCache;

    private List<ResourceBundleHelper> bundleHelpers;

    private Locale currentLocale;
    private List<ResourceBundle> currentBundles;

    public Locale getCurrentLocale() {
        return currentLocale;
    }

    public void setCurrentLocale(Locale locale) {
        if (locale == null) {
            locale = Locale.getDefault();
        }
        this.currentBundles = getBundles(locale);
        this.currentLocale = locale;
    }

    /** Creates a JavacMessages object.
     */
    public JavacMessages(Context context) {
        this(defaultBundleName, context.get(Locale.class));
        context.put(messagesKey, this);
    }

    /** Creates a JavacMessages object.
     * @param bundleName the name to identify the resource bundle of localized messages.
     */
    public JavacMessages(String bundleName) throws MissingResourceException {
        this(bundleName, null);
    }

    /** Creates a JavacMessages object.
     * @param bundleName the name to identify the resource bundle of localized messages.
     */
    public JavacMessages(String bundleName, Locale locale) throws MissingResourceException {
        bundleHelpers = List.nil();
        bundleCache = new HashMap<>();
        add(bundleName);
        setCurrentLocale(locale);
    }

    public JavacMessages() throws MissingResourceException {
        this(defaultBundleName);
    }

    @Override
    public void add(String bundleName) throws MissingResourceException {
        add(locale -> ResourceBundle.getBundle(bundleName, locale));
    }

    public void add(ResourceBundleHelper ma) {
        bundleHelpers = bundleHelpers.prepend(ma);
        if (!bundleCache.isEmpty())
            bundleCache.clear();
        currentBundles = null;
    }

    public List<ResourceBundle> getBundles(Locale locale) {
        if (locale == currentLocale && currentBundles != null)
            return currentBundles;
        SoftReference<List<ResourceBundle>> bundles = bundleCache.get(locale);
        List<ResourceBundle> bundleList = bundles == null ? null : bundles.get();
        if (bundleList == null) {
            bundleList = List.nil();
            for (ResourceBundleHelper helper : bundleHelpers) {
                try {
                    ResourceBundle rb = helper.getResourceBundle(locale);
                    bundleList = bundleList.prepend(rb);
                } catch (MissingResourceException e) {
                    throw new InternalError("Cannot find requested resource bundle for locale " +
                            locale, e);
                }
            }
            bundleCache.put(locale, new SoftReference<>(bundleList));
        }
        return bundleList;
    }

    /** Gets the localized string corresponding to a key, formatted with a set of args.
     */
    public String getLocalizedString(String key, Object... args) {
        return getLocalizedString(currentLocale, key, args);
    }

    @Override
    public String getLocalizedString(Locale l, String key, Object... args) {
        if (l == null)
            l = getCurrentLocale();
        return getLocalizedString(getBundles(l), key, args);
    }

    /* Static access:
     * javac has a firmly entrenched notion of a default message bundle
     * which it can access from any static context. This is used to get
     * easy access to simple localized strings.
     */

    private static final String defaultBundleName = "com.sun.tools.javac.resources.compiler";
    private static ResourceBundle defaultBundle;
    private static JavacMessages defaultMessages;


    /**
     * Returns a localized string from the compiler's default bundle.
     */
    // used to support legacy Log.getLocalizedString
    static String getDefaultLocalizedString(String key, Object... args) {
        return getLocalizedString(List.of(getDefaultBundle()), key, args);
    }

    // used to support legacy static Diagnostic.fragment
    @Deprecated
    static JavacMessages getDefaultMessages() {
        if (defaultMessages == null)
            defaultMessages = new JavacMessages(defaultBundleName);
        return defaultMessages;
    }

    public static ResourceBundle getDefaultBundle() {
        try {
            if (defaultBundle == null)
                defaultBundle = ResourceBundle.getBundle(defaultBundleName);
            return defaultBundle;
        }
        catch (MissingResourceException e) {
            throw new Error("Fatal: Resource for compiler is missing", e);
        }
    }

    private static String getLocalizedString(List<ResourceBundle> bundles,
                                             String key,
                                             Object... args) {
       String msg = null;
       for (List<ResourceBundle> l = bundles; l.nonEmpty() && msg == null; l = l.tail) {
           ResourceBundle rb = l.head;
           try {
               msg = rb.getString(key);
           }
           catch (MissingResourceException e) {
               // ignore, try other bundles in list
           }
       }
       if (msg == null) {
           msg = "compiler message file broken: key=" + key +
               " arguments={0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}";
       }
       return MessageFormat.format(msg, args);
    }

    /**
     * This provides a way for the JavacMessager to retrieve a
     * ResourceBundle from another module such as jdk.javadoc.
     */
    public interface ResourceBundleHelper {
        /**
         * Gets the ResourceBundle.
         * @param locale the requested bundle's locale
         * @return ResourceBundle
         */
        ResourceBundle getResourceBundle(Locale locale);
    }
}
