//@include lib_Class.js
//@include lib_sol.common.SordUtils.js
//@include lib_sol.common.RepoUtils.js
//@include lib_sol.common.AclUtils.js
//@include lib_sol.common.AsyncUtils.js
//@include lib_sol.common.SordTypeUtils.js
//@include lib_sol.common.IxUtils.js
//@include lib_sol.common.TranslateTerms.js
//@include lib_sol.common.Template.js
//@include lib_sol.common.ix.ActionBase.js

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

/**
 * An generic action to do most common tasks.
 *
 * # Prefill metadata
 * The metadata of an element can be prefilled. There are several possible configurations.
 * For the configuration the {@link #$metadata} property will be used.
 *
 * ## Special properties (always)
 *
 * - owner: The owner of the element can be set to the connection user (only used on new elements)
 * - solType: The field `SOL_TYPE` can be set to a fixed value
 *
 * ## Fixed values (always)
 * The ObjKeys of an element can always be set to fixe values by providing a `key` and a fix `value`.
 *
 * ## Copy data from a source element
 * Planned for future versions
 *
 *
 * @author PZ, ELO Digital Office GmbH
 * @version 1.04.000
 *
 * @eloix
 * @requires sol.common.SordUtils
 * @requires sol.common.RepoUtils
 * @requires sol.common.AclUtils
 * @requires sol.common.AsyncUtils
 * @requires sol.common.SordTypeUtils
 * @requires sol.common.IxUtils
 * @requires sol.common.TranslateTerms
 * @requires sol.common.Template
 * @requires sol.common.ix.ActionBase
 */
