// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This file contains implementations of the DebuggerRemoteService methods,
// defines DebuggerRemoteService and DebuggerRemoteServiceCommand constants.

#include "chrome/browser/debugger/debugger_remote_service.h"

#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/string_number_conversions.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/debugger/devtools_manager.h"
#include "chrome/browser/debugger/devtools_protocol_handler.h"
#include "chrome/browser/debugger/devtools_remote_message.h"
#include "chrome/browser/debugger/inspectable_tab_proxy.h"
#include "chrome/common/devtools_messages.h"
#include "chrome/common/render_messages.h"
#include "content/browser/renderer_host/render_view_host.h"
#include "content/browser/tab_contents/tab_contents.h"

namespace {

// Constants for the "data", "result", and "command" JSON message fields.
const char kDataKey[] = "data";
const char kResultKey[] = "result";
const char kCommandKey[] = "command";

}  // namespace

const std::string DebuggerRemoteServiceCommand::kAttach = "attach";
const std::string DebuggerRemoteServiceCommand::kDetach = "detach";
const std::string DebuggerRemoteServiceCommand::kDebuggerCommand =
    "debugger_command";
const std::string DebuggerRemoteServiceCommand::kEvaluateJavascript =
    "evaluate_javascript";
const std::string DebuggerRemoteServiceCommand::kFrameNavigate =
    "navigated";
const std::string DebuggerRemoteServiceCommand::kTabClosed =
    "closed";

const std::string DebuggerRemoteService::kToolName = "V8Debugger";

DebuggerRemoteService::DebuggerRemoteService(DevToolsProtocolHandler* delegate)
    : delegate_(delegate) {}

DebuggerRemoteService::~DebuggerRemoteService() {}

// This method handles the V8Debugger tool commands which are
// retrieved from the request "command" field. If an operation result
// is ready off-hand (synchronously), it is sent back to the remote debugger.
// Otherwise the corresponding response is received through IPC from the
// V8 debugger via DevToolsClientHost.
void DebuggerRemoteService::HandleMessage(
    const DevToolsRemoteMessage& message) {
  const std::string destination = message.destination();
  scoped_ptr<Value> request(base::JSONReader::Read(message.content(), true));
  if (request.get() == NULL) {
    // Bad JSON
    NOTREACHED();
    return;
  }
  DictionaryValue* content;
  if (!request->IsType(Value::TYPE_DICTIONARY)) {
    NOTREACHED();  // Broken protocol :(
    return;
  }
  content = static_cast<DictionaryValue*>(request.get());
  if (!content->HasKey(kCommandKey)) {
    NOTREACHED();  // Broken protocol :(
    return;
  }
  std::string command;
  DictionaryValue response;

  content->GetString(kCommandKey, &command);
  response.SetString(kCommandKey, command);
  bool send_response = true;
  if (destination.empty()) {
    // Unknown command (bad format?)
    NOTREACHED();
    response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND);
    SendResponse(response, message.tool(), message.destination());
    return;
  }
  int32 tab_uid = -1;
  base::StringToInt(destination, &tab_uid);

  if (command == DebuggerRemoteServiceCommand::kAttach) {
    // TODO(apavlov): handle 0 for a new tab
    response.SetString(kCommandKey, DebuggerRemoteServiceCommand::kAttach);
    AttachToTab(destination, &response);
  } else if (command == DebuggerRemoteServiceCommand::kDetach) {
    response.SetString(kCommandKey, DebuggerRemoteServiceCommand::kDetach);
    DetachFromTab(destination, &response);
  } else if (command == DebuggerRemoteServiceCommand::kDebuggerCommand) {
    send_response = DispatchDebuggerCommand(tab_uid, content, &response);
  } else if (command == DebuggerRemoteServiceCommand::kEvaluateJavascript) {
    send_response = DispatchEvaluateJavascript(tab_uid, content, &response);
  } else {
    // Unknown command
    NOTREACHED();
    response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND);
  }

  if (send_response) {
    SendResponse(response, message.tool(), message.destination());
  }
}

void DebuggerRemoteService::OnConnectionLost() {
  delegate_->inspectable_tab_proxy()->OnRemoteDebuggerDetached();
}

