/**
 * Analyze a sord for spezified values.
 *
 * Each of the spezified values has to be meet.
 *
 * Each value has to define a `type` and a `key` (see {@link sol.common.SordUtils#getValue})
 *
 * # Value definitions
 *
 * Check if a field exists
 *
 *     { type: "GRP", key: "STATUS", exists: true }
 *
 * Check if a field has any value
 *
 *     { type: "GRP", key: "STATUS", notEmpty: true }
 *
 * Check if a field is empty
 *
 *     { type: "GRP", key: "STATUS", isEmpty: true }
 *
 * Compare field content to a reference value (with default comparator '==')
 *
 *     { type: "GRP", key: "STATUS", referenceValue: "UPDATE" }
 *
 * Compare field content to be greater than a reference value (in this case: is the saved ISO date in the map before the sord edit date)
 *
 *     { type: "MAP", key: "CHECK_ISO", referenceValue: { type: "SORD", key: "TStamp" }, comparator: "<" }
 *
 * Compare field content with several localized keyword list keys
 *
 *     { type: "GRP", key: "CONTRACT_STATUS", localizedKwl: true, referenceValues: ["D", "S"] }
 *
 * The value analyzer has a spezial type `PARENT_MAP`. This will try to check the values from a parent elements map.
 * Therefore the collector has to return a prefilled context with a `parentMap` property (like e.g. the {@link sol.common_monitoring.as.collectors.ChildrenCollector ChildrenCollector} does).
 * If the collector does not return such a property, it will be handled as empty.
 * Check if the parent has a map value STATUS.4711 (key.objId) for the sord with `sord.id=4711`:
 *
 *     { type: "PARENT_MAP", key: "STATUS", notEmpty: true }
 *
 * # Comparisons
 * If compared with a reference value, several comparators can be defined.
 *
 * Supported comparators are:
 *
 * - "==" equal (default)
 * - ">"  greater
 * - ">=" greater or equal
 * - "<"  smaller
 * - "<=" smaller or equal
 *
 * # Context enhancement
 * In addition to just check the values, they can be saved in an context object which can then be used in further processing.
 * E.g. the {@link sol.common_monitoring.as.executors.SimpleExecutor SimpleExecutor} can use those values in the action definitions.
 *
 *     var analyzer = sol.create("sol.common_monitoring.as.analyzers.ValueAnalyzer", {
 *       action: { type: "WORKFLOW", templateId: "{{ctx.updateWorkflow}}" },
 *       values: [
 *         { type: "MAP", key: "UPDATE_WF", notEmpty: true, toContext: "updateWorkflow" }
 *       ]
 *     });
 *
 * In case of a successfull check, the corresponding value will be written to the context object, which can then be used by the following components.
 *
 * @author PZ, ELO Digital Office GmbH
 * @version 1.02.000
 *
 * @eloas
 * @requires sol.common.StringUtils
 * @requires sol.common.SordUtils
 */
