// Generated by CoffeeScript 1.6.3
/*

Persistence
============
> Handles the connection to the database and provides functionalities for
> event pollers, action invokers, rules and the encrypted storing of authentication tokens.
> General functionality as a wrapper for the module holds initialization,
> encryption/decryption, 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, crypto, decrypt, encrypt, exports, getSetRecords, hash, redis, replyHandler,
    _this = this,
    __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

  crypto = require('crypto-js');

  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(args) {
    var _ref;
    _this.log = args.logger;
    if ((_ref = _this.db) != null) {
      _ref.quit();
    }
    _this.crypto_key = "}f6y1y}B{.an$}2c$Yl.$mSnF\\HX149u*y8C:@kmN/520Gt\\v'+KFBnQ!\\r<>5X/xRI`sT<Iw;:DPV;4gy:qf]Zq{\"6sgK{,}^\"!]O;qBM3G?]h_`Psw=b6bVXKXry7*";
    _this.db = redis.createClient(args['db-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?');
      }
    });
    _this.ep = new IndexedModules('event-poller', _this.db, _this.log);
    return _this.ai = new IndexedModules('action-invoker', _this.db, _this.log);
  };

  /*
  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(cb) {
    var fCheckConnection, numAttempts;
    if (_this.db.connected) {
      return cb();
    } else {
      numAttempts = 0;
      fCheckConnection = function() {
        if (_this.connRefused) {
          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);
    }
  };

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


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

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


  exports.pushEvent = 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...');
    }
  };

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


  exports.popEvent = 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));
  };

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


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

  /*
  Hashes a string based on SHA-3-512.
  
  @private hash( *plainText* )
  @param {String} plainText
  */


  hash = function(plainText) {
    var err;
    if (plainText == null) {
      return null;
    }
    try {
      return (crypto.SHA3(plainText, {
        outputLength: 512
      })).toString();
    } catch (_error) {
      err = _error;
      _this.log.warn(err, 'DB | during hashing');
      return null;
    }
  };

  /*
  Encrypts a string using the crypto key from the config file, based on aes-256-cbc.
  
  @private encrypt( *plainText* )
  @param {String} plainText
  */


  encrypt = function(plainText) {
    var err;
    if (plainText == null) {
      return null;
    }
    try {
      return crypto.AES.encrypt(plainText, _this.crypto_key);
    } catch (_error) {
      err = _error;
      _this.log.warn(err, 'DB | during encryption');
      return null;
    }
  };

  /*
  Decrypts an encrypted string and hands it back on success or null.
  
  @private decrypt( *crypticText* )
  @param {String} crypticText
  */


  decrypt = function(crypticText) {
    var dec, err;
    if (crypticText == null) {
      return null;
    }
    try {
      dec = crypto.AES.decrypt(crypticText, _this.crypto_key);
      return dec.toString(crypto.enc.Utf8);
    } catch (_error) {
      err = _error;
      _this.log.warn(err, 'DB | during decryption');
      return null;
    }
  };

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

  IndexedModules = (function() {
    function IndexedModules(setname, db, log) {
      this.setname = setname;
      this.db = db;
      this.log = log;
      this.deleteParameters = __bind(this.deleteParameters, this);
      this.getParametersIds = __bind(this.getParametersIds, this);
      this.getParameters = __bind(this.getParameters, this);
      this.storeParameters = __bind(this.storeParameters, this);
      this.deleteModule = __bind(this.deleteModule, this);
      this.getModules = __bind(this.getModules, this);
      this.getModuleIds = __bind(this.getModuleIds, this);
      this.getModule = __bind(this.getModule, this);
      this.storeModule = __bind(this.storeModule, this);
      this.log.info("DB | Instantiated indexed modules for '" + this.setname + "'");
    }

    IndexedModules.prototype.storeModule = function(mId, data) {
      this.log.info("DB | storeModule(" + this.setname + "): " + mId);
      this.db.sadd("" + this.setname + "s", mId, replyHandler("Storing '" + this.setname + "' key '" + mId + "'"));
      return this.db.set("" + this.setname + ":" + mId, data, replyHandler("Storing '" + this.setname + ":" + mId + "'"));
    };

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

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

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

    IndexedModules.prototype.deleteModule = function(mId) {
      this.log.info("DB | deleteModule(" + this.setname + "): " + mId);
      this.db.srem("" + this.setname + "s", mId, replyHandler("Deleting '" + this.setname + "' key '" + mId + "'"));
      return this.db.del("" + this.setname + ":" + mId, replyHandler("Deleting '" + this.setname + ":" + mId + "'"));
    };

    IndexedModules.prototype.storeParameters = function(mId, userId, data) {
      this.log.info("DB | storeParameters(" + this.setname + "): '" + mId + ":" + userId + "'");
      this.db.sadd("" + this.setname + "-params", "" + mId + ":" + userId, replyHandler("Storing '" + this.setname + "' module parameters key '" + mId + "'"));
      return this.db.set("" + this.setname + "-params:" + mId + ":" + userId, encrypt(data), replyHandler("Storing '" + this.setname + "' module parameters '" + mId + ":" + userId + "'"));
    };

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

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

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

    return IndexedModules;

  })();

  /*
  ## Action Invokers
  */


  /*
  Store a string representation of an action invoker in the DB.
  
  @public storeActionInvoker ( *aiId, data* )
  @param {String} aiId
  @param {String} data
  */


  exports.storeActionInvoker = function(aiId, data) {
    return _this.ai.storeModule(aiId, data);
  };

  /*
  Query the DB for an action invoker and pass it to cb(err, obj).
  
  @public getActionInvoker( *aiId, cb* )
  @param {String} aiId
  @param {function} cb
  */


  exports.getActionInvoker = function(aiId, cb) {
    return _this.ai.getModule(aiId, cb);
  };

  /*
  Fetch all action invoker IDs and hand them to cb(err, obj).
  
  @public getActionInvokerIds( *cb* )
  @param {function} cb
  */


  exports.getActionInvokerIds = function(cb) {
    return _this.ai.getModuleIds(cb);
  };

  /*
  Fetch all action invokers and hand them to cb(err, obj).
  
  @public getActionInvokers( *cb* )
  @param {function} cb
  */


  exports.getActionInvokers = function(cb) {
    return _this.ai.getModules(cb);
  };

  /*
  Fetch all action invokers and hand them to cb(err, obj).
  
  @public getActionInvokers( *cb* )
  @param {function} cb
  */


  exports.deleteActionInvoker = function(aiId) {
    return _this.ai.deleteModule(aiId);
  };

  /*
  Store user-specific action invoker parameters .
  
  @public storeActionParams( *userId, aiId, data* )
  @param {String} userId
  @param {String} aiId
  @param {String} data
  */


  exports.storeActionParams = function(aiId, userId, data) {
    return _this.ai.storeParameters(aiId, userId, data);
  };

  /*
  Query the DB for user-specific action module parameters,
  and pass it to cb(err, obj).
  
  @public getActionParams( *userId, aiId, cb* )
  @param {String} userId
  @param {String} aiId
  @param {function} cb
  */


  exports.getActionParams = function(aiId, userId, cb) {
    return _this.ai.getParameters(aiId, userId, cb);
  };

  /*
  Fetch all action params IDs and hand them to cb(err, obj).
  
  @public getActionParamsIds( *cb* )
  @param {function} cb
  */


  exports.getActionParamsIds = function(cb) {
    return _this.ai.getParametersIds(cb);
  };

  /*
  Fetch all action modules and hand them to cb(err, obj).
  
  @public deleteActionParams( *cb* )
  @param {function} cb
  */


  exports.deleteActionParams = function(aiId, userId) {
    return _this.ai.deleteParameters(aiId, userId);
  };

  /*
  ## Event Pollers
  */


  /*
  Store a string representation of an event poller in the DB.
  
  @public storeEventPoller ( *epId, data* )
  @param {String} epId
  @param {String} data
  */


  exports.storeEventPoller = function(epId, data) {
    return _this.ep.storeModule(epId, data);
  };

  /*
  Query the DB for an event poller and pass it to cb(err, obj).
  
  @public getEventPoller( *epId, cb* )
  @param {String} epId
  @param {function} cb
  */


  exports.getEventPoller = function(epId, cb) {
    return _this.ep.getModule(epId, cb);
  };

  /*
  Fetch all event poller IDs and hand them to cb(err, obj).
  
  @public getEventPollerIds( *cb* )
  @param {function} cb
  */


  exports.getEventPollerIds = function(cb) {
    return _this.ep.getModuleIds(cb);
  };

  /*
  Fetch all event pollers and hand them to cb(err, obj).
  
  @public getEventPollers( *cb* )
  @param {function} cb
  */


  exports.getEventPollers = function(cb) {
    return _this.ep.getModules(cb);
  };

  /*
  Fetch all event pollers and hand them to cb(err, obj).
  
  @public getEventPollers( *cb* )
  @param {function} cb
  */


  exports.deleteEventPoller = function(epId) {
    return _this.ep.deleteModule(epId);
  };

  /*
  Store user-specific event poller parameters .
  
  @public storeEventParams( *userId, epId, data* )
  @param {String} userId
  @param {String} epId
  @param {String} data
  */


  exports.storeEventParams = function(epId, userId, data) {
    return _this.ep.storeParameters(epId, userId, data);
  };

  /*
  Query the DB for user-specific event module parameters,
  and pass it to cb(err, obj).
  
  @public getEventParams( *userId, epId, cb* )
  @param {String} userId
  @param {String} epId
  @param {function} cb
  */


  exports.getEventParams = function(epId, userId, cb) {
    return _this.ep.getParameters(epId, userId, cb);
  };

  /*
  Fetch all event params IDs and hand them to cb(err, obj).
  
  @public getEventParamsIds( *cb* )
  @param {function} cb
  */


  exports.getEventParamsIds = function(cb) {
    return _this.ep.getParametersIds(cb);
  };

  /*
  Fetch all event modules and hand them to cb(err, obj).
  
  @public deleteEventParams( *cb* )
  @param {function} cb
  */


  exports.deleteEventParams = function(epId, userId) {
    return _this.ep.deleteParameters(epId, userId);
  };

  /*
  ## Rules
  */


  /*
  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(ruleId, cb) {
    _this.log.info("DB | getRule: '" + ruleId + "'");
    return _this.db.get("rule:" + ruleId, cb);
  };

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


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

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


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

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


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

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


  exports.deleteRule = function(ruleId) {
    _this.log.info("DB | deleteRule: '" + ruleId + "'");
    _this.db.srem("rules", ruleId, replyHandler("Deleting rule key '" + ruleId + "'"));
    _this.db.del("rule:" + ruleId, replyHandler("Deleting rule '" + ruleId + "'"));
    _this.db.smembers("rule:" + ruleId + ":users", function(err, obj) {
      var delLinkedUserRule, id, _i, _len, _results;
      delLinkedUserRule = function(userId) {
        return _this.db.srem("user:" + userId + ":rules", ruleId, replyHandler("Deleting rule key '" + ruleId + "' in linked user '" + userId + "'"));
      };
      _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("Deleting 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("Deleting rule key '" + ruleId + "' in active user '" + userId + "'"));
      };
      _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("Deleting rule '" + ruleId + "' active users"));
  };

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


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

  /*
  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(userId, cb) {
    _this.log.info("DB | getUserLinkedRules: for user '" + userId + "'");
    return _this.db.smembers("user:" + userId + ":rules", cb);
  };

  /*
  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(ruleId, cb) {
    _this.log.info("DB | getRuleLinkedUsers: for rule '" + ruleId + "'");
    return _this.db.smembers("rule:" + ruleId + ":users", cb);
  };

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


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

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


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

  /*
  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(userId, cb) {
    _this.log.info("DB | getUserActivatedRules: for user '" + userId + "'");
    return _this.db.smembers("user:" + userId + ":active-rules", cb);
  };

  /*
  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(ruleId, cb) {
    _this.log.info("DB | getRuleActivatedUsers: for rule '" + ruleId + "'");
    return _this.db.smembers("rule:" + ruleId + ":active-users", cb);
  };

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


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

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


  exports.getAllActivatedRuleIdsPerUser = 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;
      }
    });
  };

  /*
  ## 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(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'));
    }
  };

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


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

  /*
  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(userId, cb) {
    _this.log.info("DB | getUser: '" + userId + "'");
    return _this.db.hgetall("user:" + userId, cb);
  };

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


  exports.deleteUser = 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"));
  };

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

  /*
  ## User Roles
  */


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


  exports.storeUserRole = 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 + "'"));
  };

  /*
  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(userId, cb) {
    _this.log.info("DB | getUserRoles: '" + userId + "'");
    return _this.db.smembers("user:" + userId + ":roles", cb);
  };

  /*
  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(role, cb) {
    _this.log.info("DB | getRoleUsers: '" + role + "'");
    return _this.db.smembers("role:" + role + ":users", cb);
  };

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


  exports.removeUserRole = 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 + "'"));
  };

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


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

}).call(this);
