importPackage(Packages.de.elo.ix.client);
importPackage(Packages.de.elo.ix.jscript);
importPackage(Packages.de.elo.ix.scripting);

//@include lib_Class.js
//@include lib_sol.common.StringUtils.js
//@include lib_sol.common.SordUtils.js
//@include lib_sol.common.UserProfile.js
//@include lib_sol.common.Locale.js
//@include lib_sol.common.ix.DynKwlUtils.js

/**
 * Search Iterator used by dynamic keyword lists. This class provides an abstract layer that simplifies the usage
 * of keywording information.
 *
 * If child elements should be returned please refer to the subclass sol.common.ix.DynKwlFindChildrenIterator
 * which implements that scenario. In most cases it is recommended to implement a specific scenario by a generalized
 * subclass.
 *
 * A DynKwlSearchIterator required an Index Server FindInfo object in order to find the required sord elements.
 * Thanks to a function `getRowData` the result of one row can be defined by keywording information.
 *
 * # Example implementation
 *
 * Following example shows the implementation of an iterator that returns child elements of a given parent.
 *
 *     // script: sol.common.ix.DynKwlFindChildrenIterator.js
 *
 *     sol.define('sol.common.ix.DynKwlFindChildrenIterator', {
 *       extend: 'sol.common.ix.DynKwlSearchIterator',
 *       tableHeaders: ["Name", "Description"],
 *       parentId: null,
 *       searchParams: [{mode: 'STARTS_WITH'}],
 *
 *       initialize: function (config) {
 *         config = config || {};
 *         this.parentId = config.parentId || this.parentId;
 *
 *         this.$super('sol.common.ix.DynKwlSearchIterator', 'initialize', arguments);
 *       },
 *
 *       // implement getFindInfo.
 *       getFindInfo: function(filterList) {
 *     	   var findInfo,
 *     	     findChildren, findByIndex;
 *
 *     	   findInfo = new FindInfo();
 *     	   findChildren = new FindChildren();
 *     	   findChildren.parentId = this.parentId;
 *         findInfo.findChildren = findChildren;
 *
 *         if (filterList && filterList.length > 0) {
 *           findByIndex = new FindByIndex();
 *           findByIndex.name = filterList[0];
 *           findInfo.findByIndex = findByIndex;
 *         }
 *
 *         return findInfo;
 *       },
 *
 *       getRowData: function(sord) {
 *     	   return [sord.name, sord.desc];
 *       }
 *     });
 *
 * Implementation of an Index Server data iterator that can be used by keywording forms:
 *
 *     // script: sol.pubsec.ix.dynkwl.generators.NameFile.js
 *
 *     sol.define('sol.pubsec.ix.dynkwl.generators.NameFile', {
 *       extend: 'sol.common.ix.DynKwlFindChildrenIterator',
 *
 *       tableTitle: 'Generators - File Name',
 *       tableKeyNames: ["FILE_NAME_GEN", null],
 *       parentId: '123'
 *
 *     });
 *
 *     function getDataIterator() {
 *       var iterator;
 *       iterator = sol.create('sol.pubsec.ix.dynkwl.generators.NameFile', {  });
 *       return new DynamicKeywordDataProvider(iterator);
 *     }
 *
 * @author NM, ELO Digital Office GmbH
 * @version 1.03.000
 *
 * @eloix
 * @requires sol.common.StringUtils
 * @requires sol.common.SordUtils
 * @requires sol.common.UserProfile
 * @requires sol.common.Locale
 * @requires sol.common.ix.DynKwlUtils
 */
