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

//@include lib_Class.js
//@include lib_sol.common.Template.js
//@include lib_sol.common.WfUtils.js
//@include lib_sol.common.JsonUtils.js
//@include lib_sol.common.RepoUtils.js
//@include lib_sol.common.AclUtils.js
//@include lib_sol.common.AsyncUtils.js
//@include lib_sol.common.SordUtils.js
//@include lib_sol.common.SordTypeUtils.js
//@include lib_sol.common.IxUtils.js
//@include lib_sol.common.ix.RfUtils.js
//@include lib_sol.common.ix.FunctionBase.js

/**
 * Creates a sord in the chaos cabinet (0) based on a template or from scratch. Executes specified actions on the created element.
 * Returns the objId (and flowId, if a workflow has been started) of the created element if called as an RF.
 *
 * Usually, this function will be used in conjunction with {@link sol.common.ix.functions.FillSord FillSord}, which
 * can be used to fill metadata-fields of the created sord. (CreateSord can be called from within FillSord)
 *
 * ### Creating a sord using a template
 *
 * A template can be copied, resulting in a new sord.
 *
 *     {
 *       sourceElement: { objId: "ARCPATH:/path/to/my/template" }
 *     }
 *
 * Optionally, the templates permissions (acl) can be applied to the created sord:
 *
 *     {
 *       sourceElement: { objId: "ARCPATH:/path/to/my/template" },
 *       options: { copySourceAcl: true }
 *     }
 *
 * ### Creating a sord from scratch
 *
 * If no template is available, the following can be used to create a new sord from scratch
 *
 *     {
 *       sourceElement: { fromScratch: { mask: "Basic Entry", type: "sol.mytype" } }
 *     }
 *
 * ### Retrieving an objId from a service
 *
 * If the sord creation requires a more sophisticated process than a simple template-copy or `fromScratch`,
 * one can define an IX-RF-service/function which e.g. speaks to external systems and returns the objId
 * of the created sord.
 *
 *     {
 *       sourceElement: {
 *         fromService: { name: "RF_CreateSordByDate", params: { a: "x", b: "z"} }
 *       }
 *     }
 *
 *     // this service must return one of the following objects:
 *     { objId: "123456" }
 *     { fromScratch: { mask: "Basic Entry", type: undefined } } // type is optional
 *
 * ### Moving the created sord into a folder
 *
 * As a default, the sord is created in the chaos cabinet (objId 0), however, one must define
 * an objId/ARCPATH/GUID. The created sord will then be moved into this folder after creation.
 * It is possible to define "0" here, if one wants to leave the created sord in the chaos cabinet.
 *
 *     { targetFolder: { objId: "ARCPATH:/where/all/my/sords/go" } }
 *
 * ### Determining the targetFolder objId from a service
 *
 * A Service can be defined instead of specifying a fixed path/objId
 *
 *     {
 *       targetFolder: {
 *         fromService: { name: "RF_PrepareTargetFolder", params: { a: "x", b: "z"} }
 *       }
 *     }
 *
 *     // this service must return an object having an objId or id property:
 *     { objId: "123456" }
 *     { id: "123456" }
 *
 * ### Setting Permissions
 *
 * {@link sol.common.AclUtils.changeRightsInBackground Permissions} are set while copying the template or after creating the sord from scratch.
 *
 *     {
 *       onCreatedElement: {
 *         setPermissions: {
 *           users: [ { name: "MY_GROUP", rights: { r: true, w: true } } ],
 *           recursive: true,
 *           mode: "SET"
 *         }
 *       }
 *     }
 *
 * ### Starting a workflow
 *
 * A workflow can be started by defining the workflow template name and optionally a title
 *
 *     {
 *       onCreatedElement: {
 *         startWorkflow: { name: "sol.mymodule.workflow", title: "this is a workflow title" }
 *       }
 *     }
 *
 * ### Examples
 *
 * ### Sord from scratch, fixed targetFolder and a workflow
 *
 * #### Arguments
 *
 *     {
 *       sourceElement: {
 *         fromScratch: { mask: "Basic Entry", type: "sol.mytype" }
 *       },
 *       targetFolder: { objId: "ARCPATH:/my/target/folder" },
 *       onCreatedElement: {
 *         setName: "My Sord Short Description",
 *         startWorkflow: { name: "sol.mymodule.myworkflow", title: "A workflow title" }
 *       }
 *     }
 *
 * #### Return value
 *
 *     {
 *       objId: "3982",
 *       flowId: "89"
 *     }
 *
 * @author ESt, ELO Digital Office GmbH
 * @version 1.0
 *
 * @eloix
 *
 * @requires sol.common.Template
 * @requires sol.common.WfUtils
 * @requires sol.common.JsonUtils
 * @requires sol.common.RepoUtils
 * @requires sol.common.SordUtils
 * @requires sol.common.AclUtils
 * @requires sol.common.AsyncUtils
 * @requires sol.common.SordTypeUtils
 * @requires sol.common.IxUtils
 * @requires sol.common.ix.RfUtils
 * @requires sol.common.ix.FunctionBase
 *
 */