sol.define("sol.common_monitoring.as.analyzers.ValueAnalyzer", {

  requiredConfig: ["action", "values"],

  /**
   * @cfg {Object} action
   */

  /**
   * @cfg {Object[]} values
   */

  initialize: function (config) {
    var me = this;
    me.$super("sol.Base", "initialize", [config]);
  },

  /**
   * Analyzes an object for matches of set of values.
   * @param {de.elo.ix.client.Sord} sord
   * @param {Object} ctx Context object
   * @return {Object[]}
   */
  analyze: function (sord, ctx) {
    var me = this,
        results = [],
        allRequirementsMeet = true,
        i, max, valueCfg, checkFct, checkResult;

    me.logger.enter("analyze", { objId: sord.id, name: String(sord.name), valuesCount: me.values.length });

    for (i = 0, max = me.values.length; i < max; i++) {
      valueCfg = me.values[i];
      checkFct = me.noop; // this will the rule -> in case it has an unsupported configuration
      if (valueCfg.exists === true) {
        checkFct = me.exists;
      } else if (valueCfg.notEmpty === true) {
        checkFct = me.notEmpty;
      } else if (valueCfg.isEmpty === true) {
        checkFct = me.isEmpty;
      } else if (valueCfg.referenceValue || me.checkReferenceValues) {
        checkFct = me.checkReferenceValues;
      }

      checkResult = checkFct.call(me, sord, valueCfg, ctx);

      if (checkResult.success && ctx && valueCfg.toContext) {
        ctx[valueCfg.toContext] = checkResult.value;
      }

      allRequirementsMeet = allRequirementsMeet && checkResult.success;

      if (!allRequirementsMeet) {
        break; // skip further processing
      }
    }

    if (allRequirementsMeet) {
      results.push({ action: me.action });
    }

    me.logger.exit("analyze", results);

    return results;
  },

  /**
   * @private
   * If there is an illegal check configuration, this 'no operation' will be used to void check.
   * @return {Object}
   */
  noop: function () {
    return { success: false };
  },

  /**
   * @private
   * Checks, if the spezified field exists.
   * @param {de.elo.ix.client.Sord} sord
   * @param {Object} value
   * @param {Object} ctx
   * @return {Object}
   */
  exists: function (sord, value, ctx) {
    var me = this,
        result = { success: false },
        propertyValue;

    propertyValue = me.getValue(sord, value, ctx);

    result.success = ((typeof propertyValue !== "undefined") && (propertyValue !== null));
    result.value = propertyValue;

    return result;
  },

  /**
   * @private
   * Checks, if the spezified field has any value.
   * @param {de.elo.ix.client.Sord} sord
   * @param {Object} value
   * @param {Object} ctx
   * @return {Object}
   */
  notEmpty: function (sord, value, ctx) {
    var me = this,
        result = { success: false },
        propertyValue;

    propertyValue = me.getValue(sord, value, ctx);

    result.success = !sol.common.StringUtils.isEmpty(propertyValue);
    result.value = propertyValue;

    return result;
  },

  /**
   * @private
   * Checks, if the spezified field is empty.
   * @param {de.elo.ix.client.Sord} sord
   * @param {Object} value
   * @param {Object} ctx
   * @return {Object}
   */
  isEmpty: function (sord, value, ctx) {
    var me = this,
        propertyValue, result;

    result = { success: false };

    propertyValue = me.getValue(sord, value, ctx);

    result.success = sol.common.StringUtils.isEmpty(propertyValue);

    return result;
  },

  /**
   * @private
   * Checks, if a specific value is set on the sord.
   * @param {de.elo.ix.client.Sord} sord
   * @param {Object} value
   * @param {Object} ctx
   * @return {Object}
   */
  checkReferenceValues: function (sord, value, ctx) {
    var me = this,
        result = { success: false },
        referenceValueDefs, i, referenceValueDef,
        propertyValue, referenceValue;

    propertyValue = me.getValue(sord, value, ctx);
    referenceValueDefs = value.referenceValues || [value.referenceValue];

    for (i = 0; i < referenceValueDefs.length; i++) {
      referenceValueDef = referenceValueDefs[i];

      referenceValue = me.getValue(sord, referenceValueDef, ctx);

      if (propertyValue && referenceValue) {
        result.success = me.compare(propertyValue, referenceValue, value.comparator);
        result.value = propertyValue;
        if (result.success) {
          break;
        }
      }
    }

    return result;
  },

  /**
   * @private
   * Compares two values.
   * @param {String} value
   * @param {String} referenceValue
   * @param {String} comparator
   * @return {Boolean}
   */
  compare: function (value, referenceValue, comparator) {
    var compareResult;
    switch (comparator) {
      case ">":
        compareResult = (value > referenceValue);
        break;
      case ">=":
        compareResult = (value >= referenceValue);
        break;
      case "<":
        compareResult = (value < referenceValue);
        break;
      case "<=":
        compareResult = (value <= referenceValue);
        break;
      default:
        compareResult = (value === referenceValue);
        break;
    }
    return compareResult;
  },

  /**
   * @private
   * Retrieves a value dependent on a configuration.
   *
   * If the 'valueCfg' is already a constant value, it will be returned as it is.
   *
   * If the value from sord.TStamp should be loaded, this function converts it to a legal ISO date.
   *
   * @param {de.elo.ix.client.Sord} sord
   * @param {Object} valueCfg
   * @param {Object} ctx
   * @return {String}
   */
  getValue: function (sord, valueCfg, ctx) {
    var value;

    if (valueCfg && valueCfg.hasOwnProperty("type") && valueCfg.hasOwnProperty("key")) {
      if (valueCfg.type === "PARENT_MAP" && ctx && ctx.parentMap) {
        value = ctx.parentMap.getValue(valueCfg.key + "." + sord.id);
      } else {
        if (valueCfg.localizedKwl) {
          value = (sol.common.SordUtils.getLocalizedKwlKey(sord, valueCfg) || "") + "";
        } else {
          value = (sol.common.SordUtils.getValue(sord, valueCfg) || "") + "";
        }

        if (valueCfg.type === "SORD" && valueCfg.key === "TStamp") {
          value = value.replace(/\./g, "");
        }
      }
    } else {
      value = valueCfg;
    }

    return value;
  }

});