//@include lib_Class.js
//@include lib_sol.common.ObjectUtils.js
//@include lib_sol.common.Cache.js

/*
 * Local definition of the class `sol.common.Cache` for backward compatibility of previous solution packages.
 */
if (!sol.ClassManager.getClass("sol.common.Cache")) {
  sol.define("sol.common.Cache", {

    initialize: function (config) {
      var me = this;
      me.cache = new java.util.concurrent.ConcurrentHashMap(8, 0.9, 1);
    },

    /**
     * Inserts the specified key-value pair into the cache.
     * @param {String} key
     * @param {Object} value
     * @return {Object} The previous value associated with the key, or null if there was no mapping before
     */
    put: function (key, value) {
      var me = this;
      return me.cache.put(key, value);
    },

    /**
     * Inserts all key-value pairs specified by an object into the cache. Existing mappings will be replaced.
     * @param {Object} data Property names will be used as keys and the associated values as values.
     */
    putAll: function (data) {
      var me = this;
      me.cache.putAll(data);
    },

    /**
     * Tests if the specified object is a key in the cache.
     * @param {String} key
     * @return {Boolean}
     */
    containsKey: function (key) {
      var me = this;
      return me.cache.containsKey(key);
    },

    /**
     * Returns the value for the specified key from the cache, or null if the chache contains no mapping for the key.
     * @param {String} key
     * @return {Object}
     */
    get: function (key) {
      var me = this;
      return me.cache.get(key);
    },

    /**
     * Returns an enumeration of all keys in the cache.
     * @return {Object} An `java.util.Enumeration` of all keys
     */
    keys: function () {
      var me = this;
      return me.cache.keys();
    },

    /**
     * Returns a collection view of the values contained in the cache.
     * @return {Object} An `java.util.Collection` of all values
     */
    values: function () {
      var me = this;
      return me.cache.values();
    },

    /**
     * Returns an enumeration of the values in the cache.
     * @return {Object} An `java.util.Enumeration` of all values
     */
    elements: function () {
      var me = this;
      return me.cache.elements();
    },

    /**
     * Removes the key (and its corresponding value) from the cache.
     * @param {String} key
     * @return {Object} The previous value associated with the key, or null if there was no value for the key
     */
    remove: function (key) {
      var me = this;
      return me.cache.remove(key);
    },

    /**
     * Returns the number of key-value pairs in the cache.
     * @return {Number}
     */
    size: function () {
      var me = this;
      return me.cache.size();
    },

    /**
     * Returns `true` if the chache contains no key-value pairs.
     * @return {Boolean}
     */
    isEmpty: function () {
      var me = this;
      return me.cache.isEmpty();
    },

    /**
     * Removes all of the mappings from the cache.
     */
    clear: function () {
      var me = this;
      me.cache.clear();
    }
  });
}

/**
 * Cache for Translate Terms. This class handles localization in Business Solutions.
 *
 * Property files should be placed in `Administration\Localization\`.
 *
 * ELO internal projects must use `Administration\Localization\system`. Modifications and Partner implementations must
 * use `Administration\Localization\custom`.
 *
 * # Using localization in scripts
 *
 * Thanks to a require function call all required translation terms will get loaded in the cache. This must be done
 * before terms are used by scripts. Following example shows a common usage for a dynamic keyword list.
 *
 * A property file must be located in the localization folder. e.g. `Administration\Localization\system\sol.invoice.locale.properties`
 *
 *     sol.invoice.dynkwl.Company.ID=Company id
 *     sol.invoice.dynkwl.Company.NAME=Company name
 *     sol.invoice.dynkwl.Company.CITY=City
 *
 * The TranslateTerms utilizes the use of translation keys.
 *
 *     sol.common.TranslateTerms.require('sol.invoice.dynkwl.Company');
 *
 *     var tableHeaders = [
 *       sol.common.TranslateTerms.translate('sol.invoice.dynkwl.Company.ID'),
 *       sol.common.TranslateTerms.translate('sol.invoice.dynkwl.Company.NAME'),
 *       sol.common.TranslateTerms.translate('sol.invoice.dynkwl.Company.CITY')];
 *
 * @author PZ, ELO Digital Office GmbH
 * @version 1.03.000
 *
 * @eloix
 * @eloas
 *
 * @requires sol.common.ObjectUtils
 * @requires sol.common.Cache
 */
