//@include lib_Class.js
//@include lib_sol.common.ObjectUtils.js

/**
 * Roles can be used in order to identify a proper user e.g. in a workflow.
 *
 * E.g. in an invoice approval process a user should be added to the approval process if the invoice amount exceeds a
 * defined value. In this scenario, the management group should approve invoices that are more expansive than 100000€.
 *
 * Contains the functions used to determine the correct users using a configuration object. If more then one user will be suitable, the configuration order will be maintained.
 *
 * # Configuration
 * ## Example
 *
 * The configuration is an Array which contains Objects containing the rolenames as `name` property and an `users` property which is an Array of user configurations.
 * This user configuration objects have at least a `user` property, which is the ELO username. Additionally each object can contain several conditions, which are defined thanks to an Array.
 *
 *     var myRoleConfig = [
 *       {
 *         "name": "ROLE_1",
 *         "users": [
 *           { user : "user1", conditions: [ { type: "GRP", key: "AMOUNT", rel: "GT", val: 3000, dataType: "number" } ] },
 *           { user : "user2" }
 *         ]
 *       },
 *       {
 *         name: "ROLE_2",
 *         users: [
 *           { user: "user2", conditions: [ { type: "GRP", key: "RECIPIENT_NO", rel: "EQUALS", val: "4713"} ] },
 *           { user: "group1" }
 *         ]
 *       },
 *       {
 *         name: "ROLE_3",
 *         users: [
 *           { user: { type: "GRP", key: "PO_PURCHASE_USER", mode: "SUPERVISOR", supervisorLevel: 2 }, conditions: [ { type: "GRP", key: "AMOUNT", rel: "GE", val: "4714"} ] },
 *         ]
 *       }
 *     ]
 *
 * ## Conditions
 * Conditions are optional and are used to limit the users depending on the values in an Sord object. Conditions are an Array of Objects.
 *
 *     {
 *       // type of field (GRP|SORD)
 *       type: "GRP",
 *       // name of the index group, or sord property
 *       key : "{ELO OBJEKY GROUPNAME}",
 *       // relation for the check
 *       rel : "{RELATION}",
 *       // value to check for
 *       val : "{VALUE}"
 *     }
 *
 * If conditions are configured, all of them must meet the requirements.
 *
 * Supported relations are:
 *
 *  - `GT`: value is greater than `x`
 *  - `GE`: value is greater or equal than `x`
 *  - `LT`: value is lower than `x`
 *  - `LE`: value is lower or equal than `x`
 *  - `EQUALS`: value is equal to `x`
 *
 * # Retrieving a user list
 *
 *     var sord = ixConnect.ix().checkoutSord(...);
 *     var users = sol.common.Roles.getUsers('MYROLE', sord, myRoleConfig);
 *
 * @author PZ, ELO Digital Office GmbH
 * @version 1.03.000
 *
 * @eloall
 * @requires handlebars
 * @requires sol.common.Template
 * @requires sol.common.SordUtils
 *
 */