// Sends a JSON response to the remote debugger using |response| as content,
// |tool| and |destination| as the respective header values.
void DebuggerRemoteService::SendResponse(const Value& response,
                                         const std::string& tool,
                                         const std::string& destination) {
  std::string response_content;
  base::JSONWriter::Write(&response, false, &response_content);
  scoped_ptr<DevToolsRemoteMessage> response_message(
      DevToolsRemoteMessageBuilder::instance().Create(tool,
                                                      destination,
                                                      response_content));
  delegate_->Send(*response_message.get());
}

// Gets a TabContents instance corresponding to the |tab_uid| using the
// InspectableTabProxy controllers map, or NULL if none found.
TabContents* DebuggerRemoteService::ToTabContents(int32 tab_uid) {
  const InspectableTabProxy::ControllersMap& navcon_map =
      delegate_->inspectable_tab_proxy()->controllers_map();
  InspectableTabProxy::ControllersMap::const_iterator it =
      navcon_map.find(tab_uid);
  if (it != navcon_map.end()) {
    TabContents* tab_contents = it->second->tab_contents();
    if (tab_contents == NULL) {
      return NULL;
    } else {
      return tab_contents;
    }
  } else {
    return NULL;
  }
}

// Gets invoked from a DevToolsClientHost callback whenever
// a message from the V8 VM debugger corresponding to |tab_id| is received.
// Composes a Chrome Developer Tools Protocol JSON response and sends it
// to the remote debugger.
void DebuggerRemoteService::DebuggerOutput(int32 tab_uid,
                                           const std::string& message) {
  std::string content = StringPrintf(
      "{\"command\":\"%s\",\"result\":%s,\"data\":%s}",
      DebuggerRemoteServiceCommand::kDebuggerCommand.c_str(),
      base::IntToString(RESULT_OK).c_str(),
      message.c_str());
  scoped_ptr<DevToolsRemoteMessage> response_message(
      DevToolsRemoteMessageBuilder::instance().Create(
          kToolName,
          base::IntToString(tab_uid),
          content));
  delegate_->Send(*(response_message.get()));
}

// Gets invoked from a DevToolsClientHost callback whenever
// a tab corresponding to |tab_id| changes its URL. |url| is the new
// URL of the tab (may be the same as the previous one if the tab is reloaded).
// Sends the corresponding message to the remote debugger.
void DebuggerRemoteService::FrameNavigate(int32 tab_uid,
                                          const std::string& url) {
  DictionaryValue value;
  value.SetString(kCommandKey, DebuggerRemoteServiceCommand::kFrameNavigate);
  value.SetInteger(kResultKey, RESULT_OK);
  value.SetString(kDataKey, url);
  SendResponse(value, kToolName, base::IntToString(tab_uid));
}

// Gets invoked from a DevToolsClientHost callback whenever
// a tab corresponding to |tab_id| gets closed.
// Sends the corresponding message to the remote debugger.
void DebuggerRemoteService::TabClosed(int32 tab_id) {
  DictionaryValue value;
  value.SetString(kCommandKey, DebuggerRemoteServiceCommand::kTabClosed);
  value.SetInteger(kResultKey, RESULT_OK);
  SendResponse(value, kToolName, base::IntToString(tab_id));
}

// Attaches a remote debugger to the target tab specified by |destination|
// by posting the DevToolsAgentMsg_Attach message and sends a response
// to the remote debugger immediately.
void DebuggerRemoteService::AttachToTab(const std::string& destination,
                                        DictionaryValue* response) {
  int32 tab_uid = -1;
  base::StringToInt(destination, &tab_uid);
  if (tab_uid < 0) {
    // Bad tab_uid received from remote debugger (perhaps NaN)
    response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    return;
  }
  if (tab_uid == 0) {  // single tab_uid
    // We've been asked to open a new tab with URL
    // TODO(apavlov): implement
    NOTIMPLEMENTED();
    response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    return;
  }
  TabContents* tab_contents = ToTabContents(tab_uid);
  if (tab_contents == NULL) {
    // No active tab contents with tab_uid
    response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    return;
  }
  RenderViewHost* target_host = tab_contents->render_view_host();
  DevToolsClientHost* client_host =
      delegate_->inspectable_tab_proxy()->ClientHostForTabId(tab_uid);
  if (client_host == NULL) {
    client_host =
        delegate_->inspectable_tab_proxy()->NewClientHost(tab_uid, this);
    DevToolsManager* manager = DevToolsManager::GetInstance();
    if (manager != NULL) {
      manager->RegisterDevToolsClientHostFor(target_host, client_host);
      response->SetInteger(kResultKey, RESULT_OK);
    } else {
      response->SetInteger(kResultKey, RESULT_DEBUGGER_ERROR);
    }
  } else {
    // DevToolsClientHost for this tab is already registered
    response->SetInteger(kResultKey, RESULT_ILLEGAL_TAB_STATE);
  }
}

