  /*
 * hook-shim.js: A slimmed down minimal Hook.js class for the browser
 *
 * run './bin/browser' to convert this script to a browser friendly format
 * (C) 2011 Nodejitsu Inc.
 * MIT LICENCE
 *
 */

var VERSION = "0.8.x";

var dnode  = require('dnode'),
    path   = require('path'),
    EventEmitter = require('eventemitter2').EventEmitter2;

//
// Default EventEmitter2 delimiter
//
var DELIMITER = '::';

//
// Create a shim for the base hook.io Hook class
//
var Hook = exports.Hook = function (options) {
  var self = this;

  self.reconnectionTimer = null;
  EventEmitter.call(this, { delimiter: DELIMITER, wildcard: true });

  options = options || {};
  this.defaults = {};
  this.defaults.name = "no-name";

  this.name   = this.name   || options.name   || options['hook-name'] || this.defaults.name;
  this.type   = this.type   || options.type   || options['hook-type'] || 'browser-hook';
};

//
// Hook inherit from `EventEmitter2`.
//
inherits(Hook, EventEmitter);

//
// Shim of Hook.prototype.emit from core hook.io project
//
//
// TODO: Remove all this copy and pasted code from Hook.emit and replace,
// with a require()
//

Hook.prototype.emit = function (event, data, callback, sender) {

  var self = this;

  // For local events, i.e. generated by this hook
  if(!sender) {
    sender = this._senderInfo(this);
  }
  
  // Remark: `newListener`, `removeListener`, and  is reserved by EE and EE2,
  // if we encounter it, just fire EventEmitter.emit as normal,
  // with no arguments modifications
  //
  if (['newListener', 'removeListener', 'removeAllListeners'].indexOf(event) !== -1) {
    if(self.remote && sender.name === this.name) {
      // If a client's subscriptions change, update the server.
      self.remote.message(event, data, callback, sender);
    }
    return EventEmitter.prototype.emit.apply(this, arguments);
  }

  //
  // Curry arguments to support multiple styles,
  // of callback passing.
  //
  if(typeof data === 'function') {
   callback = data;
   data = null;
  }

  if(typeof callback !== 'function') {
   //
   // Remark: If no callback has been sent,
   // attempt to auto-create a callback that emits,
   // based on the following convention:
   //
   //
   //  Since no callback function was detected, we are going to create a callback,
   //  that emits back the event name appended with either:
   //
   //         `event::result`  - Emitted when callback is fired without error
   //              OR
   //         `event::error`   - Emitted when callback is fired with an error
   //

   callback = function(err, result){
     if (err) {
       //
       // Remark: In addition to firing the `::error` event,
       // we set a property `ctx` of the error, which
       // contains the original data sent to the hook that caused,
       // the error in the first place. This is useful for debugging.
       //
       err.ctx = data;
       return self.emit(event + DELIMITER+'error', err);
     }
     result.ctx = data;
     return self.emit(event + DELIMITER+'result', result);
   };
  }

    //
    // If this call to emit has not been forced local and this instance has a
    // remote (i.e. parent) connection, then broadcast event back to the remote
    
  if (self.remote && sender.name === this.name) {
      this.remote.message(this.name+DELIMITER+event, data, callback, sender);
  }

  //
  // Remark: After we process any hook.io messaging,
  // we still need to call the event, so fire it
  //
  return EventEmitter.prototype.emit.apply(this, [event, data, callback]);
};

Hook.prototype.connect = function () {

  var self = this;

  //
  // Create a dnode / socketio client which exposes,
  // a `message` function
  //
  var client = dnode({
    message: function(event, data, callback, sender){
      self.emit(event, data, callback, sender || {name : 'unknown', type : 'unknown'});
    },
    hasEvent: function (parts, callback) {
      callback(null, self.hasEvent.call(self, parts));
    },
    cullYours: function () {}
  });

  //
  // connect() will recursively call itself,
  // until a connection is ready
  //
  // When the connection ends connect() will,
  // continue to attempt to reconnect
  //
  //
  // connect() will recursively call itself,
  // until a connection is ready
  //
  // When the connection ends connect() will,
  // continue to attempt to reconnect
  //
  function connect() {
    client.connect(function (remote, conn) {
      self.remote = remote;
      self.conn   = conn;

      clearInterval(self.reconnectionTimer);

      conn.on('end', function(){
        //  Attempt reconnection
        self.reconnectionTimer = setInterval(function(){
          connect();
        }, 3000);
      });

      remote.report(self.getInfo(),  function (newName, newID, pkginfo, proposedRank) {
        self.name = newName;
        self.id   = newID;

        self.proposedRank = Number(proposedRank) || 1;
        self.emit('hook::rank::accepting', self.proposedRank);
        self.emit('hook::ready', self.getInfo());
      });
    });
  }


  connect();
};

Hook.prototype.start = function () {
  this.connect();
};

Hook.prototype._senderInfo = function(hook) {
  return {
    'name': hook.name,
    'type': hook.type
  };
};

Hook.prototype.getInfo = function () {
  var info = {
    "name"    : this.name,
    "type"    : this.type,
    "pkginfo" : {
      "version" : VERSION
    },
    "events"  : this.getStringEvents()
  };
  return info;
};

// ### function getStringEvents ()
// #### @return {Array} Array of strings representing each listened event.
// Rebuild a list of all the listened events under their string form factor.
//
Hook.prototype.getStringEvents = function () {
  function getStringEvents(node, paths, currentPath) {
    if(typeof node !== 'object') return;
    
    Object.keys(node).forEach(function(child) {
      if(child === '_listeners') return paths.push(currentPath.join(DELIMITER));
      getStringEvents(node[child], paths, currentPath.concat(child));
    });
    
    return paths;
  }

  return getStringEvents(this.listenerTree, [], [], []);
};

Hook.prototype.hasEvent = function (parts) {
 if (typeof parts !== 'string' && !(parts instanceof Array)) return false;
 return !!this.listeners(parts).length;
};

//
// Simple Inherits from node.js core
//
function inherits (ctor, superCtor) {
  ctor.super_ = superCtor;
  ctor.prototype = Object.create(superCtor.prototype, {
    constructor: {
      value: ctor,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
}
