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

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


/**
 * Converts a MapTable of a TemplateSord structure into an array
 * All types except the mapKeys are ignored by the converter.
 *
 *
 * MapTable fields that are not in the output definition
 * are also not considered in the result and are discarded.
 *
 * This also works with wfMapKeys!
 * 
 *
 * Example of a TemplateSord object with MapTable
 *
 *    {
 *       mapKeys: {
 *          "PERSON_ADDRESS_STREET1": "Musterstraße",
 *          "PERSON_ADDRESS_CITY1": "Musterstadt"
 *       }
 *    }
 *
 * Example Call
 *
 *     var converter = sol.create("sol.meeting.ObjectFormatter.MapTableToArrayToArray", {
 *       output: [
 *         { source: { key: "PERSON_CRUD_STATUS" }, target: { prop: "status" } }
 *         { source: { key: "PERSON_ADDRESS_CITY" },  target: { prop: "city" } }
 *       ],
 *       options: {
 *         ignorePropertyNames: true,
 *         filter: [
 *          { "prop" : "sord.mapKeys.SOLUTION_FIELD", value: "CREATE" }
 *        ]
 *       }
 *     });
 *
 * Output
 *
 *    [
 *      { street: "Musterstraße", city: "Musterstadt", $mapIndex: "1"}
 *    ]
 *
 * The special prop $mapIndex is added to the result object to determine the mapIndex in
 * further processing
 *
 * ### Options
 *
 *    {
 *       options: { ignorePropertyNames: true }
 *    }
 *
 * When this parameter is defined, the target.prop name is ignored by default. An Array with objects on the original fieldnames will be created.
 *
 * #### propSelector
 *
 * When this parameter is defined the result array will be returned as simple string array instead of complex object
 * propSelector must select an output target prop. Only this prop will be returned. Others will be ignored.
 *
 *      var converter = sol.create("sol.meeting.ObjectFormatter.MapTableToArray", {
 *       output: [
 *         { source: { key: "PERSON_USERNAME1" }, target: { prop: "username" } }
 *         { source: { key: "PERSON_USERNAME2" },  target: { prop: "username" } }
 *       ],
 *       options: {
 *         propSelector: "username"
 *       }
 *     });
 *
 *  Output
 *
 *    [
 *      "Sandra Renz", "Bodo Kraft"
 *    ]
 *
 *
 * @requires sol.common.ObjectUtils
 *
 */
sol.define("sol.common.ObjectFormatter.MapTableToArray", {
  requiredConfig: ["output"],

  mixins: ["sol.common.mixins.ObjectFilter"],

  /**
   * @cfg {String} [kind=mapKeys] (optional)
   * Use formatter for wfMapKeys or mapKeys. Only this types are supported.
   * You have to decide wheather you want to convert wfMapKeys or mapKeys
   */

  /**
   * @cfg {Object} options
   * @cfg {Boolean} options.ignorePropertyNames all target prop definition will be ignored
   * @cfg {String|Boolean} options.propSelector return single property in a flat string array with the passed prop
   */

  initialize: function (config) {
    var me = this;
    if (!config.kind) {
      config.kind = "mapKeys";
    }

    me.$super("sol.Base", "initialize", [config]);
    me.outputCache = {};

    me.options = me.options || {};
    me.options.ignorePropertyNames = me.options.ignorePropertyNames || false;
    me.options.propSelector = me.options.propSelector || false;
    me.options.filter = me.options.filter || [];
    me.sanitizeOutputConfig(me.output);
  },

  sanitizeOutputConfig: function (output) {
    var me = this;

    // prepare an output temporary cache
    // With the cache we can find output definitions easier

    output.forEach(function (definition) {
       if (!definition.source) {
        throw Error("missing source prop in output definition, " + JSON.stringify(definition));
       }

       if (!sol.common.ObjectUtils.type(definition.source.key, "string")) {
         throw Error("key in output definition is not a string," + JSON.stringify(definition));
       }

       me.outputCache[definition.source.key] = definition;
    });

    if (!(me.kind === "wfMapKeys" || me.kind === "mapKeys")) {
      throw Error("Only wfMapKeys or mapKeys are supported");
    }

  },

  /**
   * Convert mapKeys of a templateSord object to an array
   * @param {Object} templateSord
   * @param {Object} config
   * @param {Array} config.fields
   */
  format: function (templateSord) {
    var me = this, result = {}, tableData, filter;

    if (!templateSord || !templateSord[me.kind]) {
      return [];
    }

    Object.keys(templateSord[me.kind]).forEach(function (mapKey) {
      var mapIndex = me.getFieldNameIndex(mapKey), outputDef, value, fieldName, targetFieldname;

      if (mapIndex > 0) {
        fieldName = me.getFieldName(mapKey);
        outputDef = me.outputCache[fieldName];

        // transform mapKey only when output definition exists
        if (outputDef) {
          value = sol.common.ObjectUtils.getProp(templateSord[me.kind], mapKey);
          if (!result[mapIndex]) {
            // create new entry, when we read a new row
            result[mapIndex] = {};
          }
          targetFieldname = me.getTargetFieldName(mapKey, outputDef);
          sol.common.ObjectUtils.setProp(result[mapIndex], targetFieldname, value);
        }
      } else {
        me.logger.debug(["skip field `{0}` because it is not a maptable", mapKey]);
      }
    });

    tableData = me.convertToArray(result);

    me.logger.info(["tableData {0}", JSON.stringify(tableData)])

    if (me.options.filter.length > 0) {
      // from mixin sol.common.ObjectUtils.ObjectFilter
      filter = me.generateFilter(me.options.filter || []);
      me.logger.info(["user filter {0}", JSON.stringify(filter)]);
      tableData = tableData.filter(me.matchObject.bind(null, filter));
    }


    if (me.options.propSelector) {
      tableData = me.returnOnlyPropSelector(tableData);
    }

    return tableData;
  },

  /**
   * @returns mapIndex when field has any index otherwise -1
   */
  getFieldNameIndex: function (fieldName) {
    var me = this, pos;
    pos = me.getIndexPosition(fieldName);

    if (pos > 0) {
      return fieldName.substring(pos);
    }

    return "";
  },

  getFieldName: function (fieldName) {
    var me = this, pos;
    pos = me.getIndexPosition(fieldName);
    if (pos > 0) {
      return fieldName.substring(0, pos);
    }

    return "";
  },

  getIndexPosition: function (fieldName) {
    if (!sol.common.ObjectUtils.isString(fieldName)) {
      throw Error("`fieldName must be a string, type=`" + sol.common.ObjectUtils.type(fieldName));
    }
    return fieldName.search(/\d+$/);
  },

  getTargetFieldName: function (mapKey, outputDefinition) {
    var me = this;
    return me.options.ignorePropertyNames
      ? mapKey
      : outputDefinition.target.prop;
  },

  convertToArray: function (result) {

    return Object.keys(result).map(function (index) {
      result[index].$mapIndex = index;
      return result[index];
    });
  },

  returnOnlyPropSelector: function (arr) {
    var me = this,
      propSelector = me.options.propSelector;

    return arr.map(function (data) {
      return data[propSelector];
    });
  }

});