/**
 * Collects all elements with a due date.
 *
 * Retrospection: searches for next run dates in the past, so it doesn't miss any object in case, one or more executions went wrong.
 *
 * The search can be limited to a specific mask by configuration.
 * The default retrospection time in days can be overridden by configuration.
 * The default field containing the due date can be overridden by configuration.
 *
 *     var collector = sol.create("sol.common_monitoring.as.collectors.NextRunCollector", {
 *       soltype: "CONTRACT"
 *       mask: "My Mask",
 *       retrospection: 27,
 *       nextRunField: "MY_DUE_DATE"
 *     });
 *
 * @eloas
 *
 * @requires sol.common.SordUtils
 * @requires sol.common.DateUtils
 */
sol.define("sol.common_monitoring.as.collectors.NextRunCollector", {

  /**
   * @cfg {String} [nextRunField="NEXT_RUN"] (optional) Spezifies, which field is used to search for the due items
   */

  /**
   * @cfg {Number} [retrospection=365] (optional) Spezifies, how long the rule looks back for next_run dates from now (in days) (see also {@link #defaultRetrospectionTime})
   */

  /**
   * @cfg {Number} [searchPeriodEndDays=0] (optional) Spezifies the end of the search period, e.g. `1` for `until yesterday`
   */

  /**
   * @cfg {String} mask (optional) If set, due items will be filtered by mask
   */

  /**
   * @cfg {String} soltype (optional) If set, due items will be filtered by solution type (GRP-field `SOL_TYPE`)
   */

  /**
   * @cfg {Object[]} additionalSearchCriteria (optional) additional criteria which will be used in the initial search for sords. multiple criterions will be "AND"ed
   * @cfg {Object} additionalSearchCriteria.Object the criterion
   * @cfg {String} additionalSearchCriteria.Object.key the GRP-field name
   * @cfg {String|Array} additionalSearchCriteria.Object.value the value, which should be searched for
   */

  /**
   * @private
   * @property {String} defaultNextRunField
   */
  defaultNextRunField: "NEXT_RUN",

  /**
   * @private
   * @property {String} defaultRetrospectionTime
   */
  defaultRetrospectionTime: 365,

  /**
   * @private
   * @property {de.elo.ix.client.SordZ} defaultSordZ
   */
  defaultBatchSize: 1000,

  /**
   * @private
   * @property {de.elo.ix.client.SordZ} defaultSordZ
   */
  defaultSordZ: SordC.mbAllIndex,

  utils: {
    log: {
      findInfo: function (self, findInfo) {
        var findByIndex = (findInfo || {}).findByIndex || {},
            name = ((findByIndex.objKeys || [])[0] || {}).name,
            data = (((findByIndex.objKeys || [])[0] || {}).data || {})[0],
            maskId = findByIndex.maskId;

        self.logger.debug(["perform search: nextRunField={0}, lookupPeriod={1}, limitToMask={2}", name, data, maskId]);
      },
      postProcess: function (self, params) {
        params = params || {};
        self.logger.info([
          "update next run date(objId={0}, objKey={1}): oldValue='{2}', newValue='{3}'",
          (params.sord || {}).id,
          self.nextRunField,
          params.currentNextRunDate,
          params.newNextRunDate
        ]);
      }
    }
  },

  initialize: function (config) {
    var me = this;
    me.$super("sol.Base", "initialize", [config]);
    me.nextRunField = (typeof me.nextRunField !== "undefined") ? me.nextRunField : me.defaultNextRunField;
    me.retrospection = (me.retrospection || (me.retrospection === 0)) ? me.retrospection : me.defaultRetrospectionTime;
    me.searchPeriodEndDays = (typeof me.searchPeriodEndDays !== "undefined") ? me.searchPeriodEndDays : 0;
    me._sordZ = me.defaultSordZ;
    me._batchSize = me.defaultBatchSize;
    me._findInfo = me.createFindInfo();
    me._hasMoreResults = true;
  },

  /**
   * Checks, if this collector has additional results.
   * @return {Boolean}
   */
  hasMoreResults: function () {
    var me = this;
    return me._hasMoreResults;
  },

  /**
   * Retrieves the next batch of work items.
   * @return {de.elo.ix.client.Sord[]}
   */
  getResults: function () {
    var me = this;

    me.logger.enter("getResults");

    me._workingSet = [];
    me._hasMoreResults = false;

    try {
      me.utils.log.findInfo(me, me._findInfo);

      if (!me._findResult) {
        me._idx = 0;
        me._findResult = ixConnect.ix().findFirstSords(me._findInfo, me._batchSize, me._sordZ);
      } else {
        me._findResult = ixConnect.ix().findNextSords(me._findResult.searchId, me._idx, me._batchSize, me._sordZ);
      }

      me._workingSet = me._findResult.sords;
      me._hasMoreResults = me._findResult.moreResults;
      me._idx += me._findResult.sords.length;

    } finally {
      if ((me._hasMoreResults === false) && me._findResult) {
        ixConnect.ix().findClose(me._findResult.searchId);
        me._findResult = null;
      }
    }

    me.logger.info(["return result batch: found {0} entries (hasMoreResults={1})", (me._workingSet || []).length, me._hasMoreResults]);
    me.logger.exit("getResults");

    return me._workingSet;
  },

  /**
   * Checks, if a result set contains any instruction for further executions (`nextRun`) and updates a sord with the most recent.
   * @param {de.elo.ix.client.Sord} sord
   * @param {Object[]} results The result object returned by an `analyzer`
   */
  postProcess: function (sord, results) {
    var me = this,
        newNextRunDate = "",
        postprocessingRequired = false,
        currentNextRunDate;

    me.logger.enter("postProcess");

    if (results && (results.length > 0)) {
      results.forEach(function (r) {
        if (r.nextRun) {
          newNextRunDate = (!newNextRunDate) ? r.nextRun : ((newNextRunDate < r.nextRun) ? newNextRunDate : r.nextRun);
          postprocessingRequired = true;
        }
      });
    }

    if (postprocessingRequired && me.nextRunField) {
      currentNextRunDate = sol.common.SordUtils.getObjKeyValue(sord, me.nextRunField);
      me.logger.debug(["next runs: currentNextRunDate={0}, newNextRunDate={1}", currentNextRunDate, newNextRunDate]);

      if (currentNextRunDate != newNextRunDate) {
        sol.common.SordUtils.setObjKeyValue(sord, me.nextRunField, newNextRunDate);
        ixConnect.ix().checkinSord(sord, me._sordZ, LockC.NO);

        me.utils.log.postProcess(me, { sord: sord, currentNextRunDate: currentNextRunDate, newNextRunDate: newNextRunDate });
      }
    } else {
      me.logger.debug("no 'nextRuns' defined, skip post processing");
    }

    me.logger.exit("postProcess");
  },

  /**
   * Performes cleanup by closeing the search and removing the current working set.
   */
  dispose: function () {
    var me = this;
    if (me._findResult) {
      ixConnect.ix().findClose(me._findResult.searchId);
      me._findResult = null;
    }
    me._workingSet = null;
  },

  /**
   * @private
   * Creates a FindInfo object for the search using {@link #nextRunField}, {@link #getLookupPeriod} and {@link #mask}.
   * @return {de.elo.ix.client.FindInfo}
   */
  createFindInfo: function () {
    var me = this,
        fi = new FindInfo(),
        fbi = new FindByIndex(),
        objKeys = [],
        nextRunObjKey = new ObjKey(),
        soltypeObjKey = new ObjKey();


    if (me.nextRunField) {
      nextRunObjKey.name = me.nextRunField;
      nextRunObjKey.data = [me.getLookupPeriod()];
      objKeys.push(nextRunObjKey);
    }

    if (me.mask) {
      fbi.maskId = me.mask;
    }

    if (me.soltype) {
      soltypeObjKey.name = "SOL_TYPE";
      soltypeObjKey.data = [me.soltype];
      objKeys.push(soltypeObjKey);
    }

    if (Array.isArray(me.additionalSearchCriteria)) {
      me.additionalSearchCriteria.forEach(function (criterion) {
        var objKey = new ObjKey();
        if (typeof criterion === "object" && criterion.key && (typeof criterion.value === "string") || Array.isArray(criterion.value)) {
          objKey.name = criterion.key;
          objKey.data = Array.isArray(criterion.value) ? criterion.value : [criterion.value];
          objKeys.push(objKey);
        } else {
          throw "an additional search criterion must consist of an object having a key and a value property";
        }
      });
    } else if (me.additionalSearchCriteria !== undefined) {
      throw "parameter additionalSearchCriteria must be an array";
    }

    fbi.objKeys = objKeys;
    fi.findByIndex = fbi;
    return fi;
  },

  /**
   * @private
   * Calculates the lookup period for the search using {@link #retrospection}.
   * The period String has the following form:
   *
   *     "YYYYMMDD...YYYYMMDD"
   *
   * where the second ISO string is always the current date.
   * If {@link #retrospection} is set to 0, just the current date will be returned in ISO form.
   *
   * @return {String}
   */
  getLookupPeriod: function () {
    var me = this,
        isoPattern = "YYYYMMDD",
        isoFrom, isoNow, isoEnd, retrospectionDays, searchPeriodEndDays, searchPeriod;

    if (retrospectionDays === 0) {
      isoNow = sol.common.DateUtils.format(new Date(), isoPattern);
      return isoNow;
    }

    retrospectionDays = -Math.abs(me.retrospection);
    searchPeriodEndDays = -Math.abs(me.searchPeriodEndDays);

    isoFrom = sol.common.DateUtils.shift(new Date(), retrospectionDays, { pattern: isoPattern });
    isoEnd = sol.common.DateUtils.shift(new Date(), searchPeriodEndDays, { pattern: isoPattern });
    searchPeriod = isoFrom + "..." + isoEnd;
    return searchPeriod;
  }
});