sol.define("sol.common.ix.functions.CreateSord", {
  extend: "sol.common.ix.FunctionBase",

  /**
   * @cfg {Object} sourceElement (optional) Defines, how the new sord will be created (copy template by `objId` or create new `fromScratch`)
   * @cfg {String} sourceElement.objId objId/ARCPATH/GUID of sord to use as template (optional if `fromScratch` is defined)
   * @cfg {Object} sourceElement.fromScratch (optional) options for creating the sord from Scratch
   * @cfg {String} [sourceElement.fromScratch.mask = "Basic Entry"] (optional) mask name
   * @cfg {String} sourceElement.fromScratch.type (optional) sord type name
   * @cfg {Object} sourceElement.fromService (optional) defines a service which must return an Object containing an `objId` or `fromScratch` definition
   * @cfg {String} sourceElement.fromService.name IX-RF name
   * @cfg {Object} [sourceElement.fromService.params = {}] (optional) argument which will be passed to the defined service
   * @cfg {Object} sourceElement.options (optional) additional options used during creation of the new element
   * @cfg {Boolean} [sourceElement.options.copySourceAcl = false] (optional) copy permissions of source element (only when `sourceElement.objId` is defined)
   */

  /**
   * @cfg {Object} targetFolder (optional) Defines, where the created sord should be put
   * @cfg {String} targetFolder.objId objId/ARCPATH/GUID of folder to put created sord in
   * @cfg {String} targetFolder.path (optional) path to folder (which will may created on the fly) NOT IMPLEMENTED YET!
   * @cfg {Object} targetFolder.fromService (optional) defines a service which must return an Object containing an `objId` (or `path` not implemented yet, see above) definition
   * @cfg {String} targetFolder.fromService.name IX-RF name
   * @cfg {Object} [targetFolder.fromService.params = {}] (optional) argument which will be passed to the defined service
   */

  /**
   * @cfg {Object} onCreatedElement (optional) Defines, which actions will pe performed on the created sord
   * @cfg {String} onCreatedElement.setName (optional) sets the sords short-description
   * @cfg {Object} onCreatedElement.setOwner (optional) tries to set `sord.userId` (owner) ...
   * @cfg {Boolean} [onCreatedElement.setOwner.fromConnection = false] (optional) ... to the current connection's user
   * @cfg {String} onCreatedElement.setOwner.toUser (optional) ... to the specified user NOT IMPLEMENTED YET!
   * @cfg {Object} [onCreatedElement.setPermissions = {}] (optional) sets {@link sol.common.AclUtils.changeRightsInBackground permissions} on the created sord
   * @cfg {Object} onCreatedElement.startWorkflow (optional) starts a workflow on the created sord
   * @cfg {String} onCreatedElement.startWorkflow.name workflow template name
   * @cfg {String} [onCreatedElement.startWorkflow.title = "Workflow"] (optional) specifies the workflow-title
   * @cfg {String} onCreatedElement.startWorkflow.startMaskStandardWorkflow (optional) start workflow defined in the specified field NOT IMPLEMENTED YET!
   */

  /**
   * @private
   * @property
   *
   * References objId and flowId
   */

  _moveFct: "RF_sol_function_Move",

  createSordFromScratch: function (cfg) {
    var me = this,
        sord,
        mask = cfg.mask || "Basic Entry",
        user = (me.user && me.user.id) || me.user,
        targetObjId;

    try {
      targetObjId = me.getTargetForCreatedSord().objId;

      me.logger.debug("Trying to create sord from scratch using configuration ", [cfg, targetObjId]);

      sord = ixConnect.ix().createSord(targetObjId, mask, EditInfoC.mbSord).sord;
    } catch (e) {
      me.logger.debug("could not create sord", e);
      throw new Error("CreateSord: could not create sord in chaos cabinet. Mask not available or insufficient permissions? mask:`" + mask + "`");
    }

    sord.name = (me.onCreatedElement && me.onCreatedElement.setName) || "temp";

    if (cfg.type) {
      try {
        sord.type = sol.common.SordTypeUtils.getSordTypeId(cfg.type);
      } catch (e) {
        me.logger.debug("could not retrieve sordtype via id", e);
        throw new Error("CreateSord: could not retrieve sordtype via id `" + cfg.type + "`");
      }
    }

    if (me.onCreatedElement && me.onCreatedElement.setOwner && ((me.onCreatedElement.setOwner.fromConnection && me.user) || (me.onCreatedElement.setOwner.toUser))) {
      if (me.onCreatedElement.setOwner.toUser) {
        throw new Error("CreateSord: `onCreatedElement.setOwner.toUser` is not implemented yet. Please define `onCreatedElement.setOwner.fromConnection` instead");
      }
      if (user) {
        sord.userId = user;
      }
    }

    try {
      return String(ixConnect.ix().checkinSord(sord, SordC.mbAllIndex, LockC.NO));
    } catch (e) {
      me.logger.debug("creating the sord failed during checkin", e);
      throw new Error("CreateSord: creating the sord failed during checkin. Invalid user?` user:" + cfg.user + "`");
    }
  },

  copyElement: function (cfg) {
    var me = this;
    me.logger.debug("Copy Template using configuration ", cfg);
    try {
      return sol.common.IxUtils.execute("RF_sol_function_CopyFolderContents", {
        objId: me.getTargetForCreatedSord().objId,
        source: String(cfg.objId),
        copySourceAcl: !!(cfg.options && cfg.options.copySourceAcl),
        inheritDestinationAcl: false,
        name: (me.onCreatedElement && me.onCreatedElement.setName) || "temp"
      });
    } catch (e) {
      me.logger.debug("could not copy template sord", e);
      throw new Error("CreateSord: could not copy template sord. Insufficient permissions for objId `" + cfg.objId + "`?");
    }
  },

  getTargetForCreatedSord: function () {
    var me = this,
        path = String(me.targetFolder.path || me.targetFolder.objId || "0"),
        preparePathConfig = {
          mask: me.targetFolder.mask,
          skipIfNotExists: false,
          returnDetails: true
        };

    return sol.common.RepoUtils.isObjId(path)
      ? { objId: path }
      : sol.common.RepoUtils.preparePath(path, preparePathConfig);
  },

  prepareSourceElement: function (src) {
    var me = this;
    if (!src) {
      throw new Error("CreateSord: `sourceElement` parameter not defined.");
    }
    if (src.objId) {
      me._createdElement.objId = String(me.copyElement(src));
    } else if (src.fromScratch) {
      me._createdElement.objId = me.createSordFromScratch(src.fromScratch, src.options);
    } else if (src.fromService && src.fromService.name) {
      try {
        me.logger.debug("sord creation fromService:", src.fromService);
        me.prepareSourceElement(sol.common.IxUtils.execute(src.fromService.name, src.fromService.params || {}));
      } catch (e) {
        me.logger.debug("sord creation fromService failed", e);
        throw new Error("CreateSord: sord creation fromService failed. Used RF:`" + src.fromService.name);
      }
    } else {
      throw new Error("Could not determine source in CreateSord. Source config must contain a property `objId`, `fromScratch` or `fromService` which defines a service returning said properties.");
    }
  },

  prepareTargetFolder: function (tgt) {
    var me = this;
    if (!tgt) {
      throw new Error("CreateSord: `targetFolder` parameter not defined.");
    }
    if (tgt.objId || tgt.id) {
      me._createdElement.targetPathId = String(tgt.objId || tgt.id);
    } else if (tgt.path) {
      if (!me.preparePath) {
        throw new Error("CreateSord: `targetFolder.path` is not implemented yet. Please define a `targetFolder.objId` instead");
      }
      me._createdElement.targetPathId = me.preparePath(String(tgt.path));
    } else if (tgt.fromService && tgt.fromService.name) {
      try {
        me.logger.debug("sord creation fromService:", tgt.fromService);
        me.prepareTargetFolder(sol.common.IxUtils.execute(tgt.fromService.name, tgt.fromService.params || {}));
      } catch (e) {
        me.logger.debug("target creation fromService failed", e);
        throw new Error("CreateSord: target creation fromService failed. Used RF:`" + tgt.fromService.name);
      }
    } else {
      throw new Error("Could not determine target folder in CreateSord. targetFolder config must contain a property `objId`, `path`, or `fromService` which defines a service returning said properties.");
    }
  },

  moveElement: function (rights) {
    var me = this,
        moveConfig = {
          objId: me._createdElement.objId,
          path: me._createdElement.targetPathId
        };

    if (rights) {
      moveConfig.rightsConfig = rights;
    }
    me.logger.debug("Moving created sord using config", moveConfig);

    try {
      sol.common.IxUtils.execute(me._moveFct, moveConfig);
    } catch (e) {
      me.logger.debug("moving the created sord failed", e);
      throw new Error("CreateSord: moving the created sord failed. Used RF:`" + me._moveFct);
    }
  },

  setElementPermissions: function (cfg) {
    var me = this;
    me.logger.debug("Setting sord permissions using config", cfg);
    sol.common.AclUtils.changeRightsInBackground(me._createdElement.objId, cfg || {});
  },

  startWorkflowOnElement: function (cfg) {
    var me = this, title = cfg.title || "Workflow";
    if (cfg.startMaskStandardWorkflow) {
      throw new Error("CreateSord: `startWorkflow.startMaskStandardWorkflow` is not implemented yet. Please define a `startWorkflow.name` instead");
    }
    me.logger.debug("Starting workflow using config", cfg);
    try {
      me._createdElement.flowId = sol.common.WfUtils.startWorkflow(cfg.name, title, me._createdElement.objId);
    } catch (e) {
      me.logger.debug("workflow start failed", e);
      throw new Error("CreateSord: starting workflow `" + cfg.name + "` on sord with objId `" + me._createdElement.objId + "` failed.`");
    }
  },

  finalizeCreatedElement: function (cfg) {
    var me = this;
    cfg = cfg || {};
    me.logger.debug("Finalizing created sord using config", cfg);
    if (me._createdElement.targetPathId !== "0") {
      me.moveElement(cfg.setPermissions);
    } else if (cfg.setPermissions) {
      me.setElementPermissions(cfg.setPermissions);
    }
    if (cfg.removePermissions) {
      cfg.removePermissions.mode = "REMOVE";
      me.setElementPermissions(cfg.removePermissions);
    }
    if (cfg.startWorkflow) {
      me.startWorkflowOnElement(cfg.startWorkflow);
    }
  },

  getServiceResult: function (element) {
    var me = this, result = { objId: element.objId };
    if (element.flowId) {
      result.flowId = String(element.flowId);
    }
    me.logger.debug("Sord has been created:", result);
    return result;
  },

  /**
   * @return {Object}
   * @return {String} return.objId objId of created sord
   * @return {String} return.flowId (optional) flowId, if a workflow has been started on the created sord
   */
  process: function () {
    var me = this;
    me._createdElement = {};

    me.logger.debug("Preparing `sourceElement`", me.sourceElement);
    me.prepareSourceElement(me.sourceElement);
    me.logger.debug("Created element so far", me._createdElement);
    me.logger.debug("Preparing `targetFolder`", me.targetFolder);
    me.prepareTargetFolder(me.targetFolder);
    me.logger.debug("Created element so far", me._createdElement);
    me.finalizeCreatedElement(me.onCreatedElement);

    return me.getServiceResult(me._createdElement);
  }
});

