// 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, updateActionModules, validConditions;

  db = require('./persistence');

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

  jsonQuery = require('js-select');

  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) {
        isRunning = true;
        _this.log = args.logger;
        db(args);
        dynmod(args);
        pollQueue();
        return module.exports;
      }
    };
  })(this);


  /*
  This is a helper function for the unit tests so we can verify that action
  modules are loaded correctly
   *TODO we should change this to functions returning true or false rather than returning
   *the whole list
  @public getListUserRules ()
   */

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


  /*
  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] = {
          rules: {},
          actions: {}
        };
      }
      oUser = listUserRules[evt.user];
      oRule = evt.rule;
      if (evt.event === 'new' || (evt.event === 'init' && !oUser.rules[oRule.id])) {
        oUser.rules[oRule.id] = oRule;
        updateActionModules(oRule, false);
      }
      if (evt.event === 'del' && oUser) {
        delete oUser.rules[oRule.id];
        return updateActionModules(oRule, true);
      }
    };
  })(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 ( *oNewRule* )
  @param {Object} oNewRule
   */

  updateActionModules = function(oNewRule, isDeleteOp) {
    var fAddRequired, fRemoveNotRequired, name, oUser, userName, _results;
    fRemoveNotRequired = function(oUser) {
      var action, fRequired, req, _results;
      fRequired = function(actionName) {
        var action, mod, nmRl, oRl, _i, _len, _ref, _ref1;
        _ref = oUser.rules;
        for (nmRl in _ref) {
          oRl = _ref[nmRl];
          _ref1 = oRl.actions;
          for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
            action = _ref1[_i];
            mod = (action.split(' -> '))[0];
            if (mod === actionName) {
              return true;
            }
          }
        }
        return false;
      };
      _results = [];
      for (action in oUser.actions) {
        req = fRequired(action);
        if (!req) {
          _results.push(delete oUser.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, _ref;
      fCheckRules = function(oRule) {
        var action, fAddIfNewOrNotExisting, _i, _len, _ref, _results;
        fAddIfNewOrNotExisting = function(actionName) {
          var moduleName;
          moduleName = (actionName.split(' -> '))[0];
          if (!isDeleteOp && (!oUser.actions[moduleName] || oRule.id === oNewRule.id)) {
            return db.actionInvokers.getModule(moduleName, function(err, obj) {
              var params, res;
              params = {};
              res = dynmod.compileString(obj.data, userName, moduleName, params, obj.lang);
              return oUser.actions[moduleName] = res.module;
            });
          }
        };
        _ref = oRule.actions;
        _results = [];
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          action = _ref[_i];
          _results.push(fAddIfNewOrNotExisting(action));
        }
        return _results;
      };
      _ref = oUser.rules;
      for (nmRl in _ref) {
        oRl = _ref[nmRl];
        fCheckRules(oRl);
      }
      if (JSON.stringify(oUser.rules) === "{}") {
        return delete listUserRules[userName];
      }
    };
    _results = [];
    for (userName in listUserRules) {
      oUser = listUserRules[userName];
      _results.push(fAddRequired(userName, oUser));
    }
    return _results;
  };

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


  /*
  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;
    _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;
  };


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

  processEvent = (function(_this) {
    return function(evt) {
      var action, arr, oRule, oUser, ruleName, userName, _results;
      _this.log.info('EN | processing event: ' + evt.event + '(' + evt.eventid + ')');
      _results = [];
      for (userName in listUserRules) {
        oUser = listUserRules[userName];
        _results.push((function() {
          var _ref, _results1;
          _ref = oUser.rules;
          _results1 = [];
          for (ruleName in _ref) {
            oRule = _ref[ruleName];
            if (evt.event === oRule.event && validConditions(evt, oRule)) {
              this.log.info('EN | EVENT FIRED: ' + evt.event + '(' + evt.eventid + ') for rule ' + ruleName);
              _results1.push((function() {
                var _i, _len, _ref1, _results2;
                _ref1 = oRule.actions;
                _results2 = [];
                for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
                  action = _ref1[_i];
                  arr = action.split(' -> ');
                  _results2.push(listUserRules[userName]['actions'][arr[0]][arr[1]](evt));
                }
                return _results2;
              })());
            } else {
              _results1.push(void 0);
            }
          }
          return _results1;
        }).call(_this));
      }
      return _results;
    };
  })(this);

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

}).call(this);