sol.define("sol.common.ix.actions.Standard", {
  extend: "sol.common.ix.ActionBase",

  _SordZ: SordC.mbAllIndex,

  /**
   * @cfg {String} $name (optional) The name of the action (returned by {@link #getName}). If not set, the name will be 'sol.common.ix.actions.Standard'.
   */

  /**
   * @cfg {Object} $new (optional) Definition for creating a new element (see also {@link #objId}).
   * @cfg {String} $new.name (optional) Handlebars syntax.
   * @cfg {String} $new.mask (optional) If a new element is created (not from template) this mask will be used. Default will be `DocMaskC.GUID_FOLDER`.
   * @cfg {String} $new.type (optional) If a new element is created (not from template) this type will be used.
   * @cfg {Object} $new.target (optional) Default will be 'DEFAULT' mode
   * @cfg {String} $new.target.mode (optional) Supported are DEFAULT (chaos cabinet), SELECTED ({@link #objId} as target) and FIND (determine target by search)
   * @cfg {String} $new.target.params (optional) Required, if `mode=FIND` is used (for configuration see {@link sol.common.RepoUtils#getObjIdByIndex})
   * @cfg {Object} $new.template (optional) Definition for creating an element by copying a template structure.
   * @cfg {String} $new.template.objId (optional) Arcpath, objId or GUID of a template. This has priority over '$new.template.base' and '$new.template.name'.
   * @cfg {String} $new.template.base (optional) The base folder (arcpath, objId or GUID) containing the templates. If used, '$new.template.name' has to be defined.
   * @cfg {String} $new.template.name (optional) The name of a sub folder (the template) in the base folder. If used, '$new.template.base' has to be defined.
   */

  /**
   * @cfg {Object} $metadata (optional) Definition of additional metadata
   * @cfg {String} $metadata.solType (optional) The SOL_TYPE of the object
   * @cfg {Object} $metadata.owner (optional)
   * @cfg {Boolean} $metadata.owner.fromConnection (optional) Sets the elements owner to the connection user (only for new elements)
   * @cfg {Object[]} $metadata.objKeys (optional) Sets the elements ObjKeys
   * @cfg {String} $metadata.objKeys.key The ObjKeys name
   * @cfg {String} $metadata.objKeys.value The ObjKeys value
   */

  /**
   * @cfg {Object} $permissions (optional)
   * @cfg {String} $permissions.mode (optional) Supported values are "ADD", "SET" and "REMOVE". See {@link sol.common.AclUtils AclUtils} for further documentation and default.
   * @cfg {Object} $permissions.inherit (optional) If `$new.target.mode=DEFAULT` is used, this parameter will be ignored.
   * @cfg {Boolean} $permissions.inherit.fromDirectParent (optional) The parent ACL will be used. Has priority over `solutionObjectTypes`.
   * @cfg {String[]} $permissions.inherit.solutionObjectTypes (optional) Searches in hierarchy for the specified SOL_TYPES and applies those objects ACL.
   * @cfg {Boolean} [$permissions.copySource=false] (optional) Has only an effect, if a new element is created from a template. The ACL of the template will be copied.Has priority over `fromDirectParent`.
   */

  /**
   * @cfg {Object} $wf
   * @cfg {String} $wf.name (optional) Handlebars syntax supported. If not set, the sord name will be used.
   * @cfg {Object} $wf.template Defines the workflow, which should be started. Either name or key has to be defined.
   * @cfg {String} $wf.template.name (optional) The name of the workflow template which should be started. This has priority over `$wf.template.key`.
   * @cfg {String} $wf.template.key (optional) The field to read the name of the workflow template which should be started from.
   */

  /**
   * @cfg {Object[]} $events (optional)
   * @cfg {String} $events.id See {@link sol.common.IxUtils#CONST.EVENT_TYPES}. Currently supported: REFRESH, GOTO, DIALOG and FEEDBACK.
   * @cfg {String} $events.onWfStatus (optional)
   */

  /**
   * @cfg {String} objId (optional) Start the action on an existing element. If there is a {@link #$new} configuration with `target` set to `mode=SELECTD` this will be used as target.
   */

  //requiredConfig: [],

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

    me.$super("sol.common.ix.ActionBase", "initialize", [config]);
  },

  getName: function () {
    var me = this;
    return me.$name || "sol.common.ix.actions.Standard";
  },

  /**
   *
   */
  process: function () {
    var me = this;

    me._ctx = {};  // used to carry data
    me._state = { dirty: false };  // used to track internal state

    me.initializeElement();
    me.editMetadata();
    me.saveChanges();

    me.applyPermissions();

    me.startWorkflow();

    me.addEvents();

  },

  /**
   * Creates a new element, either from scratch or by copying a template, if {@link #$new} is defined.
   * If there is no configuration for creating an element, an existing element will be used.
   * The new `objId` will be saved to the execution context (`_ctx.objId`).
   * If {@link #$new} is not defined the given objId will be used as _ctx.objId.
   */
  initializeElement: function () {
    var me = this;

    if (me.$new && me.$new.template) { // create element from template
      me._ctx.objId = me.createElementFromTemplate();
      me._state.new = true;
    } else if (me.$new) { // create new element
      me._sord = me.createElementFromScratch();
      me._state.new = true;
    } else if (me.objId) { // use existing element
      me._ctx.objId = me.objId;
      me._state.existing = true;
    } else {
      throw "IllegalArgumentException: at least an 'objId' has to be defined";
    }

  },

  /**
   * Applies changes to the elements metadata.
   */
  editMetadata: function () {
    var me = this,
        data = [];

    if (!me._sord && me._ctx.objId && me.$metadata) {
      me._sord = sol.common.RepoUtils.getSord(me._ctx.objId, { sordZ: me._SordZ });
    }

    if (me._sord && me.$metadata) {

      if (me.$metadata.owner && (me._state.new === true)) {
        if (me.$metadata.owner.fromConnection) {
          data.push({ type: "SORD", key: "ownerId", value: me.user.id });
        }
      }

      if (me.$metadata.solType) {
        data.push({ type: "GRP", key: "SOL_TYPE", value: me.$metadata.solType });
      }

      if (me.$metadata.objKeys && (me.$metadata.objKeys.length > 0)) {
        me.$metadata.objKeys.forEach(function (objKey) {
          if (objKey.key && objKey.value) {
            data.push({ type: "GRP", key: objKey.key, value: objKey.value });
          }
        });
      }

      if (me.$metadata.mapItems && (me.$metadata.mapItems.length > 0)) {
        me.$metadata.mapItems.forEach(function (mapItem) {
          if (mapItem.key && mapItem.value) {
            data.push({ type: "MAP", key: mapItem.key, value: mapItem.value });
          }
        });
      }

      if (data.length > 0) {
        me._mapitems = sol.common.SordUtils.updateSord(me._sord, data);
        me._state.dirty = true;
      }
    }
  },

  /**
   * Saves the sord changes or the new sord (indicated by the `_dirty` flag) to the database.
   */
  saveChanges: function () {
    var me = this;

    if (me._sord && me._state.dirty) {
      me._ctx.objId = ixConnect.ix().checkinSord(me._sord, me._SordZ, LockC.NO);

      if (me._mapitems) {
        ixConnect.ix().checkinMap(MapDomainC.DOMAIN_SORD, me._sord.id, me._sord.id, me._mapitems, LockC.NO);
      }
    }

    me._sord = null;
    me._mapitems = null;
    me._state.dirty = false;
  },

  /**
   * Applies the permissions to the opbject.
   */
  applyPermissions: function () {
    var me = this,
        apply = false,
        params, modeParam, inheritParam;

    if (!me.$permissions) {
      return;
    }

    // '$permissions.copySource' is used by (and only by) 'createElementFromTemplate'

    if (me.$permissions.inherit && !me._state.chaos) {  // skip if element is in chaos cabinet
      if (me.$permissions.inherit.fromDirectParent && !me._state.permissionsAlreadyInheritedFromParent) {  // skip, if already processed by 'createElementFromTemplate'
        inheritParam = { fromDirectParent: true };
        apply = true;
      } else if (me.$permissions.inherit.solutionObjectTypes) {
        inheritParam = { solutionObjectTypes: me.$permissions.inherit.solutionObjectTypes };
        apply = true;
      }
    }

    if (me.$permissions.mode && (me.$permissions.mode == "ADD" || me.$permissions.mode == "SET" || me.$permissions.mode == "REMOVE")) {
      modeParam = me.$permissions.mode;
    }

    if (apply) {
      params = {
        mode: modeParam,
        inherit: inheritParam
      };

      me.checkAccessRights(me._ctx.objId);

      sol.common.AclUtils.changeRightsInBackground(me._ctx.objId, params);
    }
  },

  startWorkflow: function () {
    var me = this,
        wfName, flowId;

    if (!me.$wf || !me.$wf.template) {
      throw "IllegalArgumentException: a '$wf.template' has to be defined";
    }

    if (!me.$wf.template.name && !me.$wf.template.key) {
      throw "IllegalArgumentException: at least '$wf.template.name' or '$wf.template.key' has to be defined";
    }

    wfName = me.createWorkflowName();

    if (me.$wf.template.name) {
      flowId = me.$super("sol.common.ActionBase", "startWorkflow", [me._ctx.objId, me.$wf.template.name, wfName]);
    } else if (me.$wf.template.key) {
      flowId = me.startMaskStandardWorkflow(me._ctx.objId, { field: me.$wf.template.key, name: wfName });
    }

    me._ctx.flowId = flowId;
  },

  addEvents: function () {
    var me = this;

    if (me.$events && (me.$events.length > 0)) {
      me.$events.forEach(function (event) {
        var on, checkout;

        if (event.onWfStatus && me._ctx.flowId) {
          on = { type: "WF_STATUS", value: event.onWfStatus, flowId: me._ctx.flowId };
        }

        switch (event.id) {
          case sol.common.IxUtils.CONST.EVENT_TYPES.REFRESH:
            me.addRefreshEvent(me._ctx.objId, on);
            break;
          case sol.common.IxUtils.CONST.EVENT_TYPES.GOTO:
            checkout = (event.onWfStatus == "CHECKOUT");
            me.addGotoIdEvent(me._ctx.objId, checkout, on);
            break;
          case sol.common.IxUtils.CONST.EVENT_TYPES.DIALOG:
            me.addWfDialogEvent(me._ctx.flowId, {
              objId: me._ctx.objId,
              title: me._ctx.wfName || me._ctx.name,
              dialogId: me.getName()
            }, on);
            break;
          case sol.common.IxUtils.CONST.EVENT_TYPES.FEEDBACK:
            me.addFeedbackEvent(event.message, null, null, on);
            break;
          default:
            me.logger.warn(["event type {0} is not supported", event.id]);
        }
      });
    }
  },

  createElementFromTemplate: function () {
    var me = this,
        templateId, targetId, copyACL, inheritACL, objId;

    if (me.$new.template.objId) {
      templateId = me.$new.template.objId;
    } else if (me.$new.template.base && me.$new.template.name) {
      templateId = sol.common.RepoUtils.getObjIdFromRelativePath(me.$new.template.base, "/" + me.$new.template.name);
    } else {
      throw "IllegalArgumentException: at least a '$new.template.objId' has to be defined";
    }

    targetId = me.getTargetId();

    me._state.fromTemplate = true;
    me._state.chaos = (targetId === "0");

    // force 'copyAcl' if element will be created in chaos cabinet to avoid copy error

    copyACL = (me._state.chaos || (me.$permissions && (me.$permissions.copySource === true))) ? true : false;
    inheritACL = (me._state.chaos || (me.$permissions && me.$permissions.inherit && (me.$permissions.inherit.fromDirectParent === false))) ? false : true;

    if (inheritACL) {
      me._state.permissionsAlreadyInheritedFromParent = true;
    }

    me.checkAccessRights(templateId);
    if (!me._state.chaos) {
      me.checkAccessRights(targetId, { r: true, l: true });
    }

    objId = sol.common.IxUtils.execute("RF_sol_function_CopyFolderContents", {
      objId: targetId,
      source: templateId,
      copySourceAcl: copyACL,
      inheritDestinationAcl: inheritACL,
      name: me.createElementName(),
      useQuickCopy: true,
      acl: {
        mode: "ADD",
        entries: [
          { userName: "$CURRENTUSER", rights: { r: true, w: true, d: true, e: true, l: true, p: true } }
        ]
      }
    });

    return objId;
  },

  createElementFromScratch: function () {
    var me = this,
        targetId, mask, sord, type;

    targetId = me.getTargetId();
    mask = me.getMask();

    me._state.fromScratch = true;
    me._state.chaos = (targetId === "0");

    if (!me._state.chaos) {
      me.checkAccessRights(targetId, { r: true, l: true });
    }

    sord = ixConnect.ix().createSord(targetId, mask, EditInfoC.mbSord).sord;
    sord.name = me.createElementName() || sord.guid;

    type = me.getType();
    if (type) {
      sord.type = type;
    }

    sol.common.SordUtils.addRights(sord, { users: ["$CURRENTUSER"] });

    me._state.dirty = true;

    return sord;
  },

  checkAccessRights: function (objId, rights) {
    var me = this,
        hasAccess, rightsStr, msg;

    rights = rights || { r: true };
    hasAccess = sol.common.AclUtils.hasEffectiveRights(objId, { rights: rights });
    if (!hasAccess) {
      rightsStr = ((rights.r === true) ? "R" : "") + ((rights.w === true) ? "W" : "") + ((rights.d === true) ? "D" : "") + ((rights.e === true) ? "E" : "") + ((rights.l === true) ? "L" : "") + ((rights.p === true) ? "P" : "");
      msg = "IllegalAccessException: missing permissions (" + rightsStr + ") on '" + objId + "'";
      me.logger.warn(msg);
      throw msg;
    }
  },

  createElementName: function () {
    var me = this,
        sord;
    if (!me._ctx.name) {
      if (me._ctx.objId) {
        sord = sol.common.RepoUtils.getSord(me._ctx.objId, { sordZ: SordC.mbMin });
        me._ctx.name = sord.name;
      } else if (me.$new.name) {
        me._ctx.name = sol.create("sol.common.Template", { source: me.$new.name }).apply({ date: new Date(), actionId: me.actionId });
      }
    }
    return me._ctx.name;
  },

  createWorkflowName: function () {
    var me = this;
    if (!me._ctx.wfName) {
      if (me.$wf.name) {
        me._ctx.wfName = sol.create("sol.common.Template", { source: me.$wf.name }).apply({ date: new Date(), actionId: me.actionId });
      } else {
        me._ctx.wfName = me.createElementName();
      }
    }
    return me._ctx.wfName;
  },

  getTargetId: function () {
    var me = this,
        paramsObj,
        targetId = null;

    if (me.$new && me.$new.target) {
      switch (me.$new.target.mode) {
        case "SELECTED":
          targetId = me.objId || "0";
          break;
        case "FIND":
          paramsObj = JSON.parse(me.$new.target.params);
          targetId = sol.common.RepoUtils.getObjIdByIndex(paramsObj);
          break;
        default:
          targetId = "0";
          break;
      }
    } else if (me.$new) {
      targetId = "0";
    }
    return targetId;
  },

  getMask: function () {
    var me = this;
    return (me.$new.mask) ? me.$new.mask : null;
  },

  getType: function () {
    var me = this,
        type;
    try {
      if (me.$new.type) {
        type = sol.common.SordTypeUtils.getSordTypeId(me.$new.type);
      }
    } catch (ex) {
      me.logger.warn(["could not determine sord type id for name '{0}'", me.$new.type]);
    }
    return (type) ? type : null;
  }

});


