// Copyright (c) 2011 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.

#include "chrome/browser/ui/webui/workers_ui.h"

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/json/json_writer.h"
#include "base/memory/ref_counted_memory.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "base/values.h"
#include "chrome/browser/debugger/devtools_window.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/chrome_url_data_manager_backend.h"
#include "chrome/browser/ui/webui/chrome_web_ui_data_source.h"
#include "chrome/common/url_constants.h"
#include "content/public/browser/devtools_agent_host_registry.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/browser/worker_host/worker_process_host.h"
#include "content/browser/worker_host/worker_service.h"
#include "content/browser/worker_host/worker_service_observer.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/process_type.h"
#include "grit/generated_resources.h"
#include "grit/workers_resources.h"
#include "ui/base/resource/resource_bundle.h"

using content::BrowserThread;
using content::DevToolsAgentHost;
using content::DevToolsAgentHostRegistry;

static const char kWorkersDataFile[] = "workers_data.json";

static const char kOpenDevToolsCommand[]  = "openDevTools";
static const char kTerminateWorkerCommand[]  = "terminateWorker";

static const char kWorkerProcessHostIdField[]  = "workerProcessHostId";
static const char kWorkerRouteIdField[]  = "workerRouteId";
static const char kUrlField[]  = "url";
static const char kNameField[]  = "name";
static const char kPidField[]  = "pid";

namespace {


DictionaryValue* BuildWorkerData(
    WorkerProcessHost* process,
    const WorkerProcessHost::WorkerInstance& instance) {
  DictionaryValue* worker_data = new DictionaryValue();
  worker_data->SetInteger(kWorkerProcessHostIdField, process->id());
  worker_data->SetInteger(kWorkerRouteIdField, instance.worker_route_id());
  worker_data->SetString(kUrlField, instance.url().spec());
  worker_data->SetString(kNameField, instance.name());
  worker_data->SetInteger(kPidField, base::GetProcId(process->handle()));
  return worker_data;
}

class WorkersUIHTMLSource : public ChromeWebUIDataSource {
 public:
  WorkersUIHTMLSource();

  virtual void StartDataRequest(const std::string& path,
                                bool is_incognito,
                                int request_id);
 private:
  ~WorkersUIHTMLSource() {}
  void SendSharedWorkersData(int request_id);
  DISALLOW_COPY_AND_ASSIGN(WorkersUIHTMLSource);
};

WorkersUIHTMLSource::WorkersUIHTMLSource()
    : ChromeWebUIDataSource(chrome::kChromeUIWorkersHost, NULL) {
  add_resource_path("workers.js", IDR_WORKERS_INDEX_JS);
  set_default_resource(IDR_WORKERS_INDEX_HTML);
}

void WorkersUIHTMLSource::StartDataRequest(const std::string& path,
                                           bool is_incognito,
                                           int request_id) {
  if (path == kWorkersDataFile) {
    SendSharedWorkersData(request_id);
  } else {
    ChromeWebUIDataSource::StartDataRequest(path, is_incognito, request_id);
  }
}

void WorkersUIHTMLSource::SendSharedWorkersData(int request_id) {
    ListValue workers_list;
    BrowserChildProcessHost::Iterator iter(content::PROCESS_TYPE_WORKER);
    for (; !iter.Done(); ++iter) {
      WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter);
      const WorkerProcessHost::Instances& instances = worker->instances();
      for (WorkerProcessHost::Instances::const_iterator i = instances.begin();
           i != instances.end(); ++i) {
         workers_list.Append(BuildWorkerData(worker, *i));
      }
    }

    std::string json_string;
    base::JSONWriter::Write(&workers_list, false, &json_string);

    SendResponse(request_id, base::RefCountedString::TakeString(&json_string));
}

class WorkersDOMHandler : public WebUIMessageHandler {
 public:
  WorkersDOMHandler() {}
  virtual ~WorkersDOMHandler() {}

 private:
  // WebUIMessageHandler implementation.
  virtual void RegisterMessages() OVERRIDE;

  // Callback for "openDevTools" message.
  void HandleOpenDevTools(const ListValue* args);
  void HandleTerminateWorker(const ListValue* args);

  DISALLOW_COPY_AND_ASSIGN(WorkersDOMHandler);
};

void WorkersDOMHandler::RegisterMessages() {
  web_ui_->RegisterMessageCallback(kOpenDevToolsCommand,
      base::Bind(&WorkersDOMHandler::HandleOpenDevTools,
                 base::Unretained(this)));
  web_ui_->RegisterMessageCallback(kTerminateWorkerCommand,
      base::Bind(&WorkersDOMHandler::HandleTerminateWorker,
                 base::Unretained(this)));
}

