/*
 *    Dictionary.java
 *    Copyright (C) 2005 New Zealand Digital Library, http://www.nzdl.org
 *
 *    This program 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 2 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 General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.greenstone.gsdl3.util;

import java.util.ResourceBundle;
import java.util.Locale;
import java.util.Enumeration;

import org.apache.log4j.*;

public class Dictionary
{

	static Logger logger = Logger.getLogger(org.greenstone.gsdl3.util.Dictionary.class.getName());

    /** The default servlet dictionary */
    public static final String core_servlet_dictionary_name = "core_servlet_dictionary";
  /** Fallback language */
  protected static final String fallback_language = "en";
  /** THe string to mark a value as awaiting translator input */
  public static final String PENDING_STR = "[PENDING]";
	/** The locale of this dictionary. */
	protected Locale locale = null;

	/** The resource that has been loaded */
	protected String resource = null;

 	/**
	 * The ResourceBundle which contains the raw key-value mappings. Loaded from
	 * a file named "resource_locale.properties
	 */
	private ResourceBundle raw = null;

	/**
	 * Constructs the Dictionary class by first creating a locale from the
	 * specified language, (or using the default locale if this didn't work),
	 * then loading a resource bundle based on this locale.
	 */
	public Dictionary(String resource, String lang)
	{
		// Initialize.

		this.locale = new Locale(lang);
		this.resource = resource;
		if (this.locale == null)
		{
			this.locale = Locale.getDefault();
		}
		try
		{
			this.raw = ResourceBundle.getBundle(this.resource, this.locale, new UTF8Control());
		}
		catch (Exception e)
		{
			//logger.debug("Dictionary: couldn't locate a resource bundle for "+resource);
		}
	}

	public Dictionary(String resource, Locale locale)
	{
		this.locale = locale;
		this.resource = resource;
		try
		{
			this.raw = ResourceBundle.getBundle(this.resource, this.locale, new UTF8Control());
		}
		catch (Exception e)
		{
			//logger.debug("Dictionary: couldn't locate a resource bundle for "+resource);
		}
	}

	/**
	 * Constructs the Dictionary class by first creating a locale from the
	 * specified language, (or using the default locale if this didn't work),
	 * then loading a resource bundle based on this locale. A classloader is
	 * specified which can be used to find the resource.
	 */
	public Dictionary(String resource, String lang, ClassLoader loader)
	{
		// Initialize.
		this.locale = new Locale(lang);
		this.resource = resource;
		if (this.locale == null)
		{
			this.locale = Locale.getDefault();
		}
		// try the specified class loader
		if (loader != null) {
		    try
		    {
			this.raw = ResourceBundle.getBundle(this.resource, this.locale, loader, new UTF8Control());
			return;
		    }
		    catch (Exception e)
		    {
		    }
		}

		// try with default class loader
		try
		{
		    this.raw = ResourceBundle.getBundle(this.resource, this.locale, new UTF8Control());
		}
		catch (Exception ex)
		{
			//logger.debug("Dictionary: couldn't locate a resource bundle for "+resource);
		}
	}

	public Enumeration<String> getKeys()
	{
		if (this.raw != null)
		{
			return this.raw.getKeys();
		}
		return null;
	}

	/**
	 * Overloaded to call get with both a key and an empty argument array.
	 * 
	 * @param key
	 *            A <strong>String</strong> which is mapped to a initial String
	 *            within the ResourceBundle.
	 * @return A <strong>String</strong> which has been referenced by the key
	 *         String and that contains no argument fields.
	 */
	public String get(String key)
	{
		return get(key, null);
	}

        public static String processArgs(String initial, String args[])
        {
	    // If the string contains arguments we have to insert them.
	    StringBuffer complete = new StringBuffer();
	    // While we still have initial string left.
	    while (initial.length() > 0 && initial.indexOf('{') != -1 && initial.indexOf('}') != -1)
		{
		    // Remove preamble
		    int opening = initial.indexOf('{');
		    int closing = initial.indexOf('}');
		    int comment_mark = initial.indexOf('-', opening); // May not exist
		    if (comment_mark > closing)
			{ // May also be detecting a later comment
			    comment_mark = -1;
			}
		    complete.append(initial.substring(0, opening));
		    
		    // Parse arg_num
		    String arg_str = null;
		    if (comment_mark != -1)
			{
			    arg_str = initial.substring(opening + 1, comment_mark);
			}
		    else
			{
			    arg_str = initial.substring(opening + 1, closing);
			}
		    if (closing + 1 < initial.length())
			{
			    initial = initial.substring(closing + 1);
			}
		    else
			{
			    initial = "";
			}
		    int arg_num = Integer.parseInt(arg_str);
		    // Insert argument
		    if (args != null && 0 <= arg_num && arg_num < args.length)
			{
			    complete.append(args[arg_num]);
			}
		}
	    complete.append(initial);
	    return complete.toString();	    
	}
    
	/**
	 * Used to retrieve a property value from the Locale specific
	 * ResourceBundle, based upon the key and arguments supplied. If the key
	 * cannot be found or if some other part of the call fails a default
	 * (English) error message is returned. <BR>
	 * Here the get recieves a second argument which is an array of Strings used
	 * to populate argument fields, denoted {<I>n</I>}, within the value String
	 * returned.
	 * 
	 * @param key
	 *            A <strong>String</strong> which is mapped to a initial String
	 *            within the ResourceBundle.
	 * @param args
	 *            A <strong>String[]</strong> used to populate argument fields
	 *            within the complete String.
	 * @return A <strong>String</strong> which has been referenced by the key
	 *         String and that either contains no argument fields, or has had
	 *         the argument fields populated with argument Strings provided in
	 *         the get call.
	 */
	public String get(String key, String args[])
	{
	    
		if (this.raw == null)
		{
			return null;
		}
		try
		{
		        // In older versions of Java, the resource-bundle/properties file was assumed to be "ISO-8859-1", and so
			// we used to perform a string conversion here so we could support UTF-8
			// With the introduction of UTF8Control, based on a StackOverflow posting, we now directly work
			// with the resource as UTF-8, and so not conversion is needed
			String initial = this.raw.getString(key);
			// if we haven't been given any args, don't bother looking for them
			if (args == null)
			{
				return initial;
			}

			return processArgs(initial,args);
		}
		catch (Exception e)
		{
		  //logger.debug("Dictionary Error: couldn't find string for key:" + key +" in resource "+this.resource);
			return null;
		}
	}

    
    public static String getTextString(String key, String lang, String[] args, String dictionary_name, Class class_obj, String stop_at_class, ClassLoader class_loader)
  {
      if (dictionary_name == null && class_obj == null) {
	  // no dictionary or class was specified, just use the default
	  // dictionary
	  dictionary_name = core_servlet_dictionary_name;
      }
      
      if (dictionary_name != null)
      {
        // just try the one specified dictionary
        return createDictionaryAndGetString(dictionary_name, class_loader, key, lang, args);
      }

      // try the class names first before the default dictionary
      String original_class_name = class_obj.getName();
      original_class_name = original_class_name.substring(original_class_name.lastIndexOf('.') + 1);
      String result = createDictionaryAndGetString(original_class_name, class_loader, key, lang, args);
      if (result != null)
      {
	  return result;
      }

      // we have to try super classes
      String class_name;
      Class c = class_obj.getSuperclass();
      while (c != null)
      {
	  class_name = c.getName();
	  class_name = class_name.substring(class_name.lastIndexOf('.') + 1);
	  if (class_name.equals(stop_at_class))
	  {
	      // this is as far as we go
	      break;
	  }
          result = createDictionaryAndGetString(class_name, class_loader, key, lang, args);
	  if (result != null) {
	      return result;
	  }
	  c = c.getSuperclass();
      }

      // if we got here, class names were no help, try the default dictionary

      // First we try the key qualified with the original class name
      // (eg have TextQuery.name and LuceneSearch.TextQuery.name in the
      // default properties file)
      String full_key = original_class_name+"."+key;
      result = createDictionaryAndGetString(core_servlet_dictionary_name, class_loader, full_key, lang, args);
      if (result == null) {
        // try the key by itself without class qualified prefix
        result = createDictionaryAndGetString(core_servlet_dictionary_name, class_loader, key, lang, args);
      }
      return result;
       
  }

  public static String createDictionaryAndGetString(String dictionary_name, ClassLoader class_loader, String key, String lang, String[] args) {
    Dictionary dict = new Dictionary(dictionary_name, lang, class_loader);
    String result = dict.get(key, args);
    // if we have a pending flag, then try the base language
    if (result != null && result.startsWith(PENDING_STR) && !lang.equals(fallback_language)) {
      dict = new Dictionary(dictionary_name, fallback_language, class_loader);
      result = dict.get(key, args);
    }
    return result;
  }
  

 
}
