var debugProtocol = require('debug')('node-inspector:protocol:devtools');

var RuntimeAgent = require('./RuntimeAgent').RuntimeAgent,
  PageAgent = require('./PageAgent').PageAgent,
  NetworkAgent = require('./NetworkAgent').NetworkAgent,
  DebuggerAgent = require('./DebuggerAgent').DebuggerAgent;

/**
 * @param {Object} config
 * @param {FrontendClient} frontendClient
 * @param {DebuggerClient} debuggerClient
 * @param {BreakEventHandler} breakEventHandler
 * @param {ScriptManager} scriptManager
 */
function FrontendCommandHandler(config,
                                frontendClient,
                                debuggerClient,
                                breakEventHandler,
                                scriptManager) {
  this._config = config;
  this._agents = {};
  this._specialCommands = {};
  this._frontendClient = frontendClient;
  this._debuggerClient = debuggerClient;
  this._breakEventHandler = breakEventHandler;
  this._scriptManager = scriptManager;
  this._initializeRegistry();
  this._registerEventHandlers();
  this._pauseInitialEvents();
}

FrontendCommandHandler.prototype = {
  _initializeRegistry: function() {
    this._registerAgent(
      'Debugger',
      new DebuggerAgent(
        this._config,
        this._frontendClient,
        this._debuggerClient,
        this._breakEventHandler,
        this._scriptManager)
    );

    this._registerAgent('Runtime', new RuntimeAgent(this._config, this._debuggerClient));

    this._registerAgent(
      'Page',
      new PageAgent(
        this._config,
        this._debuggerClient,
        this._scriptManager)
    );

    this._registerAgent('Network', new NetworkAgent());

    this._registerNoopCommands(
      'Network.enable',
      'Console.enable',
      'Console.clearMessages',
      'Database.enable',
      'DOMStorage.enable',
      'DOM.hideHighlight',
      'Inspector.enable',
      'Page.clearDeviceOrientationOverride',
      'Page.clearGeolocationOverride',
      'Page.setContinuousPaintingEnabled',
      'Page.setEmulatedMedia',
      'Page.setDeviceMetricsOverride',
      'Page.setShowDebugBorders',
      'Page.setShowFPSCounter',
      'Page.setShowScrollBottleneckRects',
      'Page.setShowViewportSizeOnResize',
      'Profiler.enable',
      'CSS.enable'
    );

    this._registerQuery('CSS.getSupportedCSSProperties', { cssProperties: []});
    this._registerQuery('Worker.canInspectWorkers', { result: false });
    this._registerQuery('Page.getScriptExecutionStatus', { result: 'enabled' });
  },

  _registerAgent: function(name, agent) {
    this._agents[name] = agent;
  },

  _registerNoopCommands: function() {
    var i, fullMethodName;
    for (i = 0; i < arguments.length; i++) {
      fullMethodName = arguments[i];
      this._specialCommands[fullMethodName] = {};
    }
  },

  _registerQuery: function(fullMethodName, result) {
    this._specialCommands[fullMethodName] = { result: result };
  },

  _registerEventHandlers: function() {
    this._frontendClient.on(
      'message',
       this._handleFrontendMessage.bind(this));
  },

  _handleFrontendMessage: function(message) {
    debugProtocol('frontend: ' + message);
    var command = JSON.parse(message);
    this.handleCommand(command);
  },

  _pauseInitialEvents: function() {
    this._frontendClient.pauseEvents();
    this._agents.Page.on('resource-tree', function() {
      this._frontendClient.resumeEvents();
    }.bind(this));
  },

  handleCommand: function(messageObject) {
    var fullMethodName = messageObject.method,
      domainAndMethod = fullMethodName.split('.'),
      domainName = domainAndMethod[0],
      methodName = domainAndMethod[1],
      requestId = messageObject.id,
      agent,
      method;

    if (this._specialCommands[fullMethodName]) {
      this._handleMethodResult(
        requestId,
        fullMethodName,
        null,
        this._specialCommands[fullMethodName].result);
      return;
    }

    agent = this._agents[domainName];
    if (!agent) {
      this._sendNotImplementedResponse(requestId, fullMethodName);
      return;
    }

    method = agent[methodName];
    if (!method || typeof method !== 'function') {
      this._sendNotImplementedResponse(requestId, fullMethodName);
      return;
    }


    method.call(agent, messageObject.params, function(error, result) {
      this._handleMethodResult(messageObject.id, fullMethodName, error, result);
    }.bind(this));
  },

  _sendNotImplementedResponse: function(requestId, fullMethodName) {
    console.log(
      'Received request for a method not implemented:',
      fullMethodName
    );

    this._handleMethodResult(
      requestId,
      fullMethodName,
      new Error('Not implemented.')
    );
  },

  _handleMethodResult: function(requestId, fullMethodName, error, result) {
    var response;

    if (!requestId) {
      if (response !== undefined)
        console.log('Warning: discarded result of ' + fullMethodName);
      return;
    }

    this._frontendClient.sendResponse(requestId, fullMethodName, error, result);
  }
};

exports.FrontendCommandHandler = FrontendCommandHandler;