sol.define("sol.common.ix.DynKwlSearchIterator", {
  mixins: ["sol.common.ix.DynKwlMixin"],

  /**
   * @cfg {string} tableTitle
   * name of this table. The title is displayed by the client.
   */
  tableTitle: undefined,
  /**
   * @cfg {de.elo.ix.client.SordZ} sordZ
   * Defines which information to retrieve from the search. e.g. `SordC.mbAll`. Default is `SordC.mbAllIndex`.
   */
  searchSordZ: SordC.mbAllIndex,
  /**
   * @cfg {Array} searchParams
   * Filter values that can be used for creating a FindInfo search definition.
   *
   *     [{ name: 'FILING_PLAN_REFERENCE', mode: 'STARTS', message: 'field x missing' }]
   *
   * if no name is provided current focused field is used.
   * These values are passed to the `getFindInfo` function.
   */
  searchParams: null,
  /**
   * @cfg {Number} searchCount
   * Number of search results to retrieve.
   * Please mind that results should be filtered by the user input.
   * Displaying more than 100 results is not recommended.
   */
  searchCount: 100,
  /**
   * @cfg {Array} tableKeyNames (required)
   * mapping between columns and elo group or map fields. if null, value is not transferred.
   *
   *     // GRP field     map static field           map table field
   *     ['RECORD_REFERENCE', null];
   *
   * Map fields must contain IX_MAP as a prefix and can optionally use {i} as a counter value.
   */
  tableKeyNames: undefined,
  /**
   * @cfg {Array} tableHeaders (required)
   * column header names. if null column is hidden in the client.
   *
   *     ["Name", "Description"]
   */
  tableHeaders: undefined,
  /**
   * @property
   * @private
   * Error message that is passed to the client if value is missing.
   */
  errorMessage: "",

  /**
   * @private
   * created findInfo object.
   */
  _findInfo: null,

  initialize: function (config) {
    this.log = sol.create("sol.Logger", { scope: this.$className || "sol.common.ix.SearchIterator" });
    this.log.enter("initialize", config);
    config = config || {};

    if ((!this.searchParams && !config.searchParams)
      || (!this.tableKeyNames && !config.tableKeyNames)
      || (!this.tableHeaders && !config.tableHeaders)) {
      this.log.error("Dynamic keyword list: searchParams, keyNameTemplate and tableHeaders must be set.");
    }

    this.tableTitle = config.tableTitle || this.tableTitle;
    this.searchParams = config.searchParams || this.searchParams;
    this.searchSordZ = config.searchSordZ || this.searchSordZ;
    this.searchCount = config.searchCount || this.searchCount;
    this.tableKeyNames = config.tableKeyNames || this.tableKeyNames;
    this.tableHeaders = config.tableHeaders || this.tableHeaders;
    this.focusFieldGivesValueForMap = !!config.focusFieldGivesValueForMap;

    this.connection = config.connection || ixConnect;

    this.errorMessage = "";
    this.log.exit("initialize");
  },

  /**
   * Opens a connection for the elo java client and non map field capable clients.
   *
   * @param {de.elo.ix.scripting.ScriptExecContext} ec
   * @param {de.elo.ix.client.Sord} sord working version of the current sord object
   * @param {String} fieldName name of the currently focused field
   */
  open: function (ec, sord, fieldName) {
    this.log.enter("open", { sord: sord, fieldName: fieldName });

    this._keyNames = this.getTableKeyNames(fieldName);
    this.focusValue = sol.common.SordUtils.getObjKeyValue(sord, fieldName) || "";

    this.index = 0;
    this._findInfo = this.getFindInfo(this.createSearchFilterList(ec, fieldName, null, null, sord));
    this.resultSet = this.getSearchResults();
    this.log.exit("open");
  },

  /**
   * Opens a connection for elo wf forms and map field capable components
   *
   * @param {de.elo.ix.scripting.ScriptExecContext} ec
   * @param {Object} map Map of all entries passed by the client
   * @param {String} focusName Name of the currently focused field
   */
  openMap: function (ec, map, focusName) {
    var fieldIndex;

    this.log.enter("openMap", { focusName: focusName, map: map });

    this.focusValue = map[focusName] || "";

    fieldIndex = this.getIndexFromName(focusName);
    this._keyNames = this.getTableKeyNames(focusName).map(function (keyName) {
      return !!keyName ? ((fieldIndex != "") ? keyName.replace("{i}", fieldIndex) : keyName) : null;
    });

    this.index = 0;
    this._findInfo = this.getFindInfo(this.createSearchFilterList(ec, focusName, fieldIndex, map, null));
    this.resultSet = this.getSearchResults();
    this.log.exit("openMap");
  },

  /**
   * Closes the connection for both map and non map capable clients.
   */
  close: function () {
    this.log.enter("close");
    this._findInfo = null;
    this.log.exit("close");
  },

  /**
   * Returns the next row of the table.
   *
   * @return {String[]} Table row
   */
  getNextRow: function () {
    var row = this.resultSet[this.index++];
    if (row) {
      row = this.getRowData(row);
      this.formatRow(row);
      return row;
    }
  },

  /**
   * @abstract
   * This function must be implemented by the child class. It should return the data as string[] for one table row.
   * @param {de.elo.ix.client.Sord} sord
   * @return {String[]} Table row
   */
  getRowData: function (sord) {
    throw this.$class + ": getRowData must be implemented by child class.";
  },

  /**
   * @abstract
   * This function must be implemented by the child class. It should return an Index Server FindInfo object that is used by the search.
   *
   *     getFindInfo: function(filterList) {
   *       this.log.enter("getFindInfo");
   *       var findInfo,
   *       findChildren, findByIndex;
   *
   *       findInfo = new FindInfo();
   *       findChildren = new FindChildren();
   *       findChildren.parentId = this.parentId;
   *       findInfo.findChildren = findChildren;
   *
   *       if (filterList && filterList.length > 0) {
   *         findByIndex = new FindByIndex();
   *         findByIndex.name = filterList[0];
   *         findInfo.findByIndex = findByIndex;
   *       }
   *
   *       this.log.exit("getFindInfo");
   *       return findInfo;
   *     },
   *
   * @param {String[]} filter
   * @return {de.elo.ix.client.FindInfo}
   */
  getFindInfo: function (filter) {
    throw this.$class + ": getFindInfo must be implemented by child class.";
  },

  /**
   * Returns the header of this table that can be displayed by the clients.
   *
   * @return {String[]} Table header
   */
  getHeader: function () {
    return this.tableHeaders;
  },

  /**
   * Returns the keys of this table that can be used in order to map
   * map or group fields with columns.
   *
   * @return {String[]} Table keys
   */
  getKeyNames: function () {
    return this._keyNames;
  },

  /**
   * Returns true if table has more rows.
   *
   * @return {Boolean} Has more rows
   */
  hasMoreRows: function () {
    return (this.index < (this.resultSet.length));
  },

  /**
   * Returns the error message that should be displayed by the client
   * instead of the table data.
   *
   * @return {String} error message
   */
  getMessage: function () {
    return this.errorMessage;
  },

  /**
   * Returns a title for this table used by the user interface.
   *
   * @return {String} Title
   */
  getTitle: function () {
    return this.tableTitle;
  },

  /**
   * @private
   * Retrieves a list of search results based on the given FindInfo object. Result list is limited to 100 items.
   * @return {de.elo.ix.client.Sord[]}
   */
  getSearchResults: function () {
    this.log.enter("getSearchResults", this._findInfo);
    var findResult, sords;

    findResult = this.connection.ix().findFirstSords(this._findInfo, this.searchCount, this.searchSordZ);
    sords = findResult.sords || [];

    this.connection.ix().findClose(findResult.searchId);
    this.log.info("found sords: " + sords.length);

    this.log.exit("getSearchResults");

    return sords;
  },

  /**
   * Internal function that utilizes the creation of filters used by the creation of findInfo objects.
   *
   * @param {de.elo.ix.scripting.ScriptExecContext} ec IX ScriptExecContext
   * @param {String} focusField Currently focused field
   * @param {String} fieldIndex
   * @param {Object} map Map of all entries passed by the client
   * @param {de.elo.ix.client.Sord} sord working version of the current sord object
   * @returns {String[]} Values for prepared statement
   */
  createSearchFilterList: function (ec, focusField, fieldIndex, map, sord) {
    this.log.enter("createSearchFilterList", { focusField: focusField, fieldIndex: fieldIndex, map: map, sord: sord });
    var list = [],
        i, param, fieldName, value, mapValueField;

    this.errorMessage = "";

    this.log.debug("defined searchParams: ", this.searchParams);

    for (i = 0; i < this.searchParams.length; i++) {
      param = this.searchParams[i];
      fieldName = param.name || focusField;


      if (param.value) {
        value = param.value;
      } else if (param.valueType) {
        switch (param.valueType) {
          case "LANGUAGE":
            value = ec.ci.language;
            break;
          default:
            value = "";
        }
      } else if (map) {
        // WF currently passes IX_GRP for group fields.
        // This must be fixed so SQL-Queries can be used in mixed mode.
        mapValueField = this.focusFieldGivesValueForMap ? focusField : fieldName;
        if (fieldIndex) {
          mapValueField = String(mapValueField).replace("{i}", fieldIndex);
        }
        value = (map[mapValueField] || map["IX_GRP_" + mapValueField]) || "";
      } else {
        value = sol.common.SordUtils.getObjKeyValue(sord, fieldName) || "";
      }

      if (!value && param.message) {
        this.errorMessage = param.message;
      }

      if ((param.emptyAllowed === true) && (!value || value == "")) { // BSVM-266, BSVM-270 allow empty values
        list.push(null);
      } else {
        switch (param.mode) {
          case "STARTS_WITH":
            list.push(value + "*");
            break;
          case "CONTAINS":
            list.push("*" + value + "*");
            break;
          case "ENDS_WITH":
            list.push("*" + value);
            break;
          default:
            list.push(String(value));
        }
      }

    }
    this.log.exit("createSearchFilterList");
    return list;
  },

  getIndexFromName: function (name) {
    name = String(name);
    if (!name) {
      return "";
    }
    var pos = name.search(/\d+$/);
    if (pos > 0) {
      return name.substring(pos);
    }
    return "";
  }
});