/*
options includes:
- host
- port
- masterOptions
- masterName
- logger (e.g. winston)
- debug (boolean)
*/
function RedisSentinelClient(options) {

  // RedisClient takes (stream,options). we don't want stream, make sure only one.
  if (arguments.length > 1) {
    throw new Error("Sentinel client takes only options to initialize");
  }

  // make this an EventEmitter (also below)
  events.EventEmitter.call(this);

  var self = this;

  this.options = options = options || {};

  this.options.masterName = this.options.masterName || 'mymaster';

  self.emitMasterErrors = false;

  // no socket support for now (b/c need multiple connections).
  if (options.port == null || options.host == null) {
    throw new Error("Sentinel client needs a host and port");
  }


  // if debugging is enabled for sentinel client, enable master client's too.
  // (standard client just uses console.log, not the 'logger' passed here.)
  if (options.debug) {
    RedisSingleClient.debug_mode = true;
  }

  var masterOptions = options.masterOptions || {};
  masterOptions.disable_flush = true; // Disables flush_and_error, to preserve queue

  // if master & slaves need a password to authenticate,
  // pass it in as 'master_auth_pass'.
  // (corresponds w/ 'auth_pass' for normal client,
  // but differentiating b/c we're not authenticating to the *sentinel*, rather to the master/slaves.)
  // by setting it to 'auth_pass' on master client, it should authenticate to the master (& slaves on failover).
  // note, sentinel daemon's conf needs to know this same password too/separately.
  masterOptions.auth_pass = options.master_auth_pass || null;

  // this client will always be connected to the active master.
  // using 9999 as initial; expected to fail; is replaced & re-connected to real port later.
  self.activeMasterClient = new RedisSingleClient.createClient(9999, '127.0.0.1', masterOptions);

  
  // pass up errors
  // @todo emit a separate 'master error' event?
  self.activeMasterClient.on('error', function(error){
    if (self.emitMasterErrors) {
      error.message = self.myName + " master error: " + error.message;
      self.onError.call(self, error);
    }
  });

  // pass up messages
  self.activeMasterClient.on('message', function(channel, message){
    self.emit('message', channel, message);
  });

  // pass up pmessage
  self.activeMasterClient.on('pmessage', function(pattern, channel, message){
    self.emit('pmessage', pattern, channel, message);
  });

  // pass these through
  ['unsubscribe','end', 'reconnecting'].forEach(function(staticProp){
    // @todo rewrite this to use `apply`
    self.activeMasterClient.on(staticProp, function(a, b, c, d){
      self.emit(staticProp, a, b, c, d);
    });
  });


  // used for logging & errors
  this.myName = 'sentinel-' + this.options.host + ':' + this.options.port + '-' + this.options.masterName;


  /*
  what a failover looks like:
  - master fires ECONNREFUSED errors a few times
  - sentinel listener gets:
    +sdown
    +odown
    +failover-triggered
    +failover-state-wait-start
    +failover-state-select-slave
    +selected-slave
    +failover-state-send-slaveof-noone
    +failover-state-wait-promotion
    +promoted-slave
    +failover-state-reconf-slaves
    +slave-reconf-sent
    +slave-reconf-inprog
    +slave-reconf-done
    +failover-end
    +switch-master

  (see docs @ http://redis.io/topics/sentinel)

  note, these messages don't specify WHICH master is down.
  so if a sentinel is listening to multiple masters, and we have a RedisSentinelClient
  for each sentinel:master relationship, every client will be notified of every master's failovers.
  But that's fine, b/c reconnect() checks if it actually changed, and does nothing if not.
  */

  // one client to query ('talker'), one client to subscribe ('listener').
  // these are standard redis clients.
  // talker is used by reconnect() below
  this.sentinelTalker = new RedisSingleClient.createClient(options.port, options.host);
  this.sentinelTalker.on('connect', function(){
    self.debug('connected to sentinel talker');
  });
  this.sentinelTalker.on('error', function(error){
    error.message = self.myName + " talker error: " + error.message;
    self.onError.call(self, error);
  });
  this.sentinelTalker.on('end', function(){
    self.debug('sentinel talker disconnected');
    // @todo emit something?
    // @todo does it automatically reconnect? (supposed to)
  });

  var sentinelListener = new RedisSingleClient.createClient(options.port, options.host);
  sentinelListener.on('connect', function(){
    self.debug('connected to sentinel listener');
  });
  sentinelListener.on('error', function(error){
    error.message = self.myName + " listener error: " + error.message;
    self.onError(error);
  });
  sentinelListener.on('end', function(){
    self.debug('sentinel listener disconnected');
    // @todo emit something?
  });

  // Connect on load
  this.reconnect();

  // Subscribe to all messages
  sentinelListener.psubscribe('*');

  sentinelListener.on('pmessage', function(channel, msg) {
    self.debug('sentinel message', msg);

    // pass up, in case app wants to respond
    self.emit('sentinel message', msg);

    switch(msg) {
      case '+sdown':
        self.debug('Down detected');
        self.emit('down-start');
        self.emitMasterErrors = false;
        break;

      case '+failover-triggered':
        self.debug('Failover detected');
        self.emit('failover-start');
        break;

      case '+switch-master':
        self.debug('Reconnect triggered by ' + msg);
        self.emit('failover-end');
        self.reconnect();
        break;
    }
  });

}