// Generated by CoffeeScript 1.7.1

/*

Engine
==================
> The heart of the WebAPI ECA System. The engine loads action invoker modules
> corresponding to active rules actions and invokes them if an appropriate event
> is retrieved.
 */

(function() {
  var db, dynmod, exports, isRunning, jsonQuery, listUserRules, pollQueue, processEvent, semaphore, updateActionModules, validConditions;

  db = require('./persistence');

  dynmod = require('./dynamic-modules');

  jsonQuery = require('js-select');


  /*
  This is ging to have a structure like:
  An object of users with their active rules and the required action modules
  
      "user-1":
        "rule-1":
          "rule": oRule-1
          "actions":
            "action-1": oAction-1
            "action-2": oAction-2
        "rule-2":
          "rule": oRule-2
          "actions":
            "action-1": oAction-1
      "user-2":
        "rule-3":
          "rule": oRule-3
          "actions":
            "action-3": oAction-3
   */

  listUserRules = {};

  isRunning = false;


  /*
  Module call
  -----------
  Initializes the Engine and starts polling the event queue for new events.
  
  @param {Object} args
   */

  exports = module.exports = (function(_this) {
    return function(args) {
      if (!isRunning) {
        _this.log = args.logger;
        db(args);
        dynmod(args);
        setTimeout(exports.startEngine, 10);
        return module.exports;
      }
    };
  })(this);


  /*
  This is a helper function for the unit tests so we can verify that action
  modules are loaded correctly
  
  @public getListUserRules ()
   */

  exports.getListUserRules = function() {
    return listUserRules;
  };

  exports.startEngine = function() {
    if (!isRunning) {
      isRunning = true;
      return pollQueue();
    }
  };


  /*
  An event associated to rules happened and is captured here. Such events 
  are basically CRUD on rules.
  
  @public internalEvent ( *evt* )
  @param {Object} evt
   */

  exports.internalEvent = (function(_this) {
    return function(evt) {
      var oRule, oUser;
      if (!listUserRules[evt.user] && evt.event !== 'del') {
        listUserRules[evt.user] = {};
      }
      oUser = listUserRules[evt.user];
      oRule = evt.rule;
      if (evt.event === 'new' || (evt.event === 'init' && !oUser[oRule.id])) {
        oUser[oRule.id] = {
          rule: oRule,
          actions: {}
        };
        updateActionModules(oRule.id);
      }
      if (evt.event === 'del' && oUser) {
        delete oUser[evt.ruleId];
      }
      if (JSON.stringify(oUser) === "{}") {
        return delete listUserRules[evt.user];
      }
    };
  })(this);


  /*
  As soon as changes were made to the rule set we need to ensure that the aprropriate action
  invoker modules are loaded, updated or deleted.
  
  @private updateActionModules ( *updatedRuleId* )
  @param {Object} updatedRuleId
   */

  updateActionModules = function(updatedRuleId) {
    var fAddRequired, fRemoveNotRequired, name, oUser, userName, _results;
    fRemoveNotRequired = function(oUser) {
      var action, fRequired, _results;
      fRequired = function(actionName) {
        var action, _i, _len, _ref;
        _ref = oUser[updatedRuleId].rule.actions;
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          action = _ref[_i];
          if ((action.split(' -> '))[0] === actionName) {
            return true;
          }
        }
        return false;
      };
      _results = [];
      for (action in oUser[updatedRuleId].rule.actions) {
        if (!fRequired(action)) {
          _results.push(delete oUser[updatedRuleId].actions[action]);
        } else {
          _results.push(void 0);
        }
      }
      return _results;
    };
    for (name in listUserRules) {
      oUser = listUserRules[name];
      fRemoveNotRequired(oUser);
    }
    fAddRequired = function(userName, oUser) {
      var fCheckRules, nmRl, oRl, _results;
      fCheckRules = function(oMyRule) {
        var action, fAddIfNewOrNotExisting, _i, _len, _ref, _results;
        fAddIfNewOrNotExisting = function(actionName) {
          var moduleName;
          moduleName = (actionName.split(' -> '))[0];
          if (!oMyRule.actions[moduleName] || oMyRule.rule.id === updatedRuleId) {
            return db.actionInvokers.getModule(moduleName, function(err, obj) {
              return dynmod.compileString(obj.data, userName, oMyRule.rule.id, moduleName, obj.lang, db.actionInvokers, function(result) {
                if (!result.answ === 200) {
                  this.log.error("EN | Compilation of code failed! " + userName + ", " + oMyRule.rule.id + ", " + moduleName);
                }
                return oMyRule.actions[moduleName] = result.module;
              });
            });
          }
        };
        _ref = oMyRule.rule.actions;
        _results = [];
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          action = _ref[_i];
          _results.push(fAddIfNewOrNotExisting(action));
        }
        return _results;
      };
      _results = [];
      for (nmRl in oUser) {
        oRl = oUser[nmRl];
        _results.push(fCheckRules(oRl));
      }
      return _results;
    };
    _results = [];
    for (userName in listUserRules) {
      oUser = listUserRules[userName];
      _results.push(fAddRequired(userName, oUser));
    }
    return _results;
  };

  semaphore = 0;

  pollQueue = function() {
    if (isRunning) {
      db.popEvent(function(err, obj) {
        if (!err && obj) {
          processEvent(obj);
        }
        return semaphore--;
      });
      return setTimeout(pollQueue, 20 * semaphore);
    }
  };


  /*
  Checks whether all conditions of the rule are met by the event.
  
  @private validConditions ( *evt, rule* )
  @param {Object} evt
  @param {Object} rule
   */

  validConditions = function(evt, rule) {
    var prop, _i, _len, _ref;
    if (rule.conditions.length === 0) {
      return true;
    }
    _ref = rule.conditions;
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      prop = _ref[_i];
      if (jsonQuery(evt, prop).nodes().length === 0) {
        return false;
      }
    }
    return true;
  };

  semaphore = 0;


  /*
  Handles retrieved events.
  
  @private processEvent ( *evt* )
  @param {Object} evt
   */

  processEvent = (function(_this) {
    return function(evt) {
      var action, arr, fSearchAndInvokeAction, oMyRule, oUser, ruleName, userName, _results;
      fSearchAndInvokeAction = function(node, arrPath, funcName, evt, depth) {
        var err;
        if (!node) {
          this.log.error("EN | Didn't find property in user rule list: " + arrPath.join(', ' + " at depth " + depth));
          return;
        }
        if (depth === arrPath.length) {
          try {
            semaphore++;
            node[funcName](evt.payload);
          } catch (_error) {
            err = _error;
            this.log.info("EN | ERROR IN ACTION INVOKER: " + err.message);
            node.logger(err.message);
          }
          if (semaphore-- % 100 === 0) {
            return this.log.warn("EN | The system is producing too many tokens! Currently: " + semaphore);
          }
        } else {
          return fSearchAndInvokeAction(node[arrPath[depth]], arrPath, funcName, evt, depth + 1);
        }
      };
      _this.log.info('EN | processing event: ' + evt.event + '(' + evt.eventid + ')');
      _results = [];
      for (userName in listUserRules) {
        oUser = listUserRules[userName];
        _results.push((function() {
          var _results1;
          _results1 = [];
          for (ruleName in oUser) {
            oMyRule = oUser[ruleName];
            if (evt.event === oMyRule.rule.event && validConditions(evt, oMyRule.rule)) {
              this.log.info('EN | EVENT FIRED: ' + evt.event + '(' + evt.eventid + ') for rule ' + ruleName);
              _results1.push((function() {
                var _i, _len, _ref, _results2;
                _ref = oMyRule.rule.actions;
                _results2 = [];
                for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                  action = _ref[_i];
                  arr = action.split(' -> ');
                  _results2.push(fSearchAndInvokeAction(listUserRules, [userName, ruleName, 'actions', arr[0]], arr[1], evt, 0));
                }
                return _results2;
              })());
            } else {
              _results1.push(void 0);
            }
          }
          return _results1;
        }).call(_this));
      }
      return _results;
    };
  })(this);

  exports.shutDown = function() {
    return isRunning = false;
  };

}).call(this);
