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

import nuts.core.lang.Methods;
import nuts.core.lang.PrivateAccessors;
import nuts.core.lang.Strings;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;

import com.opensymphony.xwork2.util.LocalizedTextUtil;
import com.opensymphony.xwork2.util.ValueStack;

/**
 * Provides support for localization in XWork.
 */
@SuppressWarnings("unchecked")
public class LocalizedTextUtils extends LocalizedTextUtil {

	/**
	 * put specified bundles to com.opensymphony.xwork2.util.LocalizedTextUtil.bundlesMap
	 * @param bundles resource bundles map
	 * @throws Exception if an error occurs
	 */
	public static void cacheResourceBundles(Map<String, ResourceBundle> bundles) throws Exception {
		synchronized (LocalizedTextUtils.class) {
			Map bundlesMap = (Map) PrivateAccessors.getStaticFieldValue(
				LocalizedTextUtil.class, "bundlesMap");
			bundlesMap.putAll(bundles);
		}
	}

    private static void clearMap(Class cls, Object obj, String name) throws Exception {
		Field field = cls.getDeclaredField(name);
		field.setAccessible(true);

		Map map = (Map)field.get(obj);
		synchronized (map) {
			map.clear();
		}

	}

    /**
	 * clear com.opensymphony.xwork2.util.LocalizedTextUtil.bundlesMap
	 * @throws Exception if an error occurs
	 */
	public static void clearResourceBundlesCache() throws Exception {
		synchronized (LocalizedTextUtils.class) {
			clearMap(LocalizedTextUtil.class, null, "bundlesMap");
			clearMap(LocalizedTextUtil.class, null, "messageFormats");
			try {
	        	ClassLoader cl = getResourceClassLoader();
				Methods.invokeStaticMethod(
					ResourceBundle.class, 
					"clearCache",
					new Object[] { cl },
					new Class[] { ClassLoader.class }
				);
			}
			catch (Throwable ex) {
				clearMap(ResourceBundle.class, null, "cacheList");
			}
		}
	}
	
    /**
     * Returns a localized message for the specified key, aTextName.  Neither the key nor the
     * message is evaluated.
     *
     * @param aTextName the message key
     * @return a localized message based on the specified key, or null if no localized message can be found for it
     */
    public static String findDefaultText(String aTextName) {
    	return findDefaultText(aTextName, ContextUtils.getLocale());
    }

    /**
     * Returns a localized message for the specified key, aTextName, substituting variables from the
     * array of params into the message.  Neither the key nor the message is evaluated.
     *
     * @param aTextName the message key
     * @param params    an array of objects to be substituted into the message text
     * @return A formatted message based on the specified key, or null if no localized message can be found for it
     */
    public static String findDefaultText(String aTextName, Object[] params) {
    	return findDefaultText(aTextName, ContextUtils.getLocale(), params);
    }

	/**
     * @param aTextName the message key
	 * @return Boolean value
	 */
	public static Boolean findDefaultTextAsBoolean(String aTextName) {
		return findDefaultTextAsBoolean(aTextName, ContextUtils.getLocale());
	}

	/**
     * @param aTextName the message key
     * @param locale    the locale the message should be for
	 * @return Boolean value
	 */
	public static Boolean findDefaultTextAsBoolean(String aTextName, Locale locale) {
		return findDefaultTextAsBoolean(aTextName, locale, null);
	}

	/**
     * @param aTextName the message key
	 * @param defaultValue default value
	 * @return Boolean value
	 */
	public static Boolean findDefaultTextAsBoolean(String aTextName, Boolean defaultValue) {
		String s = findDefaultText(aTextName, ContextUtils.getLocale());
		return s == null ? defaultValue : Boolean.valueOf(s);
	}

	/**
     * @param aTextName the message key
	 * @param defaultValue default value
     * @param locale    the locale the message should be for
	 * @return Boolean value
	 */
	public static Boolean findDefaultTextAsBoolean(String aTextName, Locale locale, Boolean defaultValue) {
		String s = findDefaultText(aTextName, locale);
		return s == null ? defaultValue : Boolean.valueOf(s);
	}

	/**
     * @param aTextName the message key
	 * @return integer value
	 */
	public static Integer findDefaultTextAsInt(String aTextName) {
		return findDefaultTextAsInt(aTextName, ContextUtils.getLocale());
	}

	/**
     * @param aTextName the message key
     * @param locale    the locale the message should be for
	 * @return integer value
	 */
	public static Integer findDefaultTextAsInt(String aTextName, Locale locale) {
		return findDefaultTextAsInt(aTextName, locale, null);
	}

	/**
     * @param aTextName the message key
	 * @param defaultValue default value
	 * @return integer value
	 */
	public static Integer findDefaultTextAsInt(String aTextName, Integer defaultValue) {
		String s = findDefaultText(aTextName, ContextUtils.getLocale());
		return s == null ? defaultValue : Integer.valueOf(s);
	}

