var socketIo = require('socket.io');
var command = require('./command');
var defaultAPI = require('./api/socket.io');

var DEFAULT_TIMEOUT = 10 * 1000;

function deepClone(base) {
  if (!base || typeof base != 'object')
    return base;

  var cloned = Object.create(null);
  Object.keys(base).forEach(function(key) {
    cloned[key] = deepClone(base[key]);
  });
  return cloned;
}

function sanitizeBackendMessage(envelope) {
  return {
    statusCode: envelope.statusCode,
    body: deepClone(envelope.body)
  };
}
exports.sanitizeBackendMessage = sanitizeBackendMessage;

exports.register = function(application, server, params) {
  params = params || {};
  var connection = params.connection;
  if (!connection)
    throw new Error('Connection to the backend is required!');

  if (!server)
    throw new Error('A server instance is required!');

  function createClientMessageHandler(commandName, socket) {
    return (function(data, clientOptions) {
      var commandDefinition = unifiedCommandSet[commandName];
      if (!commandDefinition)
        return;

      clientOptions = clientOptions || {};

      var event = commandName;
      if (commandDefinition.requestConverter) {
        try {
          var result = commandDefinition.requestConverter(event, data);
          event = result[0];
          data = result[1];
        } catch(error) {
          socket.emit('error', error.message || error);
          return;
        }
      }

      var callback = null;
      var options = {};
      if (command.RequestResponse.isInstance(commandDefinition)) {
        callback = function(error, envelope) {
          if (error) {
            socket.emit('error', error);
            return;
          }

          var responseEvent = envelope.type;
          var responseData = envelope.body;

          if (commandDefinition.responseConverter) {
            var result = commandDefinition.responseConverter(responseEvent, responseData);
            responseEvent = result[0];
            responseData = result[1];
          }

          if (clientOptions.responseEvent)
            responseEvent = clientOptions.responseEvent;

          socket.emit(responseEvent, responseData);
        };
        options.timeout = DEFAULT_TIMEOUT;
      } else if (command.PublishSubscribe.isInstance(commandDefinition)) {
        event += '.subscribe';
      }

      options.sessionId = socket.id;
      connection.emitMessage(event, data, callback, options);
    });
  }

  function createPublishedMessageHandler(commandName, socket) {
    return (function(body) {
      var commandDefinition = unifiedCommandSet[commandName];

      var event = commandName;
      var data = body;

      if (commandDefinition.responseConverter) {
        var result = commandDefinition.responseConverter(event, data);
        event = result[0];
        data = result[1];
      }

      socket.emit(event, data);
    });
  }

  function createErrorHandler(socket) {
    return (function(error) {
      socket.emit('error', error);
    });
  }

  var commandSets = [defaultAPI].concat(params.plugins || []);
  var unifiedCommandSet = {};
  commandSets.forEach(function(commandSet) {
    Object.keys(commandSet).forEach(function(commandName) {
      var definition = commandSet[commandName];
      if (!command.SocketCommand.isInstance(definition))
        return;
      unifiedCommandSet[commandName] = definition;
    });
  });

  var registeredCommands = [];
  Object.keys(unifiedCommandSet).forEach(function(commandName) {
    var definition = unifiedCommandSet[commandName];
    if (!command.SocketCommand.isInstance(definition))
      return;
    registeredCommands.push({ name:       commandName,
                              definition: definition });
  });

  var io = socketIo.listen(server);
  io.sockets.on('connection', function(socket) {
    application.emit('connection', socket);

    var events = [];
    var handlers = {};
    registeredCommands.forEach(function(command) {
      if (command.PublishSubscribe.isInstance(command.definition)) {
        socket.on(command.name + '.subscribe',
                  createClientMessageHandler(command.name, socket));

        var publishedMessageHandler = createPublishedMessageHandler(command.name, socket);
        connection.on(command.name, publishedMessageHandler);
        events.push(command.name);
        handlers[command.name] = publishedMessageHandler;
      } else {
        socket.on(command.name,
                  createClientMessageHandler(command.name, socket));
      }
    });

    var errorHandler = createErrorHandler(socket);
    connection.on('error', errorHandler);

    socket.on('disconnect', function() {
      events.forEach(function(event) {
        connection.removeListener(event, handlers[event]);
      });
      events = undefined;
      handlers = undefined;
      connection.removeListener('error', errorHandler);
      socket.removeAllListeners();
    });
  });

  return registeredCommands;
}

@@ -3951,25 +3951,35 @@
 ch(function(
-c
+registeredC
 ommand) %7B%0A  
@@ -4018,25 +4018,35 @@
 .isInstance(
-c
+registeredC
 ommand.defin
@@ -4065,33 +4065,43 @@
       socket.on(
-c
+registeredC
 ommand.name + '.
@@ -4149,33 +4149,43 @@
 tMessageHandler(
-c
+registeredC
 ommand.name, soc
@@ -4252,33 +4252,43 @@
 dMessageHandler(
-c
+registeredC
 ommand.name, soc
@@ -4311,25 +4311,35 @@
 nnection.on(
-c
+registeredC
 ommand.name,
@@ -4385,17 +4385,27 @@
 ts.push(
-c
+registeredC
 ommand.n
@@ -4427,17 +4427,27 @@
 andlers%5B
-c
+registeredC
 ommand.n
@@ -4511,17 +4511,27 @@
 cket.on(
-c
+registeredC
 ommand.n
@@ -4576,25 +4576,35 @@
 sageHandler(
-c
+registeredC
 ommand.name,

Resolve confliction of variable names