//
// Jala Project [http://opensvn.csie.org/traccgi/jala]
//
// Copyright 2004 ORF Online und Teletext GmbH
//
// Licensed 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.
//
// $Revision$
// $LastChangedBy$
// $LastChangedDate$
// $HeadURL$
//

/**
 * @fileoverview Methods and macros for internationalization
 * of Helma applications.
 */

// Define the global namespace for Jala modules
if (!global.jala) {
   global.jala = {};
}

/**
 * Constructs a new instance of jala.I18n
 * @class This class provides various functions and macros for
 * internationalization of Helma applications.
 * @constructor
 * @type jala.I18n
 */
jala.I18n = function() {
   /**
    * The default object containing the messages.
    * @ignore
    */
   var messages = global.messages;

   /**
    * The default method for retrieving the locale.
    * @ignore
    */
   var localeGetter = function() {
      return java.util.Locale.getDefault();
   };

   /**
    * Overwrite the default object containing
    * the messages (ie. a vanilla EcmaScript object).
    * @param {Object} msgObject The object containing the messages
    */
   this.setMessages = function(msgObject) {
      messages = msgObject;
   };

   /**
    * Get the message object.
    * @returns The object containing the messages
    * @type Object
    */
   this.getMessages = function() {
      return messages;
   };

   /**
    * Set the method for retrieving the locale.
    * @param {Function} func The getter method
    */
   this.setLocaleGetter = function(func) {
      if (func && func.constructor == Function) {
         localeGetter = func;
      } else {
         throw Error("Getter method to retrieve locale must be a function");
      }
      return;
   };

   /**
    * Get the method for retrieving the locale.
    * @returns The getter method
    * @type Function
    */
   this.getLocaleGetter = function() {
      return localeGetter;
   };

   return this;
};

/**
 * The default handler containing the messages.
 * @ignore
 */
jala.I18n.HANDLER = global;

/** @ignore */
jala.I18n.prototype.toString = function() {
   return "[Jala i18n]";
};

/**
 * Set (overwrite) the default handler containing
 * the messages (ie. a vanilla EcmaScript object).
 * @param {Object} handler The handler containing the message object
 * @deprecated Use {@link #setMessages} instead
 */
jala.I18n.prototype.setHandler = function(handler) {
   this.setMessages(handler.messages);
   return;
};

/**
 * Returns the locale for the given id, which is expected to follow
 * the form <code>language[_COUNTRY][_variant]</code>, where <code>language</code>
 * is a valid ISO Language Code (eg. "de"), <code>COUNTRY</code> a valid ISO
 * Country Code (eg. "AT"), and variant an identifier for the variant to use.
 * @returns The locale for the given id
 * @type java.util.Locale
 */
jala.I18n.prototype.getLocale = function(localeId) {
   if (localeId) {
      if (localeId.indexOf("_") > -1) {
         var arr = localeId.split("_");
         if (arr.length == 3) {
            return new java.util.Locale(arr[0], arr[1], arr[2]);
         } else {
            return new java.util.Locale(arr[0], arr[1]);
         }
      } else {
         return new java.util.Locale(localeId);
      }
   }
   return java.util.Locale.getDefault();
}

/**
 * Tries to "translate" the given message key into a localized
 * message.
 * @param {String} key The message to translate (required)
 * @param {String} plural The plural form of the message to translate
 * @param {Number} amount A number to determine whether to use the
 * singular or plural form of the message
 * @returns The localized message or the appropriate key if no
 * localized message was found
 * @type String
 */
jala.I18n.prototype.translate = function(singularKey, pluralKey, amount) {
   var translation = null;
   if (singularKey) {
      // use the getter method for retrieving the locale
      var locale = this.getLocaleGetter()();
      var catalog, key;
      if ((catalog = jala.i18n.getCatalog(locale))) {
         if (arguments.length == 3 && amount != 1) { // is plural
            key = pluralKey;
         } else {
            key = singularKey;
         }
         if (!(translation = catalog[key])) {
            translation = key;
            app.logger.debug("jala.i18n.translate(): Can't find message '" +
                             key + "' for locale '" + locale + "'");
         }
      } else {
         app.logger.debug("jala.i18n.translate(): Can't find message catalog for locale '" + locale + "'");
         if (!pluralKey || amount == 1) {
            translation = singularKey;
         } else {
            translation = pluralKey;
         }
      }
   }
   return translation;
};

/**
 * Helper method to get the message catalog
 * corresponding to the actual locale.
 * @params {java.util.Locale} locale
 * @returns The message catalog.
 */
jala.I18n.prototype.getCatalog = function(locale) {
   if (!jala.I18n.catalogs) {
      jala.I18n.catalogs = {};
   }

   var catalog = jala.I18n.catalogs[locale];

   if (catalog) return catalog;

   var messages = this.getMessages();

   if (locale && messages) {
      catalog = messages[locale.toLanguageTag()];
      jala.I18n.catalogs[locale] = catalog;
   }

   return catalog;
};

/**
 * Converts the message passed as argument into an instance
 * of java.text.MessageFormat, and formats it using the
 * replacement values passed.
 * @param {String} message The message to format
 * @param {Array} values An optional array containing replacement values
 * @returns The formatted message or, if the formatting fails, the
 * message passed as argument.
 * @type String
 * @see http://java.sun.com/j2se/1.5.0/docs/api/java/text/MessageFormat.html
 */
