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

    //@include lib_Class.js
    //@include lib_sol.common.SordUtils.js
    //@include lib_sol.common.ObjectUtils.js
    //@include lib_sol.common.IxUtils.js
    //@include lib_sol.common.AclUtils.js
    //@include lib_sol.common.RepoUtils.js
    //@include lib_sol.common.AsyncUtils.js
    //@include lib_sol.common.UserUtils.js
    //@include lib_sol.common.ObjectFormatter.js
    //@include lib_sol.common.Template.js
    //@include lib_sol.common.SordTypeUtils.js
    //@include lib_sol.common.Map.js
    //@include lib_sol.common.ix.FunctionBase.js

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

    /**
     * Moves an archive element to a new location. Alternatively only references can be created without moving.
     *
     * The path string can contain static parts as well as dynamic parts.
     * Dynamic parts are in [handlebars] (http://handlebarsjs.com/) syntax (see also {@link sol.common.Template})
     * and have access to the sord object in form of a {@link sol.common.ObjectFormatter.TemplateSord TemplateSord}.
     *
     * Please note that handlebars does html-escaping by default if used double-curlies {{ sord.name }}. Therefore trippe-curlies should be used {{{ sord.name }}} since that prevents escaping strings.
     *
     * # 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.
     *
     *     {
     *       "path": "¶Invoice¶Archive¶{{{substring sord.objKeys.INVOICE_DATE 0 4}}}¶{{{substring sord.objKeys.VENDOR_NAME 0 1}}}¶{{{sord.objKeys.VENDOR_NAME}}}",
     *       "rightsConfig": { "mode": "ADD", "inherit": true }
     *     }
     *
     *     {
     *       "path": "¶Invoice¶{{formatDate 'YYYY-MM-DD' sord.IDateIso}}"
     *     }
     *
     * # As IX function call
     *
     * In addition to the workflow node configuration the objId must be passed.
     *
     *     sol.common.IxUtils.execute("RF_sol_function_Move", {
     *       "objId": "4711",
     *       "path": "¶Invoice¶Archive¶{{{substring sord.objKeys.INVOICE_DATE 0 4}}}¶{{{substring sord.objKeys.VENDOR_NAME 0 1}}}¶{{{sord.objKeys.VENDOR_NAME}}}",
     *       "adjustRights": true
     *     });
     *
     * Move move object and keep reference on old location
     *
     *     sol.common.IxUtils.execute("RF_sol_function_Move", {
     *       "objId": "4711",
     *       "path": "¶Invoice¶Archive¶{{{substring sord.objKeys.INVOICE_DATE 0 4}}}¶{{{substring sord.objKeys.VENDOR_NAME 0 1}}}¶{{{sord.objKeys.VENDOR_NAME}}}",
     *       "refOldParentId": true
     *     });
     *
     * Set multiple references without moving
     *
     *     sol.common.IxUtils.execute("RF_sol_function_Move", {
     *       "objId": "4711",
     *       "referenceIds": ["1815", "1816", "1817"]
     *     });
     *
     *     sol.common.IxUtils.execute("RF_sol_function_Move", {
     *       "objId": "4711",
     *       "referencePaths": ["¶Invoice¶Archive¶{{substring sord.objKeys.INVOICE_DATE 0 4}}¶{{substring sord.objKeys.VENDOR_NAME 0 1}}¶{{sord.objKeys.VENDOR_NAME}}",
     *                         "¶Invoice¶Backup¶{{substring sord.objKeys.INVOICE_DATE 0 4}}¶{{substring sord.objKeys.VENDOR_NAME 0 1}}¶{{sord.objKeys.VENDOR_NAME}}"]
     *     });
     *
     * @author PZ, ELO Digital Office GmbH
     * @version 1.03.000
     *
     * @eloix
     *
     * @requires  sol.common.SordUtils
     * @requires  sol.common.ObjectUtils
     * @requires  sol.common.IxUtils
     * @requires  sol.common.JsonUtils
     * @requires  sol.common.RepoUtils
     * @requires  sol.common.ObjectFormatter
     * @requires  sol.common.Template
     * @requires  sol.common.WfUtils
     * @requires  sol.common.UserUtils
     * @requires  sol.common.ix.RfUtils
     * @requires  sol.common.Map
     * @requires  sol.common.ix.FunctionBase
     */
    sol.define("sol.common.ix.functions.Move", {
      extend: "sol.common.ix.FunctionBase",

      requiredConfig: ["objId"],

      /**
       * @cfg {String} objId (required) ObjId, GUID or ARCPATH
       */
      objId: undefined,

      /**
       * @cfg {String} path Either `path` or `targetId` has to be defined; `targetId` has priority; exception: if there are `referenceIds` or `referencePaths` are defined, in that case the original will not be moved;
       */
      path: undefined,

      /**
       * @cfg {Boolean} deleteIfPathNotFound If set, and the path does not exists, the sord will be deleted instead of being moved. This is only possible, if the sord is in the chaos cabinet.
       */
      deleteIfPathNotFound: false,

      /**
       * @cfg {String} targetId Either `path` or `targetId` has to be defined; `targetId` has priority; exception: if there are `referenceIds` or `referencePaths` are defined, in that case the original will not be moved;
       */
      targetId: undefined,

      /**
       * @cfg {String} targetIdFromMap reads the targetId from a MAP-field. Priority: targetIdFromMap > targetId > path
       */
      targetIdFromMap: undefined,

      /**
       * @cfg {Object} targetFromElementService ready target id from an elementService configuration
       */

      /**
       * @cfg {Boolean} refOldParentId If set, a reference to original position will be created after move
       */
      refOldParentId: undefined,

      /**
       * @cfg {Boolean} relative If set to true and a path without an ARCPATH is defined, the sord will be moved relative to its parent.
       */
      relative: false,

      /**
       * @cfg {String[]} referenceIds If set, references will be created
       */
      referenceIds: undefined,

      /**
       * @cfg {String[]} referencePaths If set, references will be created
       */
      referencePaths: undefined,

      /**
       * @cfg {Object} rightsConfig
       * Rights that will be set to the workflow object and it's children
       *
       * Example:
       *
       *     { "inherit": true, "users": ["user1"], "rights": { "r": true } }
       *
       * The ACL will be inherited from the parent object and in addition the read right for the user "user1" will be set.
       */
      rightsConfig: undefined,

      /**
       * @cfg {Boolean} adjustRights
       * If true, the rights will be inherited from the parent
       */

      /**
       * @cfg {String} rightsMode
       * Rights mode, e.g. `SET` or `ADD`. With this property it´s possible to overwrite the given rights mode within the `rightsConfig` property.
       */

      /**
       * @cfg {String|Number} sordType
       * Name or ID of a sord type for the newly created elements. Fallback is the IX standard behaviour.
       */

      /**
       * @cfg {String} mask
       * If set, newly created parts of the path get that mask.
       */

      /**
       * @since 1.05.000
       * @cfg {Boolean} [ignoreInvalidRefPaths=false]
       * If this is `true`, there will be no error if one of the {@link #referencePaths} is invalid and the invalid paths will just be ignored.
       */

      /**
       * @cfg {Boolean} [asAdmin=true]
       * Execute function with administrator rights
       */

      initialize: function (config) {
        var me = this;
        me.$super("sol.common.ix.FunctionBase", "initialize", [config]);
        if (!me.targetId && !me.targetIdFromMap && !me.path && !me.refOldParentId && !me.referenceIds && !me.referencePaths && !me.targetFromElementService) {
          me.logger.error("IllegalArgumentException: either 'targetId' or 'targetIdFromMap' or 'path' or 'refOldParentId' or 'referenceIds' or 'referencePaths' or 'targetFromElementService' has to be defined");
          throw "IllegalArgumentException: either 'targetId' or 'targetIdFromMap' or 'path' or 'refOldParentId' or 'referenceIds' or 'referencePaths' has to be defined";
        }
      },

      /**
       * Moves the element to a new location or set references.
       */
      process: function () {
        var me = this,
            conn, sord, wfTplSord, oldParentId, newParentId, i, refId, sordMap, mapValue,
            deleteIfPathNotFound, target;

        me.asAdmin = (typeof me.asAdmin == "undefined") ? true : me.asAdmin;

        conn = me.asAdmin ? ixConnectAdmin : ixConnect;

        me.logger.debug(["asAdmin={0}, conn.user.name={1}", me.asAdmin, conn.loginResult.user.name]);

        sord = conn.ix().checkoutSord(me.objId, EditInfoC.mbSord, LockC.NO).sord;

        oldParentId = sord.parentId;
        if (me.relative && me.path && !(me.path.indexOf("ARCPATH") == 0) && +oldParentId) { // do nothing for objId 0
          me.path = "ARCPATH[" + sol.common.RepoUtils.getGuid(oldParentId) + "]:" + me.path;
        }

        deleteIfPathNotFound = (me.deleteIfPathNotFound === true) && (+(oldParentId) === 0); // only allow sords in chaos cabinet for now.

        if (me.wFDiagram) {
          me.logger.debug(["templateName={0}", me.wFDiagram.templateName]);
        }

        if (typeof me.targetIdFromMap === "string" && me.targetIdFromMap) {
          sordMap = sol.create("sol.common.SordMap", {
            objId: me.objId
          });
          sordMap.read();
          mapValue = sordMap.getValue(me.targetIdFromMap);

          if (mapValue) {
            mapValue = String(mapValue);
            if (mapValue) {
              me.targetId = mapValue;
              me.logger.info(["using targetId {0} from map-field `{1}`.", me.targetId, me.targetIdFromMap]);
            }
          }
        } else if (typeof me.targetFromElementService && me.targetFromElementService) {
          me.logger.info(["receive sord by elementService {0}", JSON.stringify(me.targetFromElementService)]);
          me.targetId = me.findByElementService(me.targetFromElementService).id;
        }

        if (me.path) {
          me.logger.debug(["path={0}", me.path]);
          if (!me.targetId) {
            if (me.flowId) {
              wfTplSord = sol.common.WfUtils.getTemplateSord(sord, me.flowId, { formBlobs: true });
            }
            target = sol.common.RepoUtils.preparePath(me.path, { data: wfTplSord || sord, sordType: me.sordType, mask: me.mask, skipIfNotExists: deleteIfPathNotFound, returnDetails: true });
            if (target.objId) {
              me.targetId = target.objId;
              me.logger.info(["target path OK {0}.", target.path]);
            } else if (deleteIfPathNotFound && target.skipped) {
              me.logger.info(["deleteIfPathNotFound is defined. target path not found {0}. Deleting sord {1}", target.path, me.objId]);
              conn.ix().deleteSord(oldParentId, me.objId, LockC.NO, (new DeleteOptions()));
              return;
            }
          }
        }
        if (!me.referenceIds && !me.referencePaths) {
          if (!me.targetId) {
            me.logger.error(["could not resolve path: {0}", me.path]);
            throw Error("could not resolve path: " + me.path);
          }
        }

        me.logger.debug(["targetId {0}", me.targetId]);

        if (me.targetId) {
          newParentId = conn.ix().checkoutSord(me.targetId, EditInfoC.mbOnlyId, LockC.NO).sord.id; // make sure, newParentId is a number (targetId might be an arcpath)
          me.logger.info(["move sord (objId={0}, name={1}): {2} -> {3}", sord.id, sord.name, oldParentId, newParentId]);
          conn.ix().refSord(oldParentId, newParentId, sord.id, -1);

          if (me.refOldParentId) {
            conn.ix().refSord("", oldParentId, sord.id, -1);
          }
        }
        if (!me.referenceIds) {
          me.referenceIds = [];
          if (me.referencePaths instanceof Array) {
            for (i = 0; i < me.referencePaths.length; i++) {
              if (me.ignoreInvalidRefPaths !== true || me.isValidPath(me.referencePaths[i])) {
                try {
                  refId = sol.common.RepoUtils.preparePath(me.referencePaths[i], { data: wfTplSord || sord, sordType: me.sordType });
                } catch (e) {
                  if (me.ignoreInvalidRefPaths !== true) {
                    throw e;
                  }
                }
              }
              if (refId) {
                me.referenceIds.push(refId);
              }
            }
          }
        }
        for (i = 0; i < me.referenceIds.length; i++) {
          conn.ix().refSord("", me.referenceIds[i], sord.id, -1);
        }
        if ((me.rightsConfig || me.adjustRights) && (me.targetId)) {
          if (!me.rightsConfig && me.adjustRights) {
            me.rightsConfig = { mode: "SET", inherit: true };
          } else {
            me.rightsConfig = JSON.parse(JSON.stringify(me.rightsConfig));
            if (me.rightsMode) {
              me.rightsConfig.mode = me.rightsMode;
            }
          }
          sol.common.AclUtils.changeRightsInBackground(sord.id, me.rightsConfig);
        }
      },

      /**
       * @private
       * Checks if a Path is valid.
       * @param {String} path
       * @return {Boolean}
       */
      isValidPath: function (path) {
        var separator, regex;

        separator = path.charAt(0);
        regex = new RegExp(separator + separator + "|" + separator + "$"); // checks for duplicated separators or if the string ends with the separator

        return !regex.test(path);
      },

      findByElementService: function (elementServiceCfg) {
        var me = this, args, targetSord;
        args = sol.common.ObjectUtils.clone(elementServiceCfg.args || {});
        if (elementServiceCfg.objId === "fromParams") {
          args.objId = me.objId;
        }

        me.logger.debug(["use elementService {0}", elementServiceCfg.name, JSON.stringify(args)]);
        targetSord = sol.common.IxUtils.execute(elementServiceCfg.name, args);

        if (!targetSord) {
          me.logger.error(["Could not retrieve targetSord by elementService {0}, {1}", elementServiceCfg.name, JSON.stringify(args)]);
          throw Error("Could not retrieve targetSord by elementService");
        }
        me.logger.debug(["determine targetFolder {0}", targetSord.id]);
        return targetSord;
      }

    });

    /**
     * @member sol.common.ix.functions.Move
     * @static
     * @inheritdoc sol.common.ix.FunctionBase#onEnterNode
     */
    function onEnterNode(_clInfo, _userId, wFDiagram, nodeId) {
      var params, module;

      logger.enter("onEnterNode_Move", { flowId: wFDiagram.id, nodeId: nodeId });

      sol.common.WfUtils.checkMainAdminWf(wFDiagram);
      params = sol.common.WfUtils.parseAndCheckParams(wFDiagram, nodeId);
      params.objId = String(wFDiagram.objId);
      params.flowId = String(wFDiagram.id);
      params.wFDiagram = wFDiagram;

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

      logger.exit("onEnterNode_Move");
    }

    /**
     * @member sol.common.ix.functions.Move
     * @static
     * @inheritdoc sol.common.ix.FunctionBase#onExitNode
     */
    function onExitNode(_clInfo, _userId, wFDiagram, nodeId) {
      var params, module;

      logger.enter("onExitNode_Move", { flowId: wFDiagram.id, nodeId: nodeId });

      sol.common.WfUtils.checkMainAdminWf(wFDiagram);
      params = sol.common.WfUtils.parseAndCheckParams(wFDiagram, nodeId);
      params.objId = String(wFDiagram.objId);
      params.flowId = String(wFDiagram.id);
      params.wFDiagram = wFDiagram;

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

      logger.exit("onExitNode_Move");
    }

    /**
     * @member sol.common.ix.functions.Move
     * @method RF_sol_function_Move
     * @static
     * @inheritdoc sol.common.ix.FunctionBase#RF_FunctionName
     */
    function RF_sol_function_Move(iXSEContext, args) {
      var params, module;
      logger.enter("RF_sol_function_Move", args);

      params = sol.common.ix.RfUtils.parseAndCheckParams(iXSEContext, arguments.callee.name, args, "objId");
      sol.common.ix.RfUtils.checkMainAdminRights(iXSEContext.user, params);

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

      logger.exit("RF_sol_function_Move");
    }