    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");
    }