sol.define("sol.common.Roles", {
  singleton: true,

  /**
   * @private
   * Contains the calculation rules.
   */
  fct: {
    EQUALS: function (param1, param2) {
      return param1 == param2;
    },
    GT: function (param1, param2) {
      return param1 > param2;
    },
    GE: function (param1, param2) {
      return param1 >= param2;
    },
    LT: function (param1, param2) {
      return param1 < param2;
    },
    LE: function (param1, param2) {
      return param1 <= param2;
    },
    NOT: function (param1, param2) {
      return param1 != param2;
    },
    STARTSWITH: function (param1, param2) {
      param1 = (param1 || "") + "";
      param2 = (param2 || "") + "";
      return (param1.indexOf(param2) == 0);
    }
  },

  /**
   * This function evaluates a configuration to find all suitable users for a role.
   * @param {String|Object} role Role name. If this is defined as an object, a `type` and a `key` has to be defined to load the role from the sords metadata (see {@link sol.common.SordUtils#getValue}).
   * @param {de.elo.ix.client.Sord} sord Sord to check conditions from config
   * @param {Object[]} config Configuration object
   * @return {String[]} Array of usernames (or an empty Array)
   *
   */
  getUsers: function (role, sord, config) {
    var me = this,
        users, result;
    me.logger.enter("getUsers", arguments);

    users = me.getUsers2(role, sord, config);

    result = users.map(function (entry) {
      return entry.name;
    });

    me.logger.exit("getUsers", result);
    return result;
  },

  /**
   * This function evaluates a configuration to find all suitable users for a role.
   * getUser2() considers the property 'mandatory'
   *
   * @param {String|Object} role Role name. If this is defined as an object, a `type` and a `key` has to be defined to load the role from the sords metadata (see {@link sol.common.SordUtils#getValue}).
   * @param {de.elo.ix.client.Sord} sord Sord to check conditions from config
   * @param {Object[]} originalConfig Configuration object
   * @return {Array} Array of user entries
   *
   */
  getUsers2: function (role, sord, originalConfig) {
    var me = this,
        applicableRules = [],
        userEntries = [],
        config, i, j, rules, rule, conditions, condition, conditionResult, allConditionsMeet, value,
        userName, sordMap, userLogString, applicableRule, userEntry;

    config = sol.common.ObjectUtils.clone(originalConfig);

    sordMap = sol.create("sol.common.SordMap", { objId: sord.id });
    sordMap.read();

    me.logger.enter("getUsers2", arguments);

    role = me.retrieveRole(role, config, sord);
    rules = me.getUsersByRole(role, config);

    if (rules && rules.length > 0) {
      for (i = 0; i < rules.length; i++) {
        rule = rules[i];
        conditions = rule.conditions;

        userLogString = sol.common.ObjectUtils.isObject(rule.user) ? JSON.stringify(rule.user) : rule.user;

        if (conditions && conditions.length > 0) {
          allConditionsMeet = true;
          for (j = 0; j < conditions.length; j++) {
            condition = conditions[j];
            value = me.getValue(sord, sordMap, condition);
            conditionResult = this.fct[condition.rel](value, condition.val);
            me.logger.debug(["getUser2: Condition: sord.id={0}, sord.name={1}, user={2}, condition.type={3}, condition.key={4}, value={5}, condition.rel={6}, condition.val={7}, condition.val.type={8}, result={9}", sord.id, sord.name, userLogString, condition.type, condition.key, value, condition.rel, condition.val, typeof condition.val, conditionResult]);
            if (!conditionResult) {
              allConditionsMeet = false;
              break;
            }
          }
          if (allConditionsMeet) {
            me.logger.debug(["getUsers2: All conditions meet: sord.id={0}, sord.name={1}, user={2}", sord.id, sord.name, userLogString]);
            applicableRules.push(rule);
          }
        } else {
          me.logger.debug(["getUsers2: Add constant user: sord.id={0}, sord.name={1}, user={2}", sord.id, sord.name, userLogString]);
          applicableRules.push(rule);
        }
      }
    }

    for (i = 0; i < applicableRules.length; i++) {
      applicableRule = applicableRules[i];
      userName = me.getUserName(sord, applicableRule);
      if (!userName) {
        me.logger.warn(["getUsers2: Can't determinate user name: {0}", JSON.stringify(applicableRule.user)]);
        continue;
      }

      userEntry = { name: userName, mandatory: applicableRule.mandatory };

      userEntries.push(userEntry);
    }

    me.logger.exit("getUsers2", JSON.stringify(userEntries));

    return userEntries;
  },

  /**
   * Determine if a configuration has a role configured.
   * @param {String|Object} role Role name. If this is defined as an object, a `type` and a `key` has to be defined to load the role from the sords metadata (see {@link sol.common.SordUtils#getValue}). It's also possible to define an object with a `template` property which contains a Handlebars template string.
   * @param {Object[]} rolesConfig Configuration object
   * @param {de.elo.ix.client.Sord} sord (optional) Sord to check conditions from config. If `role` is an object, this has to be defined.
   * @return {String} The name of the role
   */
  retrieveRole: function (role, rolesConfig, sord) {
    var me = this,
        configuredRole = null,
        tplSord;

    me.logger.enter("retrieveRole", role);

    if (role && rolesConfig && (rolesConfig.length > 0)) {
      if (role.type && role.key) {
        if (!sord) {
          throw "IllegalArgumentException: if role is defined by 'type' and 'key', a sord has to be passed.";
        }
        role = sol.common.SordUtils.getValue(sord, role);
      }

      if (role.template) {
        if (!sord) {
          throw "IllegalArgumentException: if role is defined by 'template', a sord has to be passed.";
        }
        tplSord = sol.common.SordUtils.getTemplateSord(sord);
        role = sol.create("sol.common.Template", { source: role.template }).apply(tplSord);
      }

      rolesConfig.some(function (roleConfig) {
        if (roleConfig.name == role) {
          configuredRole = roleConfig.name;
          return true;
        }
      });
    }

    if (!configuredRole) {
      me.logger.debug(["Role '{0}' is not defined in the configuration", role]);
    }

    me.logger.exit("retrieveRole", configuredRole);
    return configuredRole;
  },

  /**
   * @private
   * Returns the username
   * @param {de.elo.ix.client.Sord} sord Sord
   * @param {Object} rule Rule
   * @return {String}
   */
  getUserName: function (sord, rule) {
    var me = this,
        userName, mode;

    userName = rule.user;

    if (sol.common.ObjectUtils.isObject(rule.user)) {
      if (!rule.user.type) {
        throw "User definition type is empty";
      }
      if (!rule.user.key) {
        throw "User definition key is empty";
      }
      userName = sol.common.SordUtils.getValue(sord, rule.user);
    }

    if (!userName) {
      return;
    }

    userName += "";

    mode = rule.mode || rule.user.mode;

    if (mode) {
      switch (mode.toUpperCase()) {
        case "SUPERVISOR":
          userName = me.getSupervisor(rule, userName);
          break;

        default:
          break;
      }
    }

    return userName;
  },

  /**
   * Returns the supervisor of the specified level
   * @param {Object} userDef User definition
   * @param {String} userName User name
   * @return {String} Supervisor
   */
  getSupervisor: function (userDef, userName) {
    var me = this,
        supervisorLevel, supervisors, supervisor;

    userDef = userDef || {};
    userDef.user = userDef.user || {};

    supervisorLevel = userDef.user.supervisorLevel;
    supervisorLevel = (typeof supervisorLevel != "undefined") ? supervisorLevel : userDef.supervisorLevel;
    supervisorLevel = (typeof supervisorLevel != "undefined") ? supervisorLevel : 0;

    supervisors = sol.common.UserUtils.getSupervisorHierarchy(userName);
    if (!supervisors || (supervisors.length <= supervisorLevel)) {
      me.logger.debug(["Can't find supervisor: userConfig={0}, userName={1}, supervisors={2}, supervisorLevel={3}", JSON.stringify(userDef), userName, supervisors, supervisorLevel]);
      return;
    }
    supervisor = supervisors[supervisorLevel];

    me.logger.debug(["getSupervisor: userConfig={0}, userName={1}, supervisors={2}, supervisorLevel={3}, supervisor={4}", JSON.stringify(userDef), userName, supervisors, supervisorLevel, supervisor]);

    return supervisor;
  },

  /**
   * @private
   * Retrieves the users for a role from the configuration array.
   *
   * @param {String} role Lookup string in config object
   * @param {Object[]} config Configuration object
   * @return {Object[]} Array of user configurations
   *
   */
  getUsersByRole: function (role, config) {
    var me = this,
        result;
    me.logger.enter("getUsersByRole", arguments);
    if (config && config.length > 0) {
      config.some(function (roleObj) {
        if (roleObj.name == role) {
          result = roleObj.users;
          return true;
        }
      });
    }
    me.logger.exit("getUsersByRole", result);
    return result;
  },

  /**
   * @private
   * Gets a value from the Sord object.
   *
   * If the condition.val is a number this function tries to retrieve the value as a number.
   *
   * @param {de.elo.ix.client.Sord} sord Sord
   * @param {sol.common.SordMap} sordMap Sord map
   * @param {Object} condition Condition
   * @param {String} condition.type The type were the value should be looked up ("SORD"|"GRP")
   * @param {String} condition.key The lookup key (either an index field name or a sord property)
   * @param {String|Number} condition.val The value to which will be compared, here only used to determine the type (string or number)
   * @return {String|Number}
   */
  getValue: function (sord, sordMap, condition) {
    var value = null;

    switch (condition.type) {
      case "SORD":
        value = (sol.common.SordUtils.isSord(sord) && sord[condition.key]) ? sord[condition.key] : null;
        break;
      case "GRP":
        value = (((typeof condition.val) === "number") || (condition.dataType === "number")) ? sol.common.SordUtils.getObjKeyValueAsNumber(sord, condition.key) : sol.common.SordUtils.getObjKeyValue(sord, condition.key);
        break;
      case "MAP":
        if (condition.dataType === "number") {
          value = sordMap.getNumValue(condition.key);
        } else {
          value = (sordMap.getValue(condition.key) || "") + "";
        }
        break;
      default:
        break;
    }

    return value;
  }
});