jala.I18n.prototype.formatMessage = function(message, values) {
   if (message) {
      var args = null;
      if (values != null && values.length > 0) {
         args = java.lang.reflect.Array.newInstance(java.lang.Object, values.length);
         var arg;
         for (var i=0;i<values.length;i++) {
            if ((arg = values[i]) != null) {
               // MessageFormat can't deal with javascript date objects
               // so we need to convert them into java.util.Date instances
               if (arg instanceof Date) {
                  args[i] = new java.util.Date(arg.getTime());
               } else {
                  args[i] = arg;
               }
            }
         }
      }
      // use the getter method for retrieving the locale
      var locale = this.getLocaleGetter()();
      // format the message
      try {
         var msgFormat = new java.text.MessageFormat(message, locale);
         return msgFormat.format(args);
      } catch (e) {
         app.logger.warn("jala.i18n.formatMessage(): Unable to format message '"
                         + message + "', reason: " + e, e.javaException);
      }
   }
   return null;
};

/**
 * Returns a localized message for the message key passed as
 * argument. If no localization is found, the message key
 * is returned. Any additional arguments passed to this function
 * will be used as replacement values during message rendering.
 * To reference these values the message can contain placeholders
 * following "{number}" notation, where <code>number</code> must
 * match the number of the additional argument (starting with zero).
 * @param {String} key The message to localize
 * @returns The translated message
 * @type String
 * @see #translate
 * @see #formatMessage
 */
jala.I18n.prototype.gettext = function(key /** [value 0][, value 1][, ...] */) {
   return this.formatMessage(this.translate(key),
                             Array.prototype.splice.call(arguments, 1));
};

/**
 * Returns a localized message for the message key passed as
 * argument. In contrast to gettext() this method
 * can handle plural forms based on the amount passed as argument.
 * If no localization is found, the appropriate message key is
 * returned. Any additional arguments passed to this function
 * will be used as replacement values during message rendering.
 * To reference these values the message can contain placeholders
 * following "{number}" notation, where <code>number</code> must
 * match the number of the additional argument (starting with zero).
 * @param {String} singularKey The singular message to localize
 * @param {String} pluralKey The plural form of the message to localize
 * @param {Number} amount The amount which is used to determine
 * whether the singular or plural form of the message should be returned.
 * @returns The translated message
 * @type String
 * @see #translate
 * @see #formatMessage
 */
jala.I18n.prototype.ngettext = function(singularKey, pluralKey, amount /** [value 0][, value 1][, ...] */) {
   return this.formatMessage(this.translate(singularKey, pluralKey, amount || 0),
                             Array.prototype.splice.call(arguments, 2));
};

/**
 * A simple proxy method which is used to mark a message string
 * for the i18n parser as to be translated.
 * @param {String} key The message that should be seen by the
 * i18n parser as to be translated.
 * @returns The message in unmodified form
 * @type String
 */
jala.I18n.prototype.markgettext = function(key) {
   return key;
};

/**
 * Returns a translated message. The following macro attributes
 * are accepted:
 * <ul>
 * <li>text: The message to translate (required)</li>
 * <li>plural: The plural form of the message</li>
 * <li>values: A list of replacement values. Use a comma to separate more
 * than one value. Each value is either interpreted as a global property
 * (if it doesn't containg a dot) or as a property name of the given macro
 * handler object (eg. "user.name"). If the value of the property is a
 * HopObject or an Array this macro uses the size() resp. length of the
 * object, otherwise the string representation of the object will be used.</li>
 * </ul>
 * @returns The translated message
 * @type String
 * @see #gettext
 * @see #ngettext
 */
jala.I18n.prototype.message_macro = function(param) {
   if (param.text) {
      var args = [param.text];
      if (param.plural) {
         args[args.length] = param.plural;
      }
      if (param.values != null) {
         var arr = param.values.split(/\s*,\s*/g);
         // convert replacement values: if the value name doesn't contain
         // a dot, look for a global property with that name, otherwise
         // for a property of the specified macro handler object.
         var propName, dotIdx, handlerName, handler;
         for (var i=0;i<arr.length;i++) {
            if ((propName = arr[i]) != null) {
               var value = null;
               if ((dotIdx = propName.indexOf(".")) > 0) {
                  var handlerName = propName.substring(0, dotIdx);
                  if (handlerName == "request") {
                     handler = req.data;
                  } else if (handlerName == "response") {
                     handler = res.data;
                  } else if (!(handler = res.handlers[handlerName])) {
                     continue;
                  }
                  propName = propName.substring(dotIdx + 1);
                  // primitive security: don't allow access to internal properties
                  // and a property named "password"
                  if (propName.charAt(0) != "_" && propName.toLowerCase() != "password") {
                     value = handler[propName];
                  }
               } else {
                  value = global[propName];
               }
               if (value != null) {
                  // if its a HopObject collection or Array, use its size/length
                  // as value
                  if (value instanceof HopObject) {
                     value = value.size();
                  } else if (value instanceof Array) {
                     value = value.length;
                  }
               }
               args[args.length] = value;
            }
         }
      }
      if (param.plural) {
         return this.ngettext.apply(this, args);
      } else {
         return this.gettext.apply(this, args);
      }
   }
   return;
};

/**
 * Default i18n class instance.
 * @type jala.I18n
 * @final
 */
jala.i18n = new jala.I18n();

/**
 * For convenience reasons the public methods and macros are
 * put into global scope too
 */
var gettext = function() {
   return jala.i18n.gettext.apply(jala.i18n, arguments);
};
var ngettext = function() {
   return jala.i18n.ngettext.apply(jala.i18n, arguments);
};
var markgettext = function() {
   return jala.i18n.markgettext.apply(jala.i18n, arguments);
};
var message_macro = function() {
   return jala.i18n.message_macro.apply(jala.i18n, arguments);
};