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


//@include lib_Class.js
//@include lib_sol.common.SordUtils.js
//@include lib_sol.common.RepoUtils.js
//@include lib_sol.common.WfUtils.js
//@include lib_sol.common.StringUtils.js
//@include lib_sol.common.ObjectUtils.js
//@include lib_sol.common.CounterUtils.js
//@include lib_sol.common.Template.js
//@include lib_sol.common.TranslateTerms.js
//@include lib_sol.common.ObjectFormatter.js
//@include lib_sol.common.ix.RfUtils.js
//@include lib_sol.common.ix.FunctionBase.js
//@include lib_sol.common.ConnectionUtils.js

var logger = sol.create("sol.Logger", { scope: "sol.common.ix.functions.Set" }); // eslint-disable-line one-var

/**
 * Edits an existing object by changing the mask or setting different values.
 *
 * # As workflow node
 *
 * ObjId is set based on the element that the workflow is attached to.
 *
 * The timeZone parameter is currently not supported in conjunction with `asAdmin`
 * because we don't want to rewrite the clientInfo of the adminConnection temporarily
 *
 * Following configuration should be applied to the comments field for a mask and field update:
 *
 * ## Example
 *
 *     {
 *       "entries": [{
 *         "type": "MASK",        // changes the mask of the sord, if defined more than once, the first one will be used
 *         "value": "Basic Entry"
 *       }, {
 *         "type": "SORD",
 *         "key": "name",
 *         "value": "Contract {{sord.objKeys.CONTRACT_NAME}} - {{count 'MY_CONTRACT_COUNTER'}}",
 *         "useTemplating": true
 *       }, {
 *         "type": "GRP",
 *         "key": "CONTRACT_STATUS",
 *         "value": "D",
 *         "useDynKwl": true,
 *         "dynKwlCfg": {
 *           "filterColumn": 0,
 *           "returnColumn": 2
 *         },
 *         "onlyIfEmpty": true
 *       }, {
 *         "type": "MAP",
 *         "key": "USER",
 *         "value": "Bill Gates",
 *       }, {
 *         "type": "WFMAP",
 *         "key": "USER",
 *         "value": "Steve Jobs"
 *       }, {
 *         "type": "WF",
 *         "key": "STATUS",
 *         "value": "CREATE"
 *       }]
 *     }
 *
 * Following configuration should be applied to the comments field and will set a value from the fields KWL starting with '3'
 *
 *     {
 *       "entries": [{
 *         "type": "GRP",
 *         "key": "INVOICE_STATUS",
 *         "value": "3",
 *         "useKwl": true
 *        }]
 *     }
 *
 * Following configuration will set a field from the fields dynamic KWL staring with 'M' (e.g. M - month) from a localized dynamic KWL
 *
 *     {
 *       "type": "GRP",
 *       "key": "REMINDER_PERIOD_UNIT",
 *       "value": "M",
 *       "useDynKwl": true,
 *       "dynKwlCfg": {
 *         "returnColumn": 2,
 *         "filterColumn": 0
 *       }
 *     }
 *
 * # As IX function call
 *
 * In addition to the workflow node configuration the objId must be passed.
 *
 *     sol.common.IxUtils.execute("RF_sol_function_Set", {
 *       objId: "4711",
 *       asAdmin: true,
 *       entries: [{
 *         type: "GRP",
 *         key: "INVOICE_STATUS",
 *         value: "3",
 *         useKwl: true
 *       }]
 *     });
 *
 *
 * @eloix
 * @requires sol.Logger
 * @requires sol.common.SordUtils
 * @requires sol.common.JsonUtils
 * @requires sol.common.RepoUtils
 * @requires sol.common.StringUtils
 * @requires sol.common.ObjectUtils
 * @requires sol.common.CounterUtils
 * @requires sol.common.Template
 * @requires sol.common.TranslateTerms
 * @requires sol.common.ObjectFormatter.TemplateSord
 * @requires sol.common.WfUtils
 * @requires sol.common.ix.RfUtils
 * @requires sol.common.ix.FunctionBase
 * @requires sol.common.ix.ConnectionUtils
 *
 */
