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

//@include lib_Class.js
//@include lib_moment.js
//@include lib_sol.common.StringUtils.js
//@include lib_sol.common.DateUtils.js
//@include lib_sol.common.SordUtils.js
//@include lib_sol.common.ix.FunctionBase.js

var logger = sol.create("sol.Logger", { scope: "sol.common.ix.functions.FeedComment" });

/**
 * Writes a feed comment for an archive element.
 *
 * The comments will be read from a language `file` which has to be present in `ELOwfBase/Feed/Script Locales`.
 * A `key` determines which value from the language file will be used. The feed comments in this file can contain placeholders.
 * An additional `data` array contains the configuration on how the placeholders will be handled.
 *
 * # As workflow node
 *
 * `ObjId` is set based on the element that the workflow is attached to.
 * Following configuration should be applied to the comments field.
 *
 *     {
 *       "file": "sol.invoice.workflow",
 *       "key": "WORKFLOW.INVOICE.APPROVAL.REJECTED",
 *       "data": [ "INVOICE_NET_AMOUNT", "INVOICE_CURRENCY_CODE" ]
 *     }
 *
 * The following configuration examples will be (if not stated otherwise) in JavaScript notation (for better readability).
 * If used in a workflow node configuration they need to be converted to JSON notation.
 *
 * # As IX function call
 *
 * In addition to the workflow node configuration the `objId` must be passed.
 *
 *     sol.common.IxUtils.execute("RF_sol_function_FeedComment", {
 *       objId: "4711",
 *       file: "sol.invoice.workflow",
 *       key: "WORKFLOW.INVOICE.APPROVAL.REJECTED",
 *       data: [ "INVOICE_NET_AMOUNT", "INVOICE_CURRENCY_CODE" ]
 *     });
 *
 * # Placeholder
 * The key in the property file can contain placeholders. Those are curly braces with an index (starting with zero): `{0}`
 * These placeholders will be replaced with either Sord properties, indexing information, map entries or workflow map entries specified by the data array.
 * Datasource is the object specified by `objId` or on which the workflow runs.
 *
 * By default, the index field names are used in the data array to determine the values to replace the placeholders:
 *
 *     data: [ "MY_FIELD" ]  // abbreviated form, will replace {0} with the value of the index field `MY_FIELD`
 *
 * Advanced placeholder configuration:
 *
 *     data: [ { type: "SORD", key: "name" } ]  // will replace {0} with the name of the sord
 *     data: [ { type: "GRP", key: "MY_FIELD" } ]  // will replace {0} with the value of the index field `MY_FIELD`
 *     data: [ { type: "MAP", key: "MY_MAP" } ]  // will replace {0} with the value of the map field `MY_MAP`
 *     data: [ { type: "WFMAP", key: "MY_WFMAP" } ]  // will replace {0} with the value of the workflow map field `MY_MAP` (only available in workflow functions)
 *     data: [ { type: "FORMBLOB", key: "REASON_OF_REJECTION", "deleteAfterUse": true } ]  // will delete the value after use (only available for type `FORMBLOB`)
 *     data: [ { type: "WFNODE", node: "my.node.translation.key", key: "comment" } ]  // only works in workflows, searches the node either by translation key or by node id and writes the node property defined in key
 *
 * Both notations can be mixed.
 *
 * # Formating
 * Values read from fields, can be formatted. An additional configuration will be needed for that.
 *
 * Currently `dateFormat`, and `eloFormat` is supported.
 *
 * The `dateFormat` has to be in [moment](http://momentjs.com/docs/#/parsing/string-format/) syntax.
 *
 *     data: [ { type: "GRP", key: "MY_DATE_FIELD", dateFormat: "DD.MM.YYYY" } ]  // will output the value of `MY_DATE_FIELD` in the given format e.g. '30.05.2016'
 *
 * The `eloFormat` currently supports the two values `hashtag` and `mention` (or the corresponding shortcuts `h` or `m`)
 *
 *     data: [ { type: "GRP", key: "MY_FIELD", eloFormat: "hashtag" } ]  // will output the value of `MY_FIELD` as an elo hashtag
 *     data: [ { type: "GRP", key: "MY_FIELD", eloFormat: "h" } ]  // will output the value of `MY_FIELD` as an elo hashtag
 *     data: [ { type: "GRP", key: "MY_FIELD", eloFormat: "mention" } ]  // will output the value of `MY_FIELD` as an elo mention
 *     data: [ { type: "GRP", key: "MY_FIELD", eloFormat: "m" } ]  // will output the value of `MY_FIELD` as an elo mention
 *
 * `dateFormat` and `eloFormat` can be combined:
 *
 *     data: [ { type: "GRP", key: "MY_DATE_FIELD", dateFormat: "YYYY[_Q]Q", eloFormat: "h" } ]  // will output the value of `MY_DATE_FIELD` as an elo hashtag, e.g. `#2016_Q2`
 *
 * Hashtags can not be longer than 40 characters. If a values should be longer it will be truncated (since 1.06.000).
 *
 * If formatting should be used, the abbreviated placeholder configuration can not be used.
 *
 * # Using information boxes in feed comments
 *
 * HTML content is allowed if used within localization property files. In order to support colorized information boxes a set of div-containers and colors can be used.
 *
 * Following locale key will display a yellow box including some information after the message.
 *
 *     CONTRACT.SIGNED=signed the contract.<div class="description yellow">Information of the box...</div>
 *
 * By default a set of colors can be used:
 *
 *     <div class="description red">red box</div>
 *     <div class="description green">green box</div>
 *     <div class="description redpurple">red purple box</div>
 *     <div class="description yellow">yellow box</div>
 *     <div class="description orange">orange box</div>
 *     <div class="description pink">pink box</div>
 *
 * HTML-Tags must be used with care since that allows breaking the layout. The use of script tags is also forbidden.
 *
 * # Examples
 *
 * For defining new feed action events a language file should be placed in `\Administration\ELOwf Base\Feed\Script Locales`. It is recommended seperating language files by the solution.
 * If a contract management solution is used define use a file name that includes a namespace. (e.g. `sol.contract` or `sol.contract_fr`)
 *
 * It is recommended using the default locale file (`sol.contract`) for the english translation if multiple language files should be supported.
 * This will be used as a fallback for unsupported languages.
 *
 * ## Using property keys
 *
 * Contents of file `\Administration\ELOwf Base\Feed\Script Locales\sol.contract`:
 *
 *     CONTRACT.CREATED=created a new contract.
 *
 * Workflow node configuration:
 *
 *     {
 *       "file": "sol.contract",
 *       "key": "CONTRACT.CREATED"
 *     }
 *
 * ## Using data placeholders
 *
 * Contents of file `\Administration\ELOwf Base\Feed\Script Locales\sol.contract`:
 *
 *     CONTRACT.CREATED=created a new contract: {0}.
 *
 * Workflow node configuration:
 *
 *     {
 *       "file": "sol.contract",
 *       "key": "CONTRACT.CREATED",
 *       "data": ["CONTRACT_NAME"]
 *     }
 *
 * ## Using information boxes and multiple data placeholders
 *
 * Contents of file `\Administration\ELOwf Base\Feed\Script Locales\sol.contract`:
 *
 *     CONTRACT.CONCLUDED=signed the contract.<div class="description yellow">Comments: {0}<br/>Resposible: {1}</div>
 *
 * Workflow node configuration:
 *
 *     {
 *       "file": "sol.contract",
 *       "key": "CONTRACT.CREATED",
 *       "data": [{ "type": "GRP", "key": "CONTRACT_NAME" }, { "type": "MAP", "key": "CONTRACT_RESPONSIBLE" }]
 *     }
 *
 * ## Using fixed text without localization info
 *
 * Workflow node configuration:
 *
 *     {
 *       "text": "has created a document {0} with content {1}",
 *       "data": ["Test", "xyz"]
 *     }
 *
 *     {
 *       "text": ""has created a document {0}",
 *       "data": [{ "type": "MAP", "key": "TEST" }],
 *       "dynamicData": true
 *     }
 *
 * @author JHR, ELO Digital Office GmbH
 * @version 1.06.000
 *
 * @eloix
 *
 * @requires  sol.common.ObjectUtils
 * @requires  sol.common.StringUtils
 * @requires  sol.common.DateUtils
 * @requires  sol.common.SordUtils
 * @requires  sol.common.JsonUtils
 * @requires  sol.common.WfUtils
 * @requires  sol.common.ix.RfUtils
 * @requires  sol.common.ix.FunctionBase
 */