/**
 * @member sol.common.ix.functions.CreateSord
 * @static
 * @inheritdoc sol.common.ix.FunctionBase#onEnterNode
 */
function onEnterNode(clInfo, userId, wfDiagram, nodeId) {
  var args, fun;

  args = sol.common.WfUtils.parseAndCheckParams(wfDiagram, nodeId);

  args.sourceElement = args.sourceElement || { objId: wfDiagram.objId, flowId: wfDiagram.id };

  fun = sol.create("sol.common.ix.functions.CreateSord", args);

  fun.process();
}

/**
 * @member sol.common.ix.functions.CreateSord
 * @static
 * @inheritdoc sol.common.ix.FunctionBase#onExitNode
 */
function onExitNode(clInfo, userId, wfDiagram, nodeId) {
  var args, fun;

  args = sol.common.WfUtils.parseAndCheckParams(wfDiagram, nodeId);

  args.sourceElement = args.sourceElement || { objId: wfDiagram.objId, flowId: wfDiagram.id };

  fun = sol.create("sol.common.ix.functions.CreateSord", args);

  fun.process();
}


/**
 * @member sol.common.ix.functions.CreateSord
 * @method RF_sol_function_CreateSord
 * @static
 * @inheritdoc sol.common.ix.FunctionBase#RF_FunctionName
 */
function RF_sol_function_CreateSord(iXSEContext, args) {
  var rfArgs, fun, logger = sol.create("sol.Logger", { scope: "sol.common.ix.functions.CreateSord" });
  logger.enter("RF_sol_function_CreateSord");

  rfArgs = sol.common.ix.RfUtils.parseAndCheckParams(iXSEContext, arguments.callee.name, args);

  fun = sol.create("sol.common.ix.functions.CreateSord", rfArgs);

  logger.exit("RF_sol_function_CreateSord");
  return JSON.stringify(fun.process());
}