void WorkersDOMHandler::HandleOpenDevTools(const ListValue* args) {
  std::string worker_process_host_id_str;
  std::string worker_route_id_str;
  int worker_process_host_id;
  int worker_route_id;
  CHECK(args->GetSize() == 2);
  CHECK(args->GetString(0, &worker_process_host_id_str));
  CHECK(args->GetString(1, &worker_route_id_str));
  CHECK(base::StringToInt(worker_process_host_id_str,
                          &worker_process_host_id));
  CHECK(base::StringToInt(worker_route_id_str, &worker_route_id));

  Profile* profile = Profile::FromWebUI(web_ui_);
  if (!profile)
    return;
  DevToolsAgentHost* agent_host =
      DevToolsAgentHostRegistry::GetDevToolsAgentHostForWorker(
          worker_process_host_id,
          worker_route_id);
  DevToolsWindow::OpenDevToolsWindowForWorker(profile, agent_host);
}

static void TerminateWorker(int worker_process_id, int worker_route_id) {
  for (BrowserChildProcessHost::Iterator iter(content::PROCESS_TYPE_WORKER);
       !iter.Done(); ++iter) {
    if (iter->id() == worker_process_id) {
      WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter);
      worker->TerminateWorker(worker_route_id);
      return;
    }
  }
}

void WorkersDOMHandler::HandleTerminateWorker(const ListValue* args) {
  std::string worker_process_host_id_str;
  std::string worker_route_id_str;
  int worker_process_host_id;
  int worker_route_id;
  CHECK(args->GetSize() == 2);
  CHECK(args->GetString(0, &worker_process_host_id_str));
  CHECK(args->GetString(1, &worker_route_id_str));
  CHECK(base::StringToInt(worker_process_host_id_str,
                          &worker_process_host_id));
  CHECK(base::StringToInt(worker_route_id_str, &worker_route_id));

  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
      base::Bind(&TerminateWorker, worker_process_host_id, worker_route_id));
}

}  // namespace

class WorkersUI::WorkerCreationDestructionListener
    : public WorkerServiceObserver,
      public base::RefCountedThreadSafe<WorkerCreationDestructionListener> {
 public:
  explicit WorkerCreationDestructionListener(WorkersUI* workers_ui)
      : workers_ui_(workers_ui) {
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&WorkerCreationDestructionListener::RegisterObserver,
                   this));
  }

  void WorkersUIDestroyed() {
    workers_ui_ = NULL;
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&WorkerCreationDestructionListener::UnregisterObserver,
                   this));
  }

 private:
  friend class base::RefCountedThreadSafe<WorkerCreationDestructionListener>;
  virtual ~WorkerCreationDestructionListener() {
  }

  virtual void WorkerCreated(
      WorkerProcessHost* process,
      const WorkerProcessHost::WorkerInstance& instance) OVERRIDE {
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        base::Bind(&WorkerCreationDestructionListener::NotifyWorkerCreated,
                   this, base::Owned(BuildWorkerData(process, instance))));
  }
  virtual void WorkerDestroyed(
      WorkerProcessHost* process,
      int worker_route_id) OVERRIDE {
    DictionaryValue* worker_data = new DictionaryValue();
    worker_data->SetInteger(kWorkerProcessHostIdField, process->id());
    worker_data->SetInteger(kWorkerRouteIdField, worker_route_id);

    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        base::Bind(&WorkerCreationDestructionListener::NotifyWorkerDestroyed,
                   this, base::Owned(worker_data)));
  }
  virtual void WorkerContextStarted(WorkerProcessHost*, int) OVERRIDE {}

  void NotifyWorkerCreated(DictionaryValue* worker_data) {
    if (workers_ui_)
      workers_ui_->CallJavascriptFunction("workerCreated", *worker_data);
  }

  void NotifyWorkerDestroyed(DictionaryValue* worker_data) {
    if (workers_ui_)
      workers_ui_->CallJavascriptFunction("workerDestroyed", *worker_data);
  }

  void RegisterObserver() {
    WorkerService::GetInstance()->AddObserver(this);
  }
  void UnregisterObserver() {
    WorkerService::GetInstance()->RemoveObserver(this);
  }

  WorkersUI* workers_ui_;
};

WorkersUI::WorkersUI(TabContents* contents)
    : ChromeWebUI(contents),
      observer_(new WorkerCreationDestructionListener(this)){
  WorkersDOMHandler* handler = new WorkersDOMHandler();
  AddMessageHandler(handler);
  handler->Attach(this);

  WorkersUIHTMLSource* html_source = new WorkersUIHTMLSource();

  // Set up the chrome://workers/ source.
  Profile* profile = Profile::FromBrowserContext(contents->browser_context());
  profile->GetChromeURLDataManager()->AddDataSource(html_source);
}

WorkersUI::~WorkersUI() {
  observer_->WorkersUIDestroyed();
  observer_ = NULL;
}