	/**
     * @param aTextName the message key
     * @param locale    the locale the message should be for
	 * @param defaultValue default value
	 * @return integer value
	 */
	public static Integer findDefaultTextAsInt(String aTextName, Locale locale, Integer defaultValue) {
		String s = findDefaultText(aTextName, locale);
		return s == null ? defaultValue : Integer.valueOf(s);
	}

	/**
     * @param aTextName the message key
	 * @return long value
	 */
	public static Long findDefaultTextAsLong(String aTextName) {
		return findDefaultTextAsLong(aTextName, ContextUtils.getLocale());
	}

	/**
     * @param aTextName the message key
     * @param locale    the locale the message should be for
	 * @return long value
	 */
	public static Long findDefaultTextAsLong(String aTextName, Locale locale) {
		return findDefaultTextAsLong(aTextName, locale, null);
	}

	/**
     * @param aTextName the message key
	 * @param defaultValue default value
	 * @return long value
	 */
	public static Long findDefaultTextAsLong(String aTextName, Integer defaultValue) {
		String s = findDefaultText(aTextName, ContextUtils.getLocale());
		return s == null ? defaultValue : Long.valueOf(s);
	}

	/**
     * @param aTextName the message key
     * @param locale    the locale the message should be for
	 * @param defaultValue default value
	 * @return long value
	 */
	public static Long findDefaultTextAsLong(String aTextName, Locale locale, Integer defaultValue) {
		String s = findDefaultText(aTextName, locale);
		return s == null ? defaultValue : Long.valueOf(s);
	}

	/**
     * @param aTextName the message key
	 * @return List value
	 */
	public static List findDefaultTextAsList(String aTextName) {
		return findDefaultTextAsList(aTextName, ContextUtils.getLocale());
	}

	/**
     * @param aTextName the message key
     * @param locale    the locale the message should be for
	 * @return List value
	 */
	public static List findDefaultTextAsList(String aTextName, Locale locale) {
		return findDefaultTextAsList(aTextName, locale, null);
	}
	
	/**
     * @param aTextName the message key
	 * @param defaultValue default value
	 * @return list value
	 */
	public static List findDefaultTextAsList(String aTextName, List defaultValue) {
		String s = findDefaultText(aTextName, ContextUtils.getLocale());
		return parseTextAsList(s, defaultValue);
	}
	
	/**
     * @param aTextName the message key
     * @param locale    the locale the message should be for
	 * @param defaultValue default value
	 * @return list value
	 */
	public static List findDefaultTextAsList(String aTextName, Locale locale, List defaultValue) {
		String s = findDefaultText(aTextName, locale);
		return parseTextAsList(s, defaultValue);
	}

	/**
     * @param aTextName the message key
	 * @return Map value
	 */
	public static Map findDefaultTextAsMap(String aTextName) {
		return findDefaultTextAsMap(aTextName, ContextUtils.getLocale(), null);
	}

	/**
     * @param aTextName the message key
     * @param locale    the locale the message should be for
	 * @return Map value
	 */
	public static Map findDefaultTextAsMap(String aTextName, Locale locale) {
		return findDefaultTextAsMap(aTextName, locale, null);
	}
	
	/**
     * @param aTextName the message key
	 * @param defaultValue default value
	 * @return Map value
	 */
	public static Map findDefaultTextAsMap(String aTextName, Map defaultValue) {
		String s = findDefaultText(aTextName, ContextUtils.getLocale());
		return parseTextAsMap(s, defaultValue);
	}
	
	/**
     * @param aTextName the message key
     * @param locale    the locale the message should be for
	 * @param defaultValue default value
	 * @return Map value
	 */
	public static Map findDefaultTextAsMap(String aTextName, Locale locale, Map defaultValue) {
		String s = findDefaultText(aTextName, locale);
		return parseTextAsMap(s, defaultValue);
	}
	
	/**
	 * @param text text
	 * @param defaultValue default value
	 * @return list value
	 */
	public static List parseTextAsList(String text, List defaultValue) {
		List list = defaultValue;

		if (Strings.isNotEmpty(text)) {
			text = Strings.strip(text);
			if (!(text.startsWith("{") && text.endsWith("}"))) {
				text = "{" + text + "}";
			}

			ValueStack vs = ContextUtils.getValueStack();
			list = (List)vs.findValue(text);
			if (list == null) {
				throw new RuntimeException("Incorrect OGNL list expression: " + text);
			}
		}

		return list;
	}

	/**
     * @param text text
	 * @param defaultValue default value
	 * @return map value
	 */
	public static Map parseTextAsMap(String text, Map defaultValue) {
		Map map = defaultValue;

		if (Strings.isNotEmpty(text)) {
			text = Strings.strip(text);
			if (text.startsWith("{") && text.endsWith("}")) {
				text = "#" + text;
			}
			else if (!text.startsWith("#")) {
				text = "#{" + text + "}";
			}

			ValueStack vs = ContextUtils.getValueStack();
			map = (Map)vs.findValue(text);
			if (map == null) {
				throw new RuntimeException("Incorrect OGNL map expression: " + text);
			}
		}

		return map;
	}
}