sol.define("sol.common.ix.functions.FeedComment", {
  extend: "sol.common.ix.FunctionBase",

  requiredConfig: ["objId"],

  /**
   * @cfg {String} objId (required)
   */
  objId: undefined,

  /**
   * @cfg {String} file (required)
   * Resource file from 'ELOwf Base\Feed\Script Locales' (without locale extension, e.g. '_de')
   */
  file: undefined,

  /**
   * @cfg {String} key (required)
   * The property key for the message
   */
  key: undefined,

  /**
   * @cfg {String} text (required, if file and key are not set)
   * The constant text for the message if locale extension is not used
   */
  text: undefined,

  /**
   * @cfg {String[]} data
   * If the key requires additional data, this property should hold a list of group fields to hand over to the feed
   */
  data: undefined,

  /**
   * @cfg {Boolean} dynamicData
   * If `text` is given, this property defines if the property `data` contains field definitions
   */
  dynamicData: undefined,

  /**
   * @cfg {Boolean} [writeToParent=false]
   * If true then the comment will be written to the parent of the element
   */

  /**
   * @cfg {Number} [writeToObjId]
   * If set the feed for the given object used used instead.
   * This allows retrieving data from the current object and write feed actions to another object.
   */

  /**
   * @cfg {String} [userGuid]
   * If set the comment will be written under the given users name.
   * This is only allowed if executing user has administrative privileges and cannot be used if used as onEnter or onExit node.
   */

  /**
   * @cfg {String[]|Object[]} [visibility]
   * If set the comment will be visible to the given users or groups only.
   * If this contains strings, they serve as user names. If it contains objects, they have to contain a type (e.g. "GRP") and a key property.
   */

  /**
   * @param {Object} config Config
   */
  initialize: function (config) {
    var me = this;
    me.$super("sol.common.ix.FunctionBase", "initialize", [config]);
  },

  /**
   * Writes the feed comment for the element.
   */
  process: function () {
    var me = this,
        values = [],
        aclItems = [],
        sord, action, dataString, feedObjId, aclItem, nameConfig, i;

    if (!me.text) {
      if (!me.file) {
        throw "Parameter file is empty";
      }
      if (!me.key) {
        throw "Parameter key is empty";
      }
    }
    if (me.text) {
      if (!me.data) {
        throw "Parameter data is empty";
      }
    }
    if (me.data && me.data.length > 0) {
      if ((me.file && me.key) || (me.text && me.dynamicData)) {
        sord = ixConnect.ix().checkoutSord(me.objId, EditInfoC.mbSord, LockC.NO).sord;
        me.data.forEach(function (entry) {
          var value;
          value = (me.getValue(sord, entry) || "") + "";
          values.push(value);
          me.deleteValue(sord, entry);
        });
      }
    }

    feedObjId = me.writeToObjId || me.objId;

    if (me.writeToParent) {
      sord = sord || sol.common.RepoUtils.getSord(me.objId);
      feedObjId = sord.parentId;
    }

    action = ixConnect.feedService.createAction(EActionType.AutoComment, feedObjId);
    dataString = JSON.stringify(values);

    if (me.text) {
      action.text = me.formatText(me.text, me.dynamicData ? values : me.data);
    } else {
      action.text = '{"file": "' + me.file + '", "key": "' + me.key + '", "data": ' + dataString + "}";
    }

    if (me.userGuid) {
      action.userGuid = me.userGuid;
    }

    if (me.visibility) {
      me.visibility = sol.common.ObjectUtils.isArray(me.visibility) ? me.visibility : [me.visibility];
      for (i in me.visibility) {
        aclItem = new AclItem();
        nameConfig = me.visibility[i];
        aclItem.access = AccessC.LUR_READ;
        if (sol.common.ObjectUtils.isObject(nameConfig)) {
          sord = sord || sol.common.RepoUtils.getSord(me.objId);
          aclItem.name = me.getValue(sord, nameConfig);
        } else {
          aclItem.name = nameConfig;
        }
        aclItems.push(aclItem);
      }
      action.aclItems = aclItems;
    }

    ixConnect.feedService.checkinAction(action, ActionC.mbAll);
  },

  /**
   * @private
   * @param {String} text e.g."has created a document {0} with content {1}"
   * @param {Object} data e.g. ["Test", "xyz"]
   * @return {String} formmatted text "has created a document Test with content "xyz""
   */
  formatText: function (text, data) {
    var i;
    for (i = 0; i < data.length; i++) {
      text = text.replace(new RegExp("\\{" + i + "\\}", "g"), data[i]);
    }
    return text;
  },

  /**
   * @private
   * @param {de.elo.ix.client.Sord} sord
   * @param {Object} dataConfig
   * @return {String}
   */
  getValue: function (sord, dataConfig) {
    var me = this,
        wfMapValue, wfNodeValue, values, value;
    if (!sol.common.ObjectUtils.isObject(dataConfig)) {
      values = sol.common.SordUtils.getObjKeyValues(sord, dataConfig);
      if (!values) {
        value = "";
      } else {
        value = values.join(", ");
      }
      return value;
    }
    try {
      wfMapValue = me.getWfMapValue(dataConfig);
      wfNodeValue = me.getWfNodeValue(dataConfig);
      if (wfMapValue || wfNodeValue) {
        value = me.formatValue(wfMapValue || wfNodeValue, dataConfig);
      } else {
        values = sol.common.SordUtils.getValues(sord, dataConfig);
        values = me.formatValues(values, dataConfig);
        if (!values) {
          value = "";
        } else {
          value = values.join(", ");
        }
      }
      return value;
    } catch (ex) {
      me.logger.warn(["IllegalConfiguration: could not determine value for '{0}'", JSON.stringify(dataConfig)], ex);
    }
  },

  /**
   * @private
   * @param {de.elo.ix.client.Sord} sord
   * @param {Object} dataConfig
   */
  deleteValue: function (sord, dataConfig) {
    if (!sol.common.ObjectUtils.isObject(dataConfig) || (dataConfig.type != "FORMBLOB") || !dataConfig.deleteAfterUse) {
      return;
    }

    ixConnect.ix().deleteMap("formdata", sord.id, [dataConfig.key], LockC.NO);
  },

  /**
   * @private
   * @param {Object} dataConfig
   * @return {String}
   */
  getWfMapValue: function (dataConfig) {
    var me = this,
        flowId = (me.wFDiagram) ? me.wFDiagram.id : me.flowId,
        wfMapitems;

    if (dataConfig.type === "WFMAP" && flowId) {
      wfMapitems = ixConnect.ix().checkoutMap(MapDomainC.DOMAIN_WORKFLOW_ACTIVE, flowId, [dataConfig.key], LockC.NO).items;
      if (wfMapitems && wfMapitems.length > 0) {
        return wfMapitems[0].value;
      }
    }
  },

  /**
   * @private
   * @param {Object} dataConfig
   * @return {String}
   */
  getWfNodeValue: function (dataConfig) {
    var me = this,
        value;

    if (me.wFDiagram && dataConfig.type === "WFNODE" && dataConfig.node && dataConfig.key) {
      me.wFDiagram.nodes.some(function (node) {
        if ((node.nameTranslationKey == dataConfig.node) || (node.id == dataConfig.node)) {
          value = node[dataConfig.key];
          return true;
        }
      });
    }
    return value;
  },

  /**
   * @private
   * @param {String} value
   * @param {Object} dataConfig
   * @return {String}
   */
  formatValue: function (value, dataConfig) {
    var date;
    if (dataConfig) {
      if (dataConfig.dateFormat) {
        date = sol.common.DateUtils.isoToDate(value);
        value = sol.common.DateUtils.format(date, dataConfig.dateFormat);
      }
      if (value && (dataConfig.eloFormat === "hashtag" || dataConfig.eloFormat === "h")) {
        value = String(value);
        if (value.length > 40) {
          value = value.substring(0, 37) + "...";
        }
        value = "#[" + value + "]";
      }
      if (value && (dataConfig.eloFormat === "mention" || dataConfig.eloFormat === "m")) {
        value = "@[" + value + "]";
      }
    }
    return value;
  },

  /**
   * @private
   * @param {String[]} values
   * @param {Object} dataConfig
   * @return {String[]}
   */
  formatValues: function (values, dataConfig) {
    var me = this,
        i;

    if (values && values.length > 0) {
      for (i = 0; i < values.length; i++) {
        values[i] = me.formatValue(values[i], dataConfig);
      }
    }
    return values;
  }
});