sol.define("sol.common.TranslateTerms", {
  singleton: true,

  /**
   * Loads a list of translation keys by a given prefix.
   * Prefixes can be either passed as an array or string.
   * @param {String} prefixes
   * @param {String} additionalLanguage (optional)
   */
  require: function (prefixes, additionalLanguage) {
    var me = this,
        requestedCount, idx, findTranslateTermInfo, findResult, i, translateTerm, j, language, term;

    me.logger.enter("require", arguments);
    if (!prefixes) {
      me.logger.warn("Translation term key prefix not set; use prefix 'sol'.");
      prefixes = ["sol"];
    }

    if (!sol.common.ObjectUtils.isArray(prefixes)) {
      prefixes = [prefixes];
    }

    requestedCount = prefixes.length;
    me.getLangs(additionalLanguage);
    prefixes = me.filterPrefixes(prefixes);

    if (prefixes.length > 0) {
      me.logger.debug(["Load {0} of {1} requested prefixes", requestedCount, prefixes.length]);
      me.translateTerms = me.translateTerms || sol.create("sol.common.Cache");

      try {
        findTranslateTermInfo = new FindTranslateTermInfo();
        findTranslateTermInfo.terms = prefixes;
        findTranslateTermInfo.langs = me.languages;
        idx = 0;
        findResult = ixConnect.ix().findFirstTranslateTerms(findTranslateTermInfo, 100);
        while (true) {
          for (i = 0; i < findResult.translateTerms.length; i++) {
            translateTerm = findResult.translateTerms[i];
            for (j = 0; j < translateTerm.langs.length; j++) {
              language = translateTerm.langs[j];
              term = translateTerm.termLangs[j] || "";
              if (!me.translateTerms.containsKey(language)) {
                me.translateTerms.put(language, sol.create("sol.common.Cache"));
              }
              me.translateTerms.get(language).put(translateTerm.translationKey, term);
            }
          }
          if (!findResult.moreResults) {
            break;
          }
          idx += findResult.translateTerms.length;
          findResult = ixConnect.ix().findNextTranslateTerms(findResult.searchId, idx, 100);
        }
        me.rememberPrefixes(prefixes);
      } finally {
        if (findResult) {
          ixConnect.ix().findClose(findResult.searchId);
        }
      }
    } else {
      me.logger.debug("All prefixes have already been cached.");
    }
    me.logger.exit("require");
  },

  /**
   * @private
   * Retrieves the system languages
   * @param {String} additionalLanguage (optional) Additional language
   */
  getLangs: function (additionalLanguage) {
    var me = this,
        langsTerm, i, lang;
    me.logger.enter("getLangs", arguments);
    if (me.languages) {
      me.addLang(additionalLanguage);
    } else {
      me.languages = [];
      langsTerm = ixConnect.ix().checkoutTranslateTerms([TranslateTermC.GUID_SYSTEM_LANGUAGES], LockC.NO);
      for (i = 0; i < langsTerm[0].langs.length; i++) {
        lang = langsTerm[0].langs[i];
        me.addLang(lang);
      }
    }
    me.logger.exit("getLangs", me.languages);
  },

  /**
   * @private
   * Adds a language
   * @param {String} language Language
   */
  addLang: function (language) {
    var me = this;
    language = String(language || "");
    if (language) {
      if (me.languages.indexOf(language) < 0) {
        me.languages.push(language);
        delete me.downloadedPrefixes;
      }
    }
  },

  /**
   * @private
   * Checks which prefixes have already been loaded.
   * @param {String[]} prefixes
   * @return {String[]}
   */
  filterPrefixes: function (prefixes) {
    var me = this,
        filteredCount = 0,
        filtered;
    if (me.downloadedPrefixes) {
      filtered = [];
      prefixes.forEach(function (prefix) {
        if (!me.downloadedPrefixes.containsKey(String(prefix))) {
          filtered.push(prefix);
        } else {
          filteredCount++;
        }
      });
    } else {
      filtered = prefixes;
    }
    me.logger.debug(["Filtered '{0}' prefixes that have already been loaded", filteredCount]);
    return filtered;
  },

  /**
   * @private
   * Saves the prefixes that have been loaded, to avoid repeated requests.
   * @param {String[]} prefixes
   */
  rememberPrefixes: function (prefixes) {
    var me = this;

    if (!me.downloadedPrefixes) {
      me.downloadedPrefixes = sol.create("sol.common.Cache");
    }
    if (me.downloadedPrefixes) {
      prefixes.forEach(function (prefix) {
        me.downloadedPrefixes.put(String(prefix), true);
      });
    }
  },

  /**
   * Get a translation for a key by a given language code.
   *
   *     sol.common.TranslationTerms.getTerm('de', sol.contract.ix.client');
   *
   * @param {String|de.elo.ix.client.ClientInfo} language Either an ISO language String, or an de.elo.ix.client.ClientInfo Object
   * @param {String} key The key in the resource files
   * @param {boolean} requestedTerm (optional) if not set, function requests term if not in list.
   * @return {String} result The value of the key
   */
  getTerm: function (language, key, requestedTerm) {
    var me = this,
        result;

    if (language instanceof ClientInfo) {
      language = language.language;
    }

    if (!language) {
      me.logger.warn("Language not set.");
      return key;
    }

    if (!me.translateTerms) {
      me.require();
    }

    if (!me.translateTerms.containsKey(language)) {
      me.translateTerms.put(language, sol.create("sol.common.Cache"));
    }

    result = me.translateTerms.get(language).get(key);
    if (!result) {
      if (!requestedTerm) {
        me.logger.debug("Translation key not found or cached. requesting key: " + key + ". This could be the case if used within templates. Please note that prefetching translation keys improves system performance.");
        me.require(key, language);

        return me.getTerm(language, key, true);
      } else {
        me.logger.warn("Translation key not found: " + key);
        return key;
      }
    }
    return result;
  },

  /**
   * Translates a key to the current language
   * @param {String} key The key in the resource files
   * @return {String} result The translated key
   *
   */
  translate: function (key) {
    var me = this,
        language, _result;

    me.logger.enter("translate", arguments);
    if (ixConnect) {
      language = ixConnect.loginResult.clientInfo.language;
    } else {
      throw "IX connection is not available.";
    }
    _result = String(me.getTerm(language, key));
    me.logger.exit("translate", _result);
    return _result;
  }
});