/**
 * @member sol.common.ix.actions.Standard
 * @method RF_sol_common_action_Standard
 * @static
 * @inheritdoc sol.common.ix.ActionBase#RF_FunctionName
 */
function RF_sol_common_action_Standard(ec, args) {
  function getTplSord(objId) {
    try {
      if (objId) {
        return sol.common.SordUtils.getTemplateSord(ixConnect.ix().checkoutSord(objId, SordC.mbAllIndex, LockC.NO)).sord;
      }
    } catch (ex) {
      // ignore
      // this could be due to a 'dynamic' objId, which will be evaluated later
    }
    return { id: null };
  }
  logger.enter("RF_sol_common_action_Standard", args);
  var config, action, result,
      templatingData;

  config = sol.common.ix.RfUtils.parseAndCheckParams(ec, arguments.callee.name, args);

  //remove following line as soon as $templating|$sordInTemplating is available in config-app
  config.$templating = config.$templating || {};
  config.$templating.$options = { exposeSord: true, emptyNonRenderedValues: true };
  config._$disableParamsTemplating = config._$disableParamsTemplating === undefined || config._$disableParamsTemplating; // Actionbase will skip standard templating based on this property because we take care of templating here

  templatingData = { objId: config.objId, type: config.$templating.$type, tree: config.$templating.$tree, preconditions: config.$templating.$preconditions };
  if (config.$templating.$options.exposeSord) {
    templatingData.sord = getTplSord(config.objId);
  }
  config = sol.common.TemplateUtils.render(config, templatingData, { emptyNonRendered: config.$templating.$options.emptyNonRenderedValues });

  if (config._$disableParamsTemplating === true) {
    config.$templating = undefined;
  }

  config.ci = ec.ci;
  config.user = ec.user;

  action = sol.create("sol.common.ix.actions.Standard", config);
  result = action.execute();

  logger.exit("RF_sol_common_action_Standard", result);

  return result;
}