// Generated by CoffeeScript 1.7.1

/*

Persistence
============
> Handles the connection to the database and provides functionalities for event pollers,
> action invokers, rules and the (hopefully encrypted) storing of user-specific parameters
> per module.
> General functionality as a wrapper for the module holds initialization,
> the retrieval of modules and shut down.
> 
> The general structure for linked data is that the key is stored in a set.
> By fetching all set entries we can then fetch all elements, which is
> automated in this function.
> For example, modules of the same group, e.g. action invokers are registered in an
> unordered set in the database, from where they can be retrieved again. For example
> a new action invoker has its ID (e.g 'probinder') first registered in the set
> 'action-invokers' and then stored in the db with the key 'action-invoker:' + ID
> (e.g. action-invoker:probinder). 
>
 */

(function() {
  var IndexedModules, exports, getSetRecords, redis, replyHandler,
    __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

  redis = require('redis');


  /*
  Module call
  -----------
  Initializes the DB connection with the given `db-port` property in the `args` object.
  
  @param {Object} args
   */

  exports = module.exports = (function(_this) {
    return function(args) {
      if (!_this.db) {
        if (!args['db-port']) {
          args['db-port'] = 6379;
        }
        _this.log = args.logger;
        exports.eventPollers = new IndexedModules('event-poller', _this.log);
        exports.actionInvokers = new IndexedModules('action-invoker', _this.log);
        return exports.initPort(args['db-port']);
      }
    };
  })(this);

  exports.getLogger = (function(_this) {
    return function() {
      return _this.log;
    };
  })(this);

  exports.initPort = (function(_this) {
    return function(port) {
      var _ref;
      _this.connRefused = false;
      if ((_ref = _this.db) != null) {
        _ref.quit();
      }
      _this.db = redis.createClient(port, 'localhost', {
        connect_timeout: 2000
      });
      _this.db.on('error', function(err) {
        if (err.message.indexOf('ECONNREFUSED') > -1) {
          _this.connRefused = true;
          return _this.log.error(err, 'DB | Wrong port?');
        }
      });
      exports.eventPollers.setDB(_this.db);
      return exports.actionInvokers.setDB(_this.db);
    };
  })(this);


  /*
  Checks whether the db is connected and passes either an error on failure after
  ten attempts within five seconds, or nothing on success to the callback(err).
  
  @public isConnected( *cb* )
  @param {function} cb
   */

  exports.isConnected = (function(_this) {
    return function(cb) {
      var fCheckConnection, numAttempts;
      if (!_this.db) {
        return cb(new Error('DB | DB initialization did not occur or failed miserably!'));
      } else {
        if (_this.db.connected) {
          return cb();
        } else {
          numAttempts = 0;
          fCheckConnection = function() {
            var _ref;
            if (_this.connRefused) {
              if ((_ref = _this.db) != null) {
                _ref.quit();
              }
              return cb(new Error('DB | Connection refused! Wrong port?'));
            } else {
              if (_this.db.connected) {
                _this.log.info('DB | Successfully connected to DB!');
                return cb();
              } else if (numAttempts++ < 10) {
                return setTimeout(fCheckConnection, 100);
              } else {
                return cb(new Error('DB | Connection to DB failed!'));
              }
            }
          };
          return setTimeout(fCheckConnection, 100);
        }
      }
    };
  })(this);


  /*
  Abstracts logging for simple action replies from the DB.
  
  @private replyHandler( *action* )
  @param {String} action
   */

  replyHandler = (function(_this) {
    return function(action) {
      return function(err, reply) {
        if (err) {
          return _this.log.warn(err, "during '" + action + "'");
        } else {
          return _this.log.info("DB | " + action + ": " + reply);
        }
      };
    };
  })(this);


  /*
  Push an event into the event queue.
  
  @public pushEvent( *oEvent* )
  @param {Object} oEvent
   */

  exports.pushEvent = (function(_this) {
    return function(oEvent) {
      if (oEvent) {
        _this.log.info("DB | Event pushed into the queue: '" + oEvent.eventid + "'");
        return _this.db.rpush('event_queue', JSON.stringify(oEvent));
      } else {
        return _this.log.warn('DB | Why would you give me an empty event...');
      }
    };
  })(this);


  /*
  Pop an event from the event queue and pass it to cb(err, obj).
  
  @public popEvent( *cb* )
  @param {function} cb
   */

  exports.popEvent = (function(_this) {
    return function(cb) {
      var makeObj;
      makeObj = function(pcb) {
        return function(err, obj) {
          return pcb(err, JSON.parse(obj));
        };
      };
      return _this.db.lpop('event_queue', makeObj(cb));
    };
  })(this);


  /*
  Purge the event queue.
  
  @public purgeEventQueue()
   */

  exports.purgeEventQueue = (function(_this) {
    return function() {
      return _this.db.del('event_queue', replyHandler('purging event queue'));
    };
  })(this);


  /*
  Fetches all linked data set keys from a linking set, fetches the single
  data objects via the provided function and returns the results to cb(err, obj).
  
  @private getSetRecords( *set, fSingle, cb* )
  @param {String} set the set name how it is stored in the DB
  @param {function} fSingle a function to retrieve a single data element
        per set entry
  @param {function} cb the callback(err, obj) function that receives all
        the retrieved data or an error
   */

  getSetRecords = (function(_this) {
    return function(set, fSingle, cb) {
      _this.log.info("DB | Fetching set records: '" + set + "'");
      return _this.db.smembers(set, function(err, arrReply) {
        var fCallback, objReplies, reply, semaphore, _i, _len, _results;
        if (err) {
          _this.log.warn(err, "DB | fetching '" + set + "'");
          return cb(err);
        } else if (arrReply.length === 0) {
          return cb();
        } else {
          semaphore = arrReply.length;
          objReplies = {};
          setTimeout(function() {
            if (semaphore > 0) {
              return cb(new Error("Timeout fetching '" + set + "'"));
            }
          }, 2000);
          fCallback = function(prop) {
            return function(err, data) {
              --semaphore;
              if (err) {
                _this.log.warn(err, "DB | fetching single element: '" + prop + "'");
              } else if (!data) {
                _this.log.warn(new Error("Empty key in DB: '" + prop + "'"));
              } else {
                objReplies[prop] = data;
              }
              if (semaphore === 0) {
                return cb(null, objReplies);
              }
            };
          };
          _results = [];
          for (_i = 0, _len = arrReply.length; _i < _len; _i++) {
            reply = arrReply[_i];
            _results.push(fSingle(reply, fCallback(reply)));
          }
          return _results;
        }
      });
    };
  })(this);

  IndexedModules = (function() {
    function IndexedModules(setname, log) {
      this.setname = setname;
      this.log = log;
      this.deleteUserParams = __bind(this.deleteUserParams, this);
      this.getUserParamsIds = __bind(this.getUserParamsIds, this);
      this.getUserParams = __bind(this.getUserParams, this);
      this.storeUserParams = __bind(this.storeUserParams, this);
      this.deleteModule = __bind(this.deleteModule, this);
      this.getModules = __bind(this.getModules, this);
      this.getModuleIds = __bind(this.getModuleIds, this);
      this.getAvailableModuleIds = __bind(this.getAvailableModuleIds, this);
      this.getModuleParams = __bind(this.getModuleParams, this);
      this.getModule = __bind(this.getModule, this);
      this.unpublish = __bind(this.unpublish, this);
      this.publish = __bind(this.publish, this);
      this.unlinkModule = __bind(this.unlinkModule, this);
      this.linkModule = __bind(this.linkModule, this);
      this.storeModule = __bind(this.storeModule, this);
      this.log.info("DB | (IdxedMods) Instantiated indexed modules for '" + this.setname + "'");
    }

    IndexedModules.prototype.setDB = function(db) {
      this.db = db;
      return this.log.info("DB | (IdxedMods) Registered new DB connection for '" + this.setname + "'");
    };


    /*
    Stores a module and links it to the user.
    
    @private storeModule( *userId, oModule* )
    @param {String} userId
    @param {object} oModule
     */

    IndexedModules.prototype.storeModule = function(userId, oModule) {
      this.log.info("DB | (IdxedMods) " + this.setname + ".storeModule( " + userId + ", oModule )");
      this.db.sadd("" + this.setname + "s", oModule.id, replyHandler("sadd '" + oModule.id + "' to '" + this.setname + "'"));
      this.db.hmset("" + this.setname + ":" + oModule.id, oModule, replyHandler("hmset properties in hash '" + this.setname + ":" + oModule.id + "'"));
      return this.linkModule(oModule.id, userId);
    };

    IndexedModules.prototype.linkModule = function(mId, userId) {
      this.log.info("DB | (IdxedMods) " + this.setname + ".linkModule( " + mId + ", " + userId + " )");
      this.db.sadd("" + this.setname + ":" + mId + ":users", userId, replyHandler("sadd " + userId + " to '" + this.setname + ":" + mId + ":users'"));
      return this.db.sadd("user:" + userId + ":" + this.setname + "s", mId, replyHandler("sadd " + mId + " to 'user:" + userId + ":" + this.setname + "s'"));
    };

    IndexedModules.prototype.unlinkModule = function(mId, userId) {
      this.log.info("DB | (IdxedMods) " + this.setname + ".unlinkModule( " + mId + ", " + userId + " )");
      this.db.srem("" + this.setname + ":" + mId + ":users", userId, replyHandler("srem " + userId + " from '" + this.setname + ":" + mId + ":users'"));
      return this.db.srem("user:" + userId + ":" + this.setname + "s", mId, replyHandler("srem " + mId + " from 'user:" + userId + ":" + this.setname + "s'"));
    };

    IndexedModules.prototype.publish = function(mId) {
      this.log.info("DB | (IdxedMods) " + this.setname + ".publish( " + mId + " )");
      return this.db.sadd("public-" + this.setname + "s", mId, replyHandler("sadd '" + mId + "' to 'public-" + this.setname + "s'"));
    };

    IndexedModules.prototype.unpublish = function(mId) {
      this.log.info("DB | (IdxedMods) " + this.setname + ".unpublish( " + mId + " )");
      return this.db.srem("public-" + this.setname + "s", mId, replyHandler("srem '" + mId + "' from 'public-" + this.setname + "s'"));
    };

    IndexedModules.prototype.getModule = function(mId, cb) {
      this.log.info("DB | (IdxedMods) " + this.setname + ".getModule( " + mId + " )");
      return this.db.hgetall("" + this.setname + ":" + mId, cb);
    };

    IndexedModules.prototype.getModuleParams = function(mId, cb) {
      this.log.info("DB | (IdxedMods) " + this.setname + ".getModuleParams( " + mId + " )");
      return this.db.hget("" + this.setname + ":" + mId, "params", cb);
    };

    IndexedModules.prototype.getAvailableModuleIds = function(userId, cb) {
      this.log.info("DB | (IdxedMods) " + this.setname + ".getPublicModuleIds( " + userId + " )");
      return this.db.sunion("public-" + this.setname + "s", "user:" + userId + ":" + this.setname + "s", cb);
    };

    IndexedModules.prototype.getModuleIds = function(cb) {
      this.log.info("DB | (IdxedMods) " + this.setname + ".getModuleIds()");
      return this.db.smembers("" + this.setname + "s", cb);
    };

    IndexedModules.prototype.getModules = function(cb) {
      this.log.info("DB | (IdxedMods) " + this.setname + ".getModules()");
      return getSetRecords("" + this.setname + "s", this.getModule, cb);
    };

    IndexedModules.prototype.deleteModule = function(mId) {
      this.log.info("DB | (IdxedMods) " + this.setname + ".deleteModule( " + mId + " )");
      this.db.srem("" + this.setname + "s", mId, replyHandler("srem '" + mId + "' from " + this.setname + "s"));
      this.db.del("" + this.setname + ":" + mId, replyHandler("del of '" + this.setname + ":" + mId + "'"));
      this.unpublish(mId);
      return this.db.smembers("" + this.setname + ":" + mId + ":users", (function(_this) {
        return function(err, obj) {
          var userId, _i, _j, _len, _len1, _results;
          for (_i = 0, _len = obj.length; _i < _len; _i++) {
            userId = obj[_i];
            _this.unlinkModule(mId, userId);
          }
          _results = [];
          for (_j = 0, _len1 = obj.length; _j < _len1; _j++) {
            userId = obj[_j];
            _results.push(_this.deleteUserParams(mId, userId));
          }
          return _results;
        };
      })(this));
    };


    /*
    Stores user params for a module. They are expected to be RSA encrypted with helps of
    the provided cryptico JS library and will only be decrypted right before the module is loaded!
    
    @private storeUserParams( *mId, userId, encData* )
    @param {String} mId
    @param {String} userId
    @param {object} encData
     */

    IndexedModules.prototype.storeUserParams = function(mId, userId, encData) {
      this.log.info("DB | (IdxedMods) " + this.setname + ".storeUserParams( " + mId + ", " + userId + ", encData )");
      this.db.sadd("" + this.setname + "-params", "" + mId + ":" + userId, replyHandler("sadd '" + mId + ":" + userId + "' to '" + this.setname + "-params'"));
      return this.db.set("" + this.setname + "-params:" + mId + ":" + userId, encData, replyHandler("set user params in '" + this.setname + "-params:" + mId + ":" + userId + "'"));
    };

    IndexedModules.prototype.getUserParams = function(mId, userId, cb) {
      this.log.info("DB | (IdxedMods) " + this.setname + ".getUserParams( " + mId + ", " + userId + " )");
      return this.db.get("" + this.setname + "-params:" + mId + ":" + userId, cb);
    };

    IndexedModules.prototype.getUserParamsIds = function(cb) {
      this.log.info("DB | (IdxedMods) " + this.setname + ".getUserParamsIds()");
      return this.db.smembers("" + this.setname + "-params", cb);
    };

    IndexedModules.prototype.deleteUserParams = function(mId, userId) {
      this.log.info("DB | (IdxedMods) " + this.setname + ".deleteUserParams(" + mId + ", " + userId + " )");
      this.db.srem("" + this.setname + "-params", "" + mId + ":" + userId, replyHandler("srem '" + mId + ":" + userId + "' from '" + this.setname + "-params'"));
      return this.db.del("" + this.setname + "-params:" + mId + ":" + userId, replyHandler("del '" + this.setname + "-params:" + mId + ":" + userId + "'"));
    };

    return IndexedModules;

  })();


  /*
   *# Rules
   */


  /*
  Appends a log entry.
  
  @public log( *userId, ruleId, message* )
  @param {String} userId
  @param {String} ruleId
  @param {String} message
   */

  exports.appendLog = (function(_this) {
    return function(userId, ruleId, moduleId, message) {
      return _this.db.append("" + userId + ":" + ruleId + ":log", "[" + ((new Date).toISOString()) + "] {" + moduleId + "} " + message + "\n");
    };
  })(this);


  /*
  Retrieves a log entry.
  
  @public getLog( *userId, ruleId* )
  @param {String} userId
  @param {String} ruleId
  @param {function} cb
   */

  exports.getLog = (function(_this) {
    return function(userId, ruleId, cb) {
      return _this.db.get("" + userId + ":" + ruleId + ":log", cb);
    };
  })(this);


  /*
  Resets a log entry.
  
  @public resetLog( *userId, ruleId* )
  @param {String} userId
  @param {String} ruleId
   */

  exports.resetLog = (function(_this) {
    return function(userId, ruleId) {
      return _this.db.del("" + userId + ":" + ruleId + ":log", replyHandler("del '" + userId + ":" + ruleId + ":log'"));
    };
  })(this);


  /*
  Query the DB for a rule and pass it to cb(err, obj).
  
  @public getRule( *ruleId, cb* )
  @param {String} ruleId
  @param {function} cb
   */

  exports.getRule = (function(_this) {
    return function(ruleId, cb) {
      _this.log.info("DB | get: 'rule:" + ruleId + "'");
      return _this.db.get("rule:" + ruleId, cb);
    };
  })(this);


  /*
  Fetch all rules and pass them to cb(err, obj).  
  
  @public getRules( *cb* )
  @param {function} cb
   */

  exports.getRules = (function(_this) {
    return function(cb) {
      _this.log.info("DB | Fetching all Rules: getSetRecords 'rules'");
      return getSetRecords('rules', exports.getRule, cb);
    };
  })(this);


  /*
  Fetch all rule IDs and hand it to cb(err, obj).
  
  @public getRuleIds( *cb* )
  @param {function} cb
   */

  exports.getRuleIds = (function(_this) {
    return function(cb) {
      _this.log.info("DB | Fetching all Rule IDs: 'rules'");
      return _this.db.smembers('rules', cb);
    };
  })(this);


  /*
  Store a string representation of a rule in the DB.
  
  @public storeRule( *ruleId, data* )
  @param {String} ruleId
  @param {String} data
   */

  exports.storeRule = (function(_this) {
    return function(ruleId, data) {
      _this.log.info("DB | storeRule: '" + ruleId + "'");
      _this.db.sadd('rules', "" + ruleId, replyHandler("sadd rules: '" + ruleId + "'"));
      return _this.db.set("rule:" + ruleId, data, replyHandler("set 'rule:" + ruleId + "': data"));
    };
  })(this);


  /*
  Delete a string representation of a rule.
  
  @public deleteRule( *ruleId, userId* )
  @param {String} ruleId
  @param {String} userId
   */

  exports.deleteRule = (function(_this) {
    return function(ruleId) {
      _this.log.info("DB | deleteRule: '" + ruleId + "'");
      _this.db.srem("rules", ruleId, replyHandler("srem 'rules': '" + ruleId + "'"));
      _this.db.del("rule:" + ruleId, replyHandler("del: 'rule:" + ruleId + "'"));
      _this.db.smembers("rule:" + ruleId + ":users", function(err, obj) {
        var delLinkedUserRule, id, _i, _len, _results;
        delLinkedUserRule = function(userId) {
          exports.resetLog(userId, ruleId);
          return _this.db.srem("user:" + userId + ":rules", ruleId, replyHandler("srem 'user:" + userId + ":rules': '" + ruleId + "'"));
        };
        _results = [];
        for (_i = 0, _len = obj.length; _i < _len; _i++) {
          id = obj[_i];
          _results.push(delLinkedUserRule(id));
        }
        return _results;
      });
      _this.db.del("rule:" + ruleId + ":users", replyHandler("del 'rule:" + ruleId + ":users'"));
      _this.db.smembers("rule:" + ruleId + ":active-users", function(err, obj) {
        var delActiveUserRule, id, _i, _len, _results;
        delActiveUserRule = function(userId) {
          return _this.db.srem("user:" + userId + ":active-rules", ruleId, replyHandler("srem 'user:" + userId + ":active-rules': '" + ruleId + "'"));
        };
        _results = [];
        for (_i = 0, _len = obj.length; _i < _len; _i++) {
          id = obj[_i];
          _results.push(delActiveUserRule(id));
        }
        return _results;
      });
      return _this.db.del("rule:" + ruleId + ":active-users", replyHandler("del 'rule:" + ruleId + ":active-users'"));
    };
  })(this);


  /*
  Associate a rule to a user.
  
  @public linkRule( *ruleId, userId* )
  @param {String} ruleId
  @param {String} userId
   */

  exports.linkRule = (function(_this) {
    return function(ruleId, userId) {
      _this.log.info("DB | linkRule: '" + ruleId + "' to user '" + userId + "'");
      _this.db.sadd("rule:" + ruleId + ":users", userId, replyHandler("sadd 'rule:" + ruleId + ":users': '" + userId + "'"));
      return _this.db.sadd("user:" + userId + ":rules", ruleId, replyHandler("sadd 'user:" + userId + ":rules': '" + ruleId + "'"));
    };
  })(this);


  /*
  Get rules linked to a user and hand it to cb(err, obj).
  
  @public getUserLinkRule( *userId, cb* )
  @param {String} userId
  @param {function} cb
   */

  exports.getUserLinkedRules = (function(_this) {
    return function(userId, cb) {
      _this.log.info("DB | getUserLinkedRules: 'user:" + userId + ":rules'");
      return _this.db.smembers("user:" + userId + ":rules", cb);
    };
  })(this);


  /*
  Get users linked to a rule and hand it to cb(err, obj).
  
  @public getRuleLinkedUsers( *ruleId, cb* )
  @param {String} ruleId
  @param {function} cb
   */

  exports.getRuleLinkedUsers = (function(_this) {
    return function(ruleId, cb) {
      _this.log.info("DB | getRuleLinkedUsers: 'rule:" + ruleId + ":users'");
      return _this.db.smembers("rule:" + ruleId + ":users", cb);
    };
  })(this);


  /*
  Delete an association of a rule to a user.
  
  @public unlinkRule( *ruleId, userId* )
  @param {String} ruleId
  @param {String} userId
   */

  exports.unlinkRule = (function(_this) {
    return function(ruleId, userId) {
      _this.log.info("DB | unlinkRule: '" + ruleId + ":" + userId + "'");
      _this.db.srem("rule:" + ruleId + ":users", userId, replyHandler("srem 'rule:" + ruleId + ":users': '" + userId + "'"));
      return _this.db.srem("user:" + userId + ":rules", ruleId, replyHandler("srem 'user:" + userId + ":rules': '" + ruleId + "'"));
    };
  })(this);


  /*
  Activate a rule.
  
  @public activateRule( *ruleId, userId* )
  @param {String} ruleId
  @param {String} userId
   */

  exports.activateRule = (function(_this) {
    return function(ruleId, userId) {
      _this.log.info("DB | activateRule: '" + ruleId + "' for '" + userId + "'");
      _this.db.sadd("rule:" + ruleId + ":active-users", userId, replyHandler("sadd 'rule:" + ruleId + ":active-users': '" + userId + "'"));
      return _this.db.sadd("user:" + userId + ":active-rules", ruleId, replyHandler("sadd 'user:" + userId + ":active-rules': '" + ruleId + "'"));
    };
  })(this);


  /*
  Get rules activated for a user and hand it to cb(err, obj).
  
  @public getUserLinkRule( *userId, cb* )
  @param {String} userId
  @param {function} cb
   */

  exports.getUserActivatedRules = (function(_this) {
    return function(userId, cb) {
      _this.log.info("DB | getUserActivatedRules: smembers 'user:" + userId + ":active-rules'");
      return _this.db.smembers("user:" + userId + ":active-rules", cb);
    };
  })(this);


  /*
  Get users activated for a rule and hand it to cb(err, obj).
  
  @public getRuleActivatedUsers ( *ruleId, cb* )
  @param {String} ruleId
  @param {function} cb
   */

  exports.getRuleActivatedUsers = (function(_this) {
    return function(ruleId, cb) {
      _this.log.info("DB | getRuleActivatedUsers: smembers 'rule:" + ruleId + ":active-users'");
      return _this.db.smembers("rule:" + ruleId + ":active-users", cb);
    };
  })(this);


  /*
  Deactivate a rule.
  
  @public deactivateRule( *ruleId, userId* )
  @param {String} ruleId
  @param {String} userId
   */

  exports.deactivateRule = (function(_this) {
    return function(ruleId, userId) {
      _this.log.info("DB | deactivateRule: '" + ruleId + "' for '" + userId + "'");
      _this.db.srem("rule:" + ruleId + ":active-users", userId, replyHandler("srem 'rule:" + ruleId + ":active-users': '" + userId + "'"));
      return _this.db.srem("user:" + userId + ":active-rules", ruleId, replyHandler("srem 'user:" + userId + ":active-rules' '" + ruleId + "'"));
    };
  })(this);


  /*
  Fetch all active ruleIds and pass them to cb(err, obj).
  
  @public getAllActivatedRuleIds( *cb* )
  @param {function} cb
   */

  exports.getAllActivatedRuleIdsPerUser = (function(_this) {
    return function(cb) {
      _this.log.info("DB | Fetching all active rules");
      return _this.db.smembers('users', function(err, obj) {
        var fFetchActiveUserRules, result, semaphore, user, _i, _len, _results;
        result = {};
        if (obj.length === 0) {
          return cb(null, result);
        } else {
          semaphore = obj.length;
          fFetchActiveUserRules = function(userId) {
            return _this.db.smembers("user:" + user + ":active-rules", function(err, obj) {
              if (obj.length > 0) {
                result[userId] = obj;
              }
              if (--semaphore === 0) {
                return cb(null, result);
              }
            });
          };
          _results = [];
          for (_i = 0, _len = obj.length; _i < _len; _i++) {
            user = obj[_i];
            _results.push(fFetchActiveUserRules(user));
          }
          return _results;
        }
      });
    };
  })(this);


  /*
   *# Users
   */


  /*
  Store a user object (needs to be a flat structure).
  The password should be hashed before it is passed to this function.
  
  @public storeUser( *objUser* )
  @param {Object} objUser
   */

  exports.storeUser = (function(_this) {
    return function(objUser) {
      _this.log.info("DB | storeUser: '" + objUser.username + "'");
      if (objUser && objUser.username && objUser.password) {
        _this.db.sadd('users', objUser.username, replyHandler("storing user key '" + objUser.username + "'"));
        objUser.password = objUser.password;
        return _this.db.hmset("user:" + objUser.username, objUser, replyHandler("storing user properties '" + objUser.username + "'"));
      } else {
        return _this.log.warn(new Error('DB | username or password was missing'));
      }
    };
  })(this);


  /*
  Fetch all user IDs and pass them to cb(err, obj).
  
  @public getUserIds( *cb* )
  @param {function} cb
   */

  exports.getUserIds = (function(_this) {
    return function(cb) {
      _this.log.info("DB | getUserIds");
      return _this.db.smembers("users", cb);
    };
  })(this);


  /*
  Fetch a user by id and pass it to cb(err, obj).
  
  @public getUser( *userId, cb* )
  @param {String} userId
  @param {function} cb
   */

  exports.getUser = (function(_this) {
    return function(userId, cb) {
      _this.log.info("DB | getUser: '" + userId + "'");
      return _this.db.hgetall("user:" + userId, cb);
    };
  })(this);


  /*
  Deletes a user and all his associated linked and active rules.
  
  @public deleteUser( *userId* )
  @param {String} userId
   */

  exports.deleteUser = (function(_this) {
    return function(userId) {
      _this.log.info("DB | deleteUser: '" + userId + "'");
      _this.db.srem("users", userId, replyHandler("Deleting user key '" + userId + "'"));
      _this.db.del("user:" + userId, replyHandler("Deleting user '" + userId + "'"));
      _this.db.smembers("user:" + userId + ":rules", function(err, obj) {
        var delLinkedRuleUser, id, _i, _len, _results;
        delLinkedRuleUser = function(ruleId) {
          return _this.db.srem("rule:" + ruleId + ":users", userId, replyHandler("Deleting user key '" + userId + "' in linked rule '" + ruleId + "'"));
        };
        _results = [];
        for (_i = 0, _len = obj.length; _i < _len; _i++) {
          id = obj[_i];
          _results.push(delLinkedRuleUser(id));
        }
        return _results;
      });
      _this.db.del("user:" + userId + ":rules", replyHandler("Deleting user '" + userId + "' rules"));
      _this.db.smembers("user:" + userId + ":active-rules", function(err, obj) {
        var delActivatedRuleUser, id, _i, _len, _results;
        delActivatedRuleUser = function(ruleId) {
          return _this.db.srem("rule:" + ruleId + ":active-users", userId, replyHandler("Deleting user key '" + userId + "' in active rule '" + ruleId + "'"));
        };
        _results = [];
        for (_i = 0, _len = obj.length; _i < _len; _i++) {
          id = obj[_i];
          _results.push(delActivatedRuleUser(id));
        }
        return _results;
      });
      _this.db.del("user:" + userId + ":active-rules", replyHandler("Deleting user '" + userId + "' rules"));
      _this.db.smembers("user:" + userId + ":roles", function(err, obj) {
        var delRoleUser, id, _i, _len, _results;
        delRoleUser = function(roleId) {
          return _this.db.srem("role:" + roleId + ":users", userId, replyHandler("Deleting user key '" + userId + "' in role '" + roleId + "'"));
        };
        _results = [];
        for (_i = 0, _len = obj.length; _i < _len; _i++) {
          id = obj[_i];
          _results.push(delRoleUser(id));
        }
        return _results;
      });
      return _this.db.del("user:" + userId + ":roles", replyHandler("Deleting user '" + userId + "' roles"));
    };
  })(this);


  /*
  Checks the credentials and on success returns the user object to the
  callback(err, obj) function. The password has to be hashed (SHA-3-512)
  beforehand by the instance closest to the user that enters the password,
  because we only store hashes of passwords for security6 reasons.
  
  @public loginUser( *userId, password, cb* )
  @param {String} userId
  @param {String} password
  @param {function} cb
   */

  exports.loginUser = (function(_this) {
    return function(userId, password, cb) {
      var fCheck;
      _this.log.info("DB | User '" + userId + "' tries to log in");
      fCheck = function(pw) {
        return function(err, obj) {
          if (err) {
            return cb(err, null);
          } else if (obj && obj.password) {
            if (pw === obj.password) {
              _this.log.info("DB | User '" + obj.username + "' logged in!");
              return cb(null, obj);
            } else {
              return cb(new Error('Wrong credentials!'), null);
            }
          } else {
            return cb(new Error('User not found!'), null);
          }
        };
      };
      return _this.db.hgetall("user:" + userId, fCheck(password));
    };
  })(this);


  /*
   *# User Roles
   */


  /*
  Associate a role with a user.
  
  @public storeUserRole( *userId, role* )
  @param {String} userId
  @param {String} role
   */

  exports.storeUserRole = (function(_this) {
    return function(userId, role) {
      _this.log.info("DB | storeUserRole: '" + userId + ":" + role + "'");
      _this.db.sadd('roles', role, replyHandler("adding role '" + role + "' to role index set"));
      _this.db.sadd("user:" + userId + ":roles", role, replyHandler("adding role '" + role + "' to user '" + userId + "'"));
      return _this.db.sadd("role:" + role + ":users", userId, replyHandler("adding user '" + userId + "' to role '" + role + "'"));
    };
  })(this);


  /*
  Fetch all roles of a user and pass them to cb(err, obj).
  
  @public getUserRoles( *userId* )
  @param {String} userId
  @param {function} cb
   */

  exports.getUserRoles = (function(_this) {
    return function(userId, cb) {
      _this.log.info("DB | getUserRoles: '" + userId + "'");
      return _this.db.smembers("user:" + userId + ":roles", cb);
    };
  })(this);


  /*
  Fetch all users of a role and pass them to cb(err, obj).
  
  @public getUserRoles( *role* )
  @param {String} role
  @param {function} cb
   */

  exports.getRoleUsers = (function(_this) {
    return function(role, cb) {
      _this.log.info("DB | getRoleUsers: '" + role + "'");
      return _this.db.smembers("role:" + role + ":users", cb);
    };
  })(this);


  /*
  Remove a role from a user.
  
  @public removeRoleFromUser( *role, userId* )
  @param {String} role
  @param {String} userId
   */

  exports.removeUserRole = (function(_this) {
    return function(userId, role) {
      _this.log.info("DB | removeRoleFromUser: role '" + role + "', user '" + userId + "'");
      _this.db.srem("user:" + userId + ":roles", role, replyHandler("Removing role '" + role + "' from user '" + userId + "'"));
      return _this.db.srem("role:" + role + ":users", userId, replyHandler("Removing user '" + userId + "' from role '" + role + "'"));
    };
  })(this);


  /*
  Shuts down the db link.
  
  @public shutDown()
   */

  exports.shutDown = (function(_this) {
    return function() {
      var _ref;
      return (_ref = _this.db) != null ? _ref.quit() : void 0;
    };
  })(this);

}).call(this);
