// Copyright (c) 2012 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 "content/browser/media/media_internals_proxy.h"

#include <stddef.h>

#include <utility>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/browser/media/media_internals_handler.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_ui.h"
#include "net/log/net_log_capture_mode.h"
#include "net/log/net_log_entry.h"
#include "net/log/net_log_event_type.h"

namespace content {

static const int kMediaInternalsProxyEventDelayMilliseconds = 100;

static const net::NetLogEventType kNetEventTypeFilter[] = {
    net::NetLogEventType::DISK_CACHE_ENTRY_IMPL,
    net::NetLogEventType::SPARSE_READ,
    net::NetLogEventType::SPARSE_WRITE,
    net::NetLogEventType::URL_REQUEST_START_JOB,
    net::NetLogEventType::HTTP_TRANSACTION_READ_RESPONSE_HEADERS,
};

MediaInternalsProxy::MediaInternalsProxy() {
  registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED,
                 NotificationService::AllBrowserContextsAndSources());
}

void MediaInternalsProxy::Observe(int type,
                                  const NotificationSource& source,
                                  const NotificationDetails& details) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK_EQ(type, NOTIFICATION_RENDERER_PROCESS_TERMINATED);
  RenderProcessHost* process = Source<RenderProcessHost>(source).ptr();
  CallJavaScriptFunctionOnUIThread("media.onRendererTerminated",
      new base::FundamentalValue(process->GetID()));
}

void MediaInternalsProxy::Attach(MediaInternalsMessageHandler* handler) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  handler_ = handler;
  update_callback_ = base::Bind(&MediaInternalsProxy::UpdateUIOnUIThread, this);
  MediaInternals::GetInstance()->AddUpdateCallback(update_callback_);

  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      base::Bind(&MediaInternalsProxy::ObserveMediaInternalsOnIOThread, this));
}

void MediaInternalsProxy::Detach() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  handler_ = NULL;
  MediaInternals::GetInstance()->RemoveUpdateCallback(update_callback_);

  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      base::Bind(
          &MediaInternalsProxy::StopObservingMediaInternalsOnIOThread, this));
}

void MediaInternalsProxy::GetEverything() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  MediaInternals::GetInstance()->SendHistoricalMediaEvents();

  // Ask MediaInternals for its data on IO thread.
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      base::Bind(&MediaInternalsProxy::GetEverythingOnIOThread, this));

  // Send the page names for constants.
  CallJavaScriptFunctionOnUIThread("media.onReceiveConstants", GetConstants());
}

void MediaInternalsProxy::OnAddEntry(const net::NetLogEntry& entry) {
  bool is_event_interesting = false;
  for (size_t i = 0; i < arraysize(kNetEventTypeFilter); i++) {
    if (entry.type() == kNetEventTypeFilter[i]) {
      is_event_interesting = true;
      break;
    }
  }

  if (!is_event_interesting)
    return;

  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&MediaInternalsProxy::AddNetEventOnUIThread, this,
                 base::Passed(entry.ToValue())));
}

MediaInternalsProxy::~MediaInternalsProxy() {}

base::Value* MediaInternalsProxy::GetConstants() {
  base::DictionaryValue* event_phases = new base::DictionaryValue();
  event_phases->SetInteger(
      net::NetLog::EventPhaseToString(net::NetLogEventPhase::NONE),
      static_cast<int>(net::NetLogEventPhase::NONE));
  event_phases->SetInteger(
      net::NetLog::EventPhaseToString(net::NetLogEventPhase::BEGIN),
      static_cast<int>(net::NetLogEventPhase::BEGIN));
  event_phases->SetInteger(
      net::NetLog::EventPhaseToString(net::NetLogEventPhase::END),
      static_cast<int>(net::NetLogEventPhase::END));

  base::DictionaryValue* constants = new base::DictionaryValue();
  constants->Set("eventTypes", net::NetLog::GetEventTypesAsValue());
  constants->Set("eventPhases", event_phases);

  return constants;
}

void MediaInternalsProxy::ObserveMediaInternalsOnIOThread() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (GetContentClient()->browser()->GetNetLog()) {
    net::NetLog* net_log = GetContentClient()->browser()->GetNetLog();
    net_log->DeprecatedAddObserver(
        this, net::NetLogCaptureMode::IncludeCookiesAndCredentials());
  }
}

void MediaInternalsProxy::StopObservingMediaInternalsOnIOThread() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (GetContentClient()->browser()->GetNetLog()) {
    net::NetLog* net_log = GetContentClient()->browser()->GetNetLog();
    net_log->DeprecatedRemoveObserver(this);
  }
}

void MediaInternalsProxy::GetEverythingOnIOThread() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  // TODO(xhwang): Investigate whether we can update on UI thread directly.
  MediaInternals::GetInstance()->SendAudioStreamData();
  MediaInternals::GetInstance()->SendVideoCaptureDeviceCapabilities();
}

void MediaInternalsProxy::UpdateUIOnUIThread(const base::string16& update) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  // Don't forward updates to a destructed UI.
  if (handler_)
    handler_->OnUpdate(update);
}

void MediaInternalsProxy::AddNetEventOnUIThread(
    std::unique_ptr<base::Value> entry) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // Send the updates to the page in kMediaInternalsProxyEventDelayMilliseconds
  // if an update is not already pending.
  if (!pending_net_updates_) {
    pending_net_updates_.reset(new base::ListValue());
    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
        FROM_HERE,
        base::Bind(&MediaInternalsProxy::SendNetEventsOnUIThread, this),
        base::TimeDelta::FromMilliseconds(
            kMediaInternalsProxyEventDelayMilliseconds));
  }
  pending_net_updates_->Append(std::move(entry));
}

void MediaInternalsProxy::SendNetEventsOnUIThread() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  CallJavaScriptFunctionOnUIThread("media.onNetUpdate",
                                   pending_net_updates_.release());
}

void MediaInternalsProxy::CallJavaScriptFunctionOnUIThread(
    const std::string& function, base::Value* args) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  std::unique_ptr<base::Value> args_value(args);
  std::vector<const base::Value*> args_vector;
  args_vector.push_back(args_value.get());
  base::string16 update = WebUI::GetJavascriptCall(function, args_vector);
  UpdateUIOnUIThread(update);
}

}  // namespace content
