/**
 * This executor can be used to start simple tasks.
 *
 * That an action can be executed it has to define a type. Supported types are:
 *
 * - workflows: `type="WORKFLOW"` (starts a workflow)
 * - technical workflows: `type="TECHNICAL_WORKFLOW"` (starts a workflow and deletes it after execution if the workflow is already finished, i.e. does not contain any user nodes)
 * - reminder: `type="REMINDER"` (creates a reminder)
 * - function modules: `type="FUNCTION"` (executes a function with the objId of the current object as only parameter)
 *
 * # Enhancement
 * An analyzer can enhance a context object with additional information.
 * Some properties in the action definition can use those information to dynamically change the configuration property.
 *
 * The following action properties can use those enhancement:
 *
 * - WORKFLOW / TECHNICAL_WORKFLOW : user
 * - WORKFLOW / TECHNICAL_WORKFLOW : templateId
 * - WORKFLOW / TECHNICAL_WORKFLOW : nameTemplate
 * - REMINDER : user
 * - REMINDER : nameTemplate
 *
 * # Configuration
 * Depending on the type, the action needs additional information:
 *
 *     {
 *       type: "WORKFLOW",
 *       user: "Administrator", // optional, if none is defined, the during initialization set user will be user
 *       templateId: "NameOrIdOfTheWorkflowTemplate",
 *       nameTemplate: "{{translate 'wfPrefix'}}: {{sord.name}}" // optional, default is the name of the sord
 *     }
 *
 *     {
 *       type: "TECHNICAL_WORKFLOW",
 *       user: "Administrator", // optional, if none is defined, the during initialization set user will be user
 *       templateId: "{{ctx.TemplateNameReadFromFieldByValueAnalyzer}}", // enhancement example
 *       nameTemplate: "{{translate 'wfPrefix'}}: {{sord.name}}" // optional, default is the name of the sord
 *     }
 *
 *     {
 *       type: "REMINDER",
 *       user: "Administrator", // optional, if none is defined, the during initialization set user will be user
 *       nameTemplate: "Erinnerung: {{sord.objKeys.CONTRACT_NAME}}" // optional, default is the name of the sord
 *     }
 *
 *     {
 *       type: "FUNCTION",                        // only one of `module`, `direct` or `regfct` will be used, priority order is `regfct` -> `direct` -> `module`
 *       module: "my.namespace.MyFunctionModule"  // tries to create a module with this name and calls its `process` function (module and its dependencies have to be included)
 *       direct: "my.direct.Rule"                 // calls a direct function with this name
 *       regfct: "RF_my_Function"                 // calls a registered function with this name
 *     }
 *
 * # Initialization example:
 *
 *     var executor = sol.create("sol.common_monitoring.as.executors.SimpleExecutor", {
 *       user: "Administrator" // The default user if an action specifies none
 *     });
 *
 * @author PZ, ELO Digital Office GmbH
 * @version 1.01.000
 *
 * @eloas
 *
 * @requires sol.common.IxUtils
 * @requires sol.common.ObjectUtils
 * @requires sol.common.SordUtils
 * @requires sol.common.DateUtils
 * @requires sol.common.WfUtils
 * @requires sol.common.ObjectFormatter
 * @requires sol.common.TranslateTerms
 * @requires sol.common.Template
 */