sol.define("sol.common.ix.functions.Set", {
  extend: "sol.common.ix.FunctionBase",

  requiredConfig: ["objId", "entries"],

  /**
   * @cfg types which are supported
   * @private
   */
  types: ["MASK", "SORD", "GRP", "MAP", "FORMBLOB", "WFMAP", "WF"],


  /**
   * @cfg {String} objId (required)
   * Object ID
   */

  /**
   * @cfg {Object[]} entries (required)
   * @cfg {String} entries.key field key
   * @cfg {String} entries.type the type of the field {@link sol.common.ix.functions.Set#types}
   * @cfg {String} entries.value the value which should be set
   * @cfg {Boolean} entries.useKwl enable keyword lookup for the current value
   * @cfg {Boolean} entries.useDynKwl enable dynamic keyword lookup for the current value
   * @cfg {Object} entries.dynKwlCfg
   * @cfg {Boolean} entries.onlyIfEmpty the value will only be set when selected field is not empty
   *
   * Entries that contains the values to set
   */

  /**
   * @cfg {String} flowId
   * Flow ID
   */

  /**
   * @cfg {String} timeZone
   * Time zone
   */

  /**
   * @cfg {Boolean} asAdmin
   * Execute function in admin context
   */

  initialize: function (config) {
    var me = this;

    me.$super("sol.common.ix.FunctionBase", "initialize", [config]);
    me.sordMaskChanged = false;

    if (me.asAdmin) {
      if (me.wfDiagram) {
        me.checkAdminExecutionContext(function () {
          sol.common.WfUtils.checkMainAdminWf(me.wfDiagram);
        });
      } else {
        me.checkAdminExecutionContext(function () {
          sol.common.ix.RfUtils.checkMainAdminRights(sol.common.IxConnectionUtils.getCurrentUser());
        });
      }

      if (me.timeZone) {
        // We should not change ixConnectAdmin timeZone parameter because this object
        // is a singleton.
        throw Error("Use of an admin connection in combination with the timeZone parameter is currently not implemented");
      }
    }

    me.connection = sol.common.IxConnectionUtils.getConnection(me.asAdmin);
  },

  checkAdminExecutionContext: function (callback) {
    var me = this;
    try {
      if (!callback || !sol.common.ObjectUtils.isFunction(callback)) {
        throw Error("Callback in  `checkAdminExecutionContext` is not a callback function");
      }

      callback.call(me);
    } catch (ex) {
      logger.warn("Ignore `asAdmin` parameter", ex);
      me.asAdmin = false;

      if (me.$throwError === true) {
        throw ex;
      }

    }
  },

  /**
   * Sets a field value
   */
  process: function () {
    var me = this,
        i, entry, type, func, entries;

    if (!me.entries) {
      return;
    }

    me.logger.debug(["sol.common.ix.functions.Set: entries={0}", JSON.stringify(me.entries)]);

    me.entries = sol.common.ObjectUtils.clone(me.entries);

    me.separatedEntries = {};

    me.sordData = [];

    me.sordMapKeyValues = [];
    me.sordFormdataValues = []; //blob
    me.wfMapKeyValues = [];

    if (me.timeZone) {
      me.savedTimeZone = ixConnect.loginResult.clientInfo.timeZone;
      ixConnect.loginResult.clientInfo.timeZone = me.timeZone;
      me.logger.debug(["user.name={0}, timeZone={1}", ixConnect.loginResult.user.name, ixConnect.loginResult.clientInfo.timeZone]);
    }

    me.sord = sol.common.RepoUtils.getSord(me.objId, { sordZ: SordC.mbAllIndex, connection: me.connection });

    for (i = 0; i < me.entries.length; i++) {
      entry = me.entries[i];
      type = entry.type.toUpperCase();
      if (me.types.indexOf(entry.type) < 0) {
        throw "Unsupported type: " + entry.type;
      }
      if (!me.filterEntry(me.sord, entry)) {
        me.separatedEntries[type] = me.separatedEntries[type] || [];
        me.separatedEntries[type].push(entry);
      }
    }

    for (i = 0; i < me.types.length; i++) {
      type = me.types[i];
      func = me["processType" + type];
      entries = me.separatedEntries[type];
      me.logger.debug(["separatedEntries[{0}]={1}", type, JSON.stringify(entries)]);
      if (func && entries) {
        func.call(me, me.separatedEntries[type]);
      }
    }

    me.saveSordValues();
    me.saveWorkflowValues();

    if (me.savedTimeZone) {
      ixConnect.loginResult.clientInfo.timeZone = me.savedTimeZone;
    }
  },

  emptyFilterTypes: ["GRP", "MAP"],

  filterEntry: function (sord, entry) {
    var me = this;
    return !!(
      ~me.emptyFilterTypes.indexOf(entry.type)
      && entry.onlyIfEmpty
      && String(sol.common.SordUtils.getValue(sord, entry) || "")
    );
  },

  processTypeMASK: function (maskEntries) {
    var me = this,
        mask;

    if (maskEntries.length > 0) {
      mask = maskEntries[0].value;
      if (mask && sol.common.SordUtils.docMaskExists(mask)) {
        me.sord = me.connection.ix().changeSordMask(me.sord, mask, EditInfoC.mbSord).sord;
        me.sordMaskChanged = true;
      }
    }
  },

  processTypeSORD: function (sordEntries) {
    var me = this;
    me.applyTemplates(sordEntries);
    me.sordData = me.sordData.concat(sordEntries);
  },

  processTypeGRP: function (grpEntries) {
    var me = this,
        i, entry;

    me.applyTemplates(grpEntries);

    for (i = 0; i < grpEntries.length; i++) {
      entry = grpEntries[i];
      if (entry.useKwl) {
        entry.value = me.getValueFromKwl(entry.key, entry.value);
      } else if (entry.useDynKwl) {
        entry.value = me.getValueFromDynKwl(me.sord.maskName, entry.key, entry.value, entry.dynKwlCfg, entry.useForeignKey);
      }
    }
    me.sordData = me.sordData.concat(grpEntries);
  },

  processTypeMAP: function (sordMapEntries) {
    var me = this,
        i, entry;
    me.applyTemplates(sordMapEntries);

    for (i = 0; i < sordMapEntries.length; i++) {
      entry = sordMapEntries[i];
      me.sordMapKeyValues.push(new KeyValue(entry.key, entry.value));
    }
  },

  processTypeFORMBLOB: function (sordMapEntries) {
    var me = this, blb,
        i, entry;
    me.applyTemplates(sordMapEntries);

    for (i = 0; i < sordMapEntries.length; i++) {
      entry = sordMapEntries[i];

      if (entry.asJSON) {
        try {
          entry.value = JSON.stringify(entry.value);
        } catch (e) {
          throw "SET: could not stringify value marked with option 'asJSON'. Writing to FORMBLOB field `" + entry.key + "` failed.";
        }
      }

      blb = new MapValue((new java.lang.String(entry.key)), (new FileData("text/plain", (new java.lang.String(entry.value)).getBytes(java.nio.charset.StandardCharsets.UTF_8))));
      me.sordFormdataValues.push(blb);
    }
  },

  processTypeWFMAP: function (wfMapEntries) {
    var me = this,
        i, entry;
    me.applyTemplates(wfMapEntries);

    for (i = 0; i < wfMapEntries.length; i++) {
      entry = wfMapEntries[i];
      me.wfMapKeyValues.push(new KeyValue(entry.key, entry.value));
    }
  },

  processTypeWF: function (wfEntries) {
    var me = this,
        i, entry;

    if (!me.wfDiagram) {
      return;
    }

    me.applyTemplates(wfEntries);

    for (i = 0; i < wfEntries.length; i++) {
      entry = wfEntries[i];
      if (entry.key == "STATUS") {
        sol.common.WfUtils.setWorkflowStatus(me.wfDiagram, entry.value);
      }
    }
  },

  applyTemplates: function (entries) {
    var me = this, maxLength,
        i, entry;

    for (i = 0; i < entries.length; i++) {
      entry = entries[i];
      if (entry.useTemplating && entry.value) {
        me.tplSord = me.tplSord || sol.common.WfUtils.getTemplateSord(me.sord, me.flowId, {
          formBlobs: entry.useFormBlobs || false
        });
        entry.value = sol.create("sol.common.Template", { source: entry.value }).apply(me.tplSord);
      }

      if (entry.truncate === true) {
        maxLength = entry.maxLength || ixConnect.CONST.SORD.lnName;
        entry.value = sol.common.StringUtils.truncate(entry.value, maxLength);
      }
    }
  },

  /**
   * @private
   */
  saveSordValues: function () {
    var me = this,
        sordDirty = me.sordMaskChanged;

    if (me.sordData && (me.sordData.length > 0)) {
      sol.common.SordUtils.updateSord(me.sord, me.sordData);
      sordDirty = true;
    }
    if (sordDirty) {
      me.connection.ix().checkinSord(me.sord, SordC.mbAllIndex, LockC.NO);
    }
    if (me.sordMapKeyValues && (me.sordMapKeyValues.length > 0)) {
      me.connection.ix().checkinMap(MapDomainC.DOMAIN_SORD, me.sord.id || me.objId, me.sord.id || me.objId, me.sordMapKeyValues, LockC.NO);
    }
    if (me.sordFormdataValues && (me.sordFormdataValues.length > 0)) { // blob
      me.connection.ix().checkinMap("formdata", me.sord.id || me.objId, me.sord.id || me.objId, me.sordFormdataValues, LockC.NO);
    }
  },

  /**
   * @private
   */
  saveWorkflowValues: function () {
    var me = this;
    if (me.wfMapKeyValues && (me.wfMapKeyValues.length > 0)) {
      if (!me.flowId) {
        throw "Flow ID is empty";
      }
      me.connection.ix().checkinMap(MapDomainC.DOMAIN_WORKFLOW_ACTIVE, me.flowId, me.sord.id || me.objId, me.wfMapKeyValues, LockC.NO);
    }
  },

  /**
   * @private
   * Retrieves a value from the keyword list of the specified field.
   * It checks, if one of the entries in the keyword list starts with the valuePrefix.
   * @param {String} fieldName
   * @param {String} valuePrefix
   * @return {String} The found item from the keyword list or the valuePrefix, if nothing was found
   */
  getValueFromKwl: function (fieldName, valuePrefix) {
    var me = this, kwl, items, i, item;

    kwl = me.connection.ix().checkoutKeywordList(fieldName, KeywordC.mbView, 30, LockC.NO);

    if (kwl) {
      items = kwl.children;
      for (i = 0; i < items.length; i++) {
        item = items[i].text;
        if (item.startsWith(valuePrefix)) {
          return item;
        }
      }
    } else {
      this.logger.warn(["No keywordlist for field '{0}'", fieldName]);
    }

    this.logger.warn(["No keywordlist match for: '{0}'", valuePrefix]);
    return valuePrefix;
  },

  /**
   * @private
   * Retrieves a value from the dynamic keyword list of the spezified field.
   * It checks, if one of the entries in the keyword list starts with the valuePrefix.
   * @param {String} maskName
   * @param {String} fieldName
   * @param {String} valuePrefix
   * @param {Object} config
   * @param {Object} foreignKeyConfig
   * @return {String} The found item from the keyword list or the valuePrefix, if nothing was found
   */
  getValueFromDynKwl: function (maskName, fieldName, valuePrefix, config, foreignKeyConfig) {
    var me = this, params, result;

    params = { data: valuePrefix };

    if (config) {
      params.returnColumn = config.returnColumn;
      params.filterColumn = config.filterColumn;
    }

    if (foreignKeyConfig) {
      foreignKeyConfig.value === undefined
        && me.entries.some(function (entry) {
          return (entry.key === foreignKeyConfig.key)
            && ((foreignKeyConfig.value = entry.value), true); // empty strings are ok
        });
      if (foreignKeyConfig.value === undefined) {
        throw "No value found for foreignKey " + foreignKeyConfig.key + ". Please define an array-entry having said key or define a value directly in `useForeignKey`";
      }
      params.useForeignKey = foreignKeyConfig;
    }

    result = sol.common.SordUtils.getDynamicKeywordlistValue(maskName, fieldName, params);

    if (result.length <= 0) {
      this.logger.warn(["No keywordlist match for: '{0}'", valuePrefix]);
    } else if (result.length > 1) {
      this.logger.warn(["No unique keywordlist match for: '{0}'", valuePrefix]);
    } else {
      valuePrefix = result[0];
    }

    return result;
  }
});

