/* Copyright 2009-2013 David Hadka
 *
 * This file is part of the MOEA Framework.
 *
 * The MOEA Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * The MOEA Framework 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 Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the MOEA Framework.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.moeaframework.util;

import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

/**
 * Facilitates internalization (i18n) and localization (l10n) of strings.
 * Locale-specific strings are stored in a single file in each package
 * named {@code LocalStrings.properties}, 
 * {@code LocalStrings_{locale}.properties} or any other format supported by
 * {@link ResourceBundle}.  This class supports two modes of localization:
 * <p>
 * <h3>Package-specific</h3>
 * This is useful when classes in a package share common resources.  All
 * classes have access to all stored resources.  Use the
 * {@link #getLocalization(String)} method to create instances of the
 * {@code Localization} class for a specific package.  Here, you use the
 * {@link #getString(String)} and {@link #getString(String, Object...)}
 * methods on the {@code Localization} instance you previously created.
 * <p>
 * <h3>Class-specific</h3>
 * The {@link #getString(Class, String)} and
 * {@link #getString(Class, String, Object...)} static methods do not require
 * you to explicitly create a new {@code Localization} instance.  In addition,
 * the keys are automatically prefixed with the class name.  For example, if
 * your property file contains the following lines:
 * <pre>
 * WindowA.title = First Title
 * WindowB.title = Second Title
 * </pre>
 * You can read each entry with
 * {@code Localization.getString(WindowA.class, "title")} and 
 * {@code Localization.getString(WindowB.class, "title")}.  This is convenient
 * for providing localization in subclasses by using the {@code getClass()}
 * method.
 */
public class Localization {

	/**
	 * The underlying resource bundle storing the key-value resource mapping.
	 */
	private final ResourceBundle bundle;

	/**
	 * Constructs a new localization instance based on the specified resource
	 * bundle.  If this resource bundle is {@code null}, then the
	 * {@code getString} methods return the {@code key}.
	 * 
	 * @param bundle the resource bundle storing the key-value resource mappings
	 *        used by this localization object
	 */
	private Localization(ResourceBundle bundle) {
		super();
		this.bundle = bundle;
	}

	/**
	 * Returns the underlying resource bundle.
	 * 
	 * @return the underlying resource bundle
	 */
	public ResourceBundle getBundle() {
		return bundle;
	}

	/**
	 * Returns the locale of this localization.
	 * 
	 * @return the locale of this localization
	 */
	public Locale getLocale() {
		return bundle.getLocale();
	}

	/**
	 * Returns the localized string for the given key.  If the key is not
	 * found, then this methods returns the key itself.
	 * 
	 * @param key the key for the desired string
	 * @return the localized string for the given key
	 */
	public String getString(String key) {
		try {
			if (bundle == null) {
				return key;
			} else {
				return bundle.getString(key);
			}
		} catch (MissingResourceException e) {
			return key;
		}
	}
	
	/**
	 * Returns {@code true} if a localized string exists for the given key;
	 * {@code false} otherwise.
	 * 
	 * @param key the key for the desired string
	 * @return {@code true} if a localized string exists for the given key;
	 *         {@code false} otherwise
	 */
	public boolean containsKey(String key) {
		if (bundle == null) {
			return false;
		}
			
		return bundle.containsKey(key);
	}

	/**
	 * Returns the localized string for the given key and formatting arguments.
	 * This method uses {@link MessageFormat} for formatting the arguments.  If
	 * the key is not found, then this methods returns the key itself.
	 * 
	 * @param key the key for the desired string
	 * @param arguments the formatting arguments
	 * @return the localized string for the given key and formatting arguments
	 */
	public String getString(String key, Object... arguments) {
		MessageFormat format = new MessageFormat(getString(key), 
				getLocale());

		return format.format(arguments, new StringBuffer(), null).toString();
	}

	/**
	 * Returns the localization object for the given package.
	 * 
	 * @param packageName the name of the package
	 * @return the localization object for the given package
	 */
	public static Localization getLocalization(String packageName) {
		return getLocalization(packageName, Locale.getDefault());
	}

	/**
	 * Returns the localization object for the given package and locale.
	 * 
	 * @param packageName the name of the package
	 * @param locale the target locale
	 * @return the localization object for the given package
	 */
	public static Localization getLocalization(String packageName,
			Locale locale) {
		ResourceBundle bundle = null;

		try {
			bundle = ResourceBundle.getBundle(packageName + ".LocalStrings", 
					locale);
		} catch (MissingResourceException e) {
			//bundle remains null, so the localization returns the key to
			//provide a visual clue for debugging
		}

		return new Localization(bundle);
	}

	/**
	 * Returns the localization object for the given class.
	 * 
	 * @param type the class requesting the localization object
	 * @return the localization object for the given class
	 */
	public static Localization getLocalization(Class<?> type) {
		return getLocalization(type.getPackage().getName());
	}

	/**
	 * Returns the localization object for the given class and locale.
	 * 
	 * @param type the class requesting the localization object
	 * @param locale the target locale
	 * @return the localization object for the given class
	 */
	public static Localization getLocalization(Class<?> type, Locale locale) {
		return getLocalization(type.getPackage().getName(), locale);
	}
	
	/**
	 * Returns {@code true} if a localized string exists for the given key;
	 * {@code false} otherwise.  This method automatically finds the correct
	 * resource bundle and key prefix appropriate for the class.
	 * 
	 * @param type the class requesting the localized string
	 * @param key the key (minus the class prefix)
	 * @return {@code true} if a localized string exists for the given key;
	 *         {@code false} otherwise
	 */
	public static boolean containsKey(Class<?> type, String key) {
		return getLocalization(type).containsKey(type.getSimpleName() + "." +
				key);
	}

	/**
	 * Returns the localized string for the given key.  This method
	 * automatically finds the correct resource bundle and key prefix
	 * appropriate for the class.  For example, calling
	 * {@code Localization.getString(MainGUI.class, "title")} returns the
	 * localized string for the key {@code "MainGUI.title"}.
	 * 
	 * @param type the class requesting the localized string
	 * @param key the key (minus the class prefix)
	 * @return the localized string for the given key
	 */
	public static String getString(Class<?> type, String key) {
		return getLocalization(type).getString(type.getSimpleName() + "." +
				key);
	}

	/**
	 * Returns the localized string for the given key and formatting arguments.
	 * This method automatically finds the correct resource bundle and key
	 * prefix appropriate for the class.  For example, calling
	 * {@code Localization.getString(MainGUI.class, "title")} returns the
	 * localized string for the key {@code "MainGUI.title"}.
	 * 
	 * @param type the class requesting the localized string
	 * @param key the key (minus the class prefix)
	 * @param arguments the formatting arguments
	 * @return the localized string for the given key
	 */
	public static String getString(Class<?> type, String key, 
			Object... arguments) {
		return getLocalization(type).getString(type.getSimpleName() + "." + key,
				arguments);
	}

}