// Detaches a remote debugger from the target tab specified by |destination|
// by posting the DevToolsAgentMsg_Detach message and sends a response
// to the remote debugger immediately.
void DebuggerRemoteService::DetachFromTab(const std::string& destination,
                                          DictionaryValue* response) {
  int32 tab_uid = -1;
  base::StringToInt(destination, &tab_uid);
  if (tab_uid == -1) {
    // Bad tab_uid received from remote debugger (NaN)
    if (response != NULL) {
      response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    }
    return;
  }
  int result_code;
  DevToolsClientHostImpl* client_host =
      delegate_->inspectable_tab_proxy()->ClientHostForTabId(tab_uid);
  if (client_host != NULL) {
    client_host->Close();
    result_code = RESULT_OK;
  } else {
    // No client host registered for |tab_uid|.
    result_code = RESULT_UNKNOWN_TAB;
  }
  if (response != NULL) {
    response->SetInteger(kResultKey, result_code);
  }
}

// Sends a V8 debugger command to the target tab V8 debugger.
// Does not send back a response (which is received asynchronously
// through IPC) unless an error occurs before the command has actually
// been sent.
bool DebuggerRemoteService::DispatchDebuggerCommand(int tab_uid,
                                                    DictionaryValue* content,
                                                    DictionaryValue* response) {
  if (tab_uid == -1) {
    // Invalid tab_uid from remote debugger (perhaps NaN)
    response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    return true;
  }
  DevToolsManager* manager = DevToolsManager::GetInstance();
  if (manager == NULL) {
    response->SetInteger(kResultKey, RESULT_DEBUGGER_ERROR);
    return true;
  }
  TabContents* tab_contents = ToTabContents(tab_uid);
  if (tab_contents == NULL) {
    // Unknown tab_uid from remote debugger
    response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    return true;
  }
  DevToolsClientHost* client_host =
      manager->GetDevToolsClientHostFor(tab_contents->render_view_host());
  if (client_host == NULL) {
    // tab_uid is not being debugged (Attach has not been invoked)
    response->SetInteger(kResultKey, RESULT_ILLEGAL_TAB_STATE);
    return true;
  }
  std::string v8_command;
  DictionaryValue* v8_command_value;
  content->GetDictionary(kDataKey, &v8_command_value);
  base::JSONWriter::Write(v8_command_value, false, &v8_command);
  manager->ForwardToDevToolsAgent(
      client_host, DevToolsAgentMsg_DebuggerCommand(v8_command));
  // Do not send the response right now, as the JSON will be received from
  // the V8 debugger asynchronously.
  return false;
}

// Sends the immediate "evaluate Javascript" command to the V8 debugger.
// The evaluation result is not sent back to the client as this command
// is in fact needed to invoke processing of queued debugger commands.
bool DebuggerRemoteService::DispatchEvaluateJavascript(
    int tab_uid,
    DictionaryValue* content,
    DictionaryValue* response) {
  if (tab_uid == -1) {
    // Invalid tab_uid from remote debugger (perhaps NaN)
    response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    return true;
  }
  TabContents* tab_contents = ToTabContents(tab_uid);
  if (tab_contents == NULL) {
    // Unknown tab_uid from remote debugger
    response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    return true;
  }
  RenderViewHost* render_view_host = tab_contents->render_view_host();
  if (render_view_host == NULL) {
    // No RenderViewHost
    response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    return true;
  }
  std::string javascript;
  content->GetString(kDataKey, &javascript);
  render_view_host->ExecuteJavascriptInWebFrame(string16(),
                                                UTF8ToUTF16(javascript));
  return false;
}