/**
 * @member sol.common.ix.functions.Set
 * @static
 * @inheritdoc sol.common.ix.FunctionBase#onEnterNode
 */
function onEnterNode(clientInfo, userId, wfDiagram, nodeId) {
  var params, module;

  logger.enter("onEnterNode_Set", { flowId: wfDiagram.id, nodeId: nodeId });
  params = sol.common.WfUtils.parseAndCheckParams(wfDiagram, nodeId, "entries");

  params.objId = wfDiagram.objId;
  params.flowId = wfDiagram.id;
  params.wfDiagram = wfDiagram;
  module = sol.create("sol.common.ix.functions.Set", params);

  module.process();

  logger.exit("onEnterNode_Set");
}

/**
 * @member sol.common.ix.functions.Set
 * @static
 * @inheritdoc sol.common.ix.FunctionBase#onExitNode
 */
function onExitNode(clientInfo, userId, wfDiagram, nodeId) {
  var params, module;

  logger.enter("onExitNode_Set", { flowId: wfDiagram.id, nodeId: nodeId });
  params = sol.common.WfUtils.parseAndCheckParams(wfDiagram, nodeId, "entries");

  params.objId = wfDiagram.objId;
  params.flowId = wfDiagram.id;
  params.wfDiagram = wfDiagram;

  module = sol.create("sol.common.ix.functions.Set", params);

  module.process();

  logger.exit("onExitNode_Set");
}


/**
 * @member sol.common.ix.functions.Set
 * @method RF_sol_function_Set
 * @static
 * @inheritdoc sol.common.ix.FunctionBase#RF_FunctionName
 */
function RF_sol_function_Set(ec, args) {
  var params, module;

  logger.enter("RF_sol_function_Set", args);

  params = sol.common.ix.RfUtils.parseAndCheckParams(ec, arguments.callee.name, args, "objId", "entries");

  // Hint: Must never be true within an Registered function call
  // because this would result in a security issue because each person can
  // update each sord object within the repository
  //params.asAdmin = false;
  module = sol.create("sol.common.ix.functions.Set", params);

  module.process();

  logger.exit("RF_sol_function_Set");
}