sol.define("sol.common_monitoring.as.executors.SimpleExecutor", {

  /**
   * @cfg {String} user (optional) If set, this user will be used for all actions (but will be overridden, if an action itself defines a user)
   */

  /**
   * @private
   * @property {String} _todayIso The current date cached as ISO string
   */

  /**
   * @private
   * @property {Object} _userConnectionCache Caches user specific connections
   */
  _userConnectionCache: {},

  initialize: function (config) {
    var me = this;
    me.$super("sol.Base", "initialize", [config]);
    me._todayIso = sol.common.DateUtils.dateToIso(new Date());
    me.getConnection(me.user); // initializes a user connection in the cache for later re-use (if there is a user configured)
  },

  /**
   * Evaluates a result set and executes actions if the results contain any.
   * @param {de.elo.ix.client.Sord} sord
   * @param {Object[]} results
   * @param {Object} ctx
   */
  execute: function (sord, results, ctx) {
    var me = this;

    me.logger.enter("execute", { objId: sord.id, name: String(sord.name) });

    if (results && (results.length > 0)) {
      results.forEach(function (r) {
        if (r.action) {
          me.executeAction(sord, r.action, ctx);
        }
      });
    }

    me.logger.exit("execute");
  },

  /**
   * Performes cleanup. Closes all opened user connections.
   */
  dispose: function () {
    var me = this;

    Object.keys(me._userConnectionCache || {})
      .map(me.getValueAtKey.bind(me, me._userConnectionCache))
      .map(me.disposeUserConnection);
  },

  /**
   * @private
   * get value at key
   * @param {Object} source
   * @param {String} key
   * @returns {Object} the object at provided key
   */
  getValueAtKey: function (source, key) {
    return source[key];
  },


  /**
   * @private
   * Closes an user cconnection
   * @param {de.elo.ix.client.IXConnection} userConnection
   */
  disposeUserConnection: function (userConnection) {
    userConnection.close();
  },

  /**
   * @private
   * Executes an individual action.
   * @param {de.elo.ix.client.Sord} sord
   * @param {Object} action
   * @param {Object} ctx
   */
  executeAction: function (sord, action, ctx) {
    var me = this,
        fct = me.ACTIONS[action.type];

    me.logger.debug("execute action", action);

    if (fct && sol.common.ObjectUtils.isFunction(fct)) {
      fct.call(me, sord, action, ctx);
    } else {
      throw "ActionExecutionFailed: unsupported action type: " + action.type;
    }
  },

  /**
   * @private
   * Retrieves a connection for a specific user, using an internal cache.
   * If no user is defined, it returns the standard connection.
   * @param {String} user
   * @return {de.elo.ix.client.IXConnection}
   */
  getConnection: function (user) {
    var me = this;

    if (!user) {
      return ixConnect;
    }
    if (!me._userConnectionCache[user]) {
      me._userConnectionCache[user] = ixConnect.createConnectionForUser(user);
    }
    return me._userConnectionCache[user];
  },

  /**
   * @private
   * Builds the name from a template using the sord data. Fallback is `sord.name`.
   * @param {de.elo.ix.client.Sord} sord
   * @param {Object} ctx
   * @param {String} nameTemplate (optional)
   * @return {String}
   */
  buildName: function (sord, ctx, nameTemplate) {
    var me = this,
        name;
    try {
      if (nameTemplate) {
        sord = sol.common.SordUtils.getTemplateSord(sord).sord;
        name = sol.create("sol.common.Template", { source: nameTemplate }).apply({ sord: sord, ctx: ctx });
      } else {
        name = sord.name;
      }
    } catch (ex) {
      name = sord.name;
      me.logger.warn("error generating name, use 'sord.name'", ex);
    }
    return name;
  },

  /**
   * @private
   * Retrieves the value from a action using handlebars applying the sord and the context.
   * @param {String} value
   * @param {de.elo.ix.client.Sord} sord
   * @param {Object} ctx
   * @return {String}
   */
  eveluateActionProperty: function (value, sord, ctx) {
    var me = this,
        result = value;
    if (value.indexOf("{{") > -1) {
      try {
        sord = sol.common.SordUtils.getTemplateSord(sord).sord;
        result = sol.create("sol.common.Template", { source: value }).apply({ sord: sord, ctx: ctx });
      } catch (ignore) {
        me.logger.debug("error evaluating handlebars action property", ignore);
      }
    }
    return result;
  },

  /**
   * @private
   * @property
   * Lookup object for the different functions supported by this executor.
   * All functions will be called with a `de.elo.ix.client.Sord` as first, an object (representing the action which should be executed) as second parameter and an context object possibly containing additional informations.
   */
  ACTIONS: {
    WORKFLOW: function (sord, action, ctx) {
      var me = this,
          flowTemplate, flowName, flowUser, conn, flowId;

      flowName = me.buildName(sord, ctx, action.nameTemplate);
      flowTemplate = me.eveluateActionProperty(action.templateId, sord, ctx);
      flowUser = (action.user) ? me.eveluateActionProperty(action.user, sord, ctx) : me.user;
      conn = me.getConnection(flowUser);
      flowId = conn.ix().startWorkFlow(flowTemplate, flowName, sord.id);
      me.logger.info(["workflow started: flowId={0}, flowName={1}, flowOwner={2}", flowId, flowName, flowUser]);
    },
    TECHNICAL_WORKFLOW: function (sord, action, ctx) {
      var me = this,
          flowTemplate, flowName, flowUser, conn, flowId, activeWorkflows, flowFinished;

      flowName = me.buildName(sord, ctx, action.nameTemplate);
      flowTemplate = me.eveluateActionProperty(action.templateId, sord, ctx);
      flowUser = (action.user) ? me.eveluateActionProperty(action.user, sord, ctx) : me.user;
      conn = me.getConnection(flowUser);
      flowId = conn.ix().startWorkFlow(flowTemplate, flowName, sord.id);
      me.logger.info(["workflow started: flowId={0}, flowName={1}, flowOwner={2}", flowId, flowName, flowUser]);

      activeWorkflows = sol.common.WfUtils.getActiveWorkflows(sord.id, { template: flowTemplate });
      flowFinished = !activeWorkflows.some(function (wf) {
        return wf.id === flowId;
      });
      if (flowFinished) {
        me.logger.info(["delete finished workflow: flowId={0}, flowName={1}, flowOwner={2}", flowId, flowName, flowUser]);
        me.getConnection(flowUser).ix().deleteWorkFlow(flowId, WFTypeC.FINISHED, LockC.NO);
      }

    },
    REMINDER: function (sord, action, ctx) {
      var me = this,
          user, reminder;

      user = (action.user) ? me.eveluateActionProperty(action.user, sord, ctx) : me.user;

      if (user) {
        reminder = ixConnect.ix().createReminder(sord.id);
        reminder.name = me.buildName(sord, ctx, action.nameTemplate);
        reminder.promptDateIso = me._todayIso;
        //reminder.prio = UserTaskPriorityC.HIGHEST;
        //reminder.desc = "...";
        ixConnect.ix().checkinReminder(reminder, [user], false, LockC.NO);
        me.logger.info(["reminder created: objId={0}, user={1}", sord.id, user]);
      }
    },
    FUNCTION: function (sord, action, ctx) {
      // TODO: can this result in a new "nextRun"?  --> see e.g. File
      var me = this,
          paramObj = { objId: sord.id };
      if (action.regfct) {
        sol.common.IxUtils.execute(action.regfct, paramObj);
        me.logger.info(["refistered function executed: {0}", action.regfct]);
        return;
      }
      if (action.direct) {
        if (ruleExecutor.hasDirectRule(action.direct)) {
          ruleExecutor.runDirectRule(action.direct, sord.id, JSON.stringify(paramObj), "");
        } else {
          me.logger.debug(["Direct rule '{0}' not found.", action.direct]);
        }
        return;
      }
      if (action.module) {
        sol.create(action.module, paramObj).process();
        me.logger.info(["function module executed: {0}", action.module]);
      }
    }
  }

});