/**
 * @member sol.common.ix.functions.FeedComment
 * @static
 * @inheritdoc sol.common.ix.FunctionBase#onEnterNode
 */
function onEnterNode(clInfo, userId, wFDiagram, nodeId) {
  logger.enter("onEnterNode_FeedComment", { flowId: wFDiagram.id, nodeId: nodeId });
  var params = sol.common.WfUtils.parseAndCheckParams(wFDiagram, nodeId),
      module;
  params.userGuid = undefined;

  params.objId = wFDiagram.objId;
  params.wFDiagram = wFDiagram;
  module = sol.create("sol.common.ix.functions.FeedComment", params);

  module.process();

  logger.exit("onEnterNode_FeedComment");
}


/**
 * @member sol.common.ix.functions.FeedComment
 * @static
 * @inheritdoc sol.common.ix.FunctionBase#onExitNode
 */
function onExitNode(clInfo, userId, wFDiagram, nodeId) {
  logger.enter("onExitNode_FeedComment", { flowId: wFDiagram.id, nodeId: nodeId });
  var params = sol.common.WfUtils.parseAndCheckParams(wFDiagram, nodeId),
      module;
  params.userGuid = undefined;

  params.objId = wFDiagram.objId;
  params.wFDiagram = wFDiagram;
  module = sol.create("sol.common.ix.functions.FeedComment", params);

  module.process();

  logger.exit("onExitNode_FeedComment");
}


/**
 * @member sol.common.ix.functions.FeedComment
 * @method RF_sol_function_FeedComment
 * @static
 * @inheritdoc sol.common.ix.FunctionBase#RF_FunctionName
 */
function RF_sol_function_FeedComment(iXSEContext, args) {
  logger.enter("RF_sol_function_FeedComment", args);
  var params = sol.common.ix.RfUtils.parseAndCheckParams(iXSEContext, arguments.callee.name, args, "objId"),
      module = sol.create("sol.common.ix.functions.FeedComment", params);

  if (params.userGuid) {
    sol.common.ix.RfUtils.checkMainAdminRights(iXSEContext.user, params);
  }

  module.process();

  logger.exit("RF_sol_function_FeedComment");
}