/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  The ASF licenses this file to You
 * under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.  For additional information regarding
 * copyright in this work, please see the NOTICE file in the top level
 * directory of this distribution.
 */

package org.apache.roller.weblogger.util;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * A utility class for handling i18n messaging.
 */
public final class I18nMessages {
    
    private static final Log LOG = LogFactory.getLog(I18nMessages.class);
    
    // locale and bundle we are using for messaging
    private final Locale locale;
    private final ResourceBundle bundle;
    
    // a map of cached messages instances, keyed by locale
    private static Map<Locale, I18nMessages> messagesMap = 
            Collections.synchronizedMap(new HashMap<>());
    
    
    private I18nMessages(String locale) {
        Locale loc = I18nUtils.toLocale(locale);
        this.locale = loc;
        this.bundle = ResourceBundle.getBundle("ApplicationResources", loc);
    }
    
    private I18nMessages(Locale locale) {
        this.locale = locale;
        this.bundle = ResourceBundle.getBundle("ApplicationResources", locale);
    }
    
    
    /**
     * Get an instance for a given locale.
     */
    public static I18nMessages getMessages(String locale) {
        
        LOG.debug("request for messages in locale = " + locale);
        
        // check if we already have a message utils created for that locale
        I18nMessages messages = messagesMap.get(I18nUtils.toLocale(locale));
        
        // if no utils for that language yet then construct
        if(messages == null) {
            messages = new I18nMessages(locale);
            
            // keep a reference to it
            messagesMap.put(messages.getLocale(), messages);
        }
        
        return messages;
    }
    
    
    /**
     * Get an instance for a given locale.
     */
    public static I18nMessages getMessages(Locale locale) {
        
        LOG.debug("request for messages in locale = " + locale.toString());
        
        // check if we already have a message utils created for that locale
        I18nMessages messages = messagesMap.get(locale);
        
        // if no utils for that language yet then construct
        if(messages == null) {
            messages = new I18nMessages(locale);
            
            // keep a reference to it
            messagesMap.put(messages.getLocale(), messages);
        }
        
        return messages;
    }
    
    
    /**
     * The locale representing this message utils.
     */
    public Locale getLocale() {
        return this.locale;
    }
    
    
    /**
     * Get a message from the bundle.
     */
    public String getString(String key) {
        
        try {
            return bundle.getString(key);
        } catch (Exception e) {
            // send a warning in the logs
            LOG.warn("Error getting key " + key);
            return key;
        }
    }
    
    
    /**
     * Get a message from the bundle and substitute the given args into
     * the message contents.
     */
    public String getString(String key, List<?> args) {
        
        try {
            String msg = bundle.getString(key);
            return MessageFormat.format(msg, args.toArray());
        } catch (Exception e) {
            // send a warning in the logs
            LOG.warn("Error getting key " + key, e);
            return key;
        }
    }
    
    
    /**
     * Get a message from the bundle and substitute the given args into
     * the message contents.
     */
    public String getString(String key, Object[] args) {
        
        try {
            String msg = bundle.getString(key);
            return MessageFormat.format(msg, args);
        } catch (Exception e) {
            // send a warning in the logs
            LOG.warn("Error getting key " + key, e);
            return key;
        }
    }
    
	/**
	 * Reload bundle.
	 * 
	 * @param key
	 *            the key
	 */
	public static void reloadBundle(Locale key) {

		try {

			Class<?> type = ResourceBundle.class;
			Field cacheList = type.getDeclaredField("cacheList");

			synchronized (cacheList) {
				cacheList.setAccessible(true);
				((Map<?, ?>) cacheList.get(ResourceBundle.class)).clear();
			}

			clearTomcatCache();

			// Remove cached bundle
			messagesMap.remove(key);

		} catch (Exception e) {
			LOG.error("Error clearing message resource bundles", e);
		}

	}

	/**
	 * Clear tomcat cache.
	 * 
	 * @see com.opensymphony.xwork2.util.LocalizedTextUtil
	 */
	private static void clearTomcatCache() {

		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		// no need for compilation here.
		Class<?> cl = loader.getClass();

		try {
			if ("org.apache.catalina.loader.WebappClassLoader".equals(cl
					.getName())) {
				clearMap(cl, loader, "resourceEntries");
			} else {
				if (LOG.isDebugEnabled()) {
					LOG.debug("class loader " + cl.getName()
							+ " is not tomcat loader.");
				}
			}
		} catch (Exception e) {
			LOG.warn("couldn't clear tomcat cache", e);
		}
	}

	private static void clearMap(Class<?> cl, Object obj, String name)
			throws NoSuchFieldException, IllegalAccessException,
			NoSuchMethodException, InvocationTargetException {
		Field field = cl.getDeclaredField(name);
		field.setAccessible(true);

		Object cache = field.get(obj);

		synchronized (cache) {
			Class<?> ccl = cache.getClass();
			Method clearMethod = ccl.getMethod("clear");
			clearMethod.invoke(cache);
		}

	}
}
