// 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 "net/log/net_log.h"

#include "base/check_op.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
#include "net/log/net_log_values.h"

namespace net {

NetLog::ThreadSafeObserver::ThreadSafeObserver()
    : capture_mode_(NetLogCaptureMode::kDefault), net_log_(nullptr) {}

NetLog::ThreadSafeObserver::~ThreadSafeObserver() {
  // Make sure we aren't watching a NetLog on destruction.  Because the NetLog
  // may pass events to each observer on multiple threads, we cannot safely
  // stop watching a NetLog automatically from a parent class.
  DCHECK(!net_log_);
}

NetLogCaptureMode NetLog::ThreadSafeObserver::capture_mode() const {
  DCHECK(net_log_);
  return capture_mode_;
}

NetLog* NetLog::ThreadSafeObserver::net_log() const {
  return net_log_;
}

NetLog::ThreadSafeCaptureModeObserver::ThreadSafeCaptureModeObserver() =
    default;
NetLog::ThreadSafeCaptureModeObserver::~ThreadSafeCaptureModeObserver() =
    default;

NetLogCaptureModeSet
NetLog::ThreadSafeCaptureModeObserver::GetObserverCaptureModes() const {
  DCHECK(net_log_);
  return net_log_->GetObserverCaptureModes();
}

void NetLog::ThreadSafeCaptureModeObserver::
    AddEntryAtTimeWithMaterializedParams(NetLogEventType type,
                                         const NetLogSource& source,
                                         NetLogEventPhase phase,
                                         base::TimeTicks time,
                                         base::Value&& params) {
  DCHECK(net_log_);
  net_log_->AddEntryAtTimeWithMaterializedParams(type, source, phase, time,
                                                 std::move(params));
}

// static
NetLog* NetLog::Get() {
  static base::NoDestructor<NetLog> instance{util::PassKey<NetLog>()};
  return instance.get();
}

NetLog::NetLog(util::PassKey<NetLog>) {}
NetLog::NetLog(util::PassKey<NetLogWithSource>) {}
NetLog::NetLog(util::PassKey<TestNetLog>) {}

NetLog::~NetLog() = default;

void NetLog::AddEntry(NetLogEventType type,
                      const NetLogSource& source,
                      NetLogEventPhase phase) {
  AddEntry(type, source, phase, [] { return base::Value(); });
}

void NetLog::AddGlobalEntry(NetLogEventType type) {
  AddEntry(type, NetLogSource(NetLogSourceType::NONE, NextID()),
           NetLogEventPhase::NONE);
}

void NetLog::AddGlobalEntryWithStringParams(NetLogEventType type,
                                            base::StringPiece name,
                                            base::StringPiece value) {
  AddGlobalEntry(type, [&] { return NetLogParamsWithString(name, value); });
}

uint32_t NetLog::NextID() {
  return base::subtle::NoBarrier_AtomicIncrement(&last_id_, 1);
}

void NetLog::AddObserver(NetLog::ThreadSafeObserver* observer,
                         NetLogCaptureMode capture_mode) {
  base::AutoLock lock(lock_);

  DCHECK(!observer->net_log_);
  DCHECK(!HasObserver(observer));
  DCHECK_LT(observers_.size(), 20u);  // Performance sanity check.

  observers_.push_back(observer);

  observer->net_log_ = this;
  observer->capture_mode_ = capture_mode;
  UpdateObserverCaptureModes();
}

void NetLog::RemoveObserver(NetLog::ThreadSafeObserver* observer) {
  base::AutoLock lock(lock_);

  DCHECK_EQ(this, observer->net_log_);

  auto it = std::find(observers_.begin(), observers_.end(), observer);
  DCHECK(it != observers_.end());
  observers_.erase(it);

  observer->net_log_ = nullptr;
  observer->capture_mode_ = NetLogCaptureMode::kDefault;
  UpdateObserverCaptureModes();
}

void NetLog::AddCaptureModeObserver(
    NetLog::ThreadSafeCaptureModeObserver* observer) {
  base::AutoLock lock(lock_);

  DCHECK(!observer->net_log_);
  DCHECK(!HasCaptureModeObserver(observer));
  DCHECK_LT(capture_mode_observers_.size(), 20u);  // Performance sanity check.

  observer->net_log_ = this;
  capture_mode_observers_.push_back(observer);
}

void NetLog::RemoveCaptureModeObserver(
    NetLog::ThreadSafeCaptureModeObserver* observer) {
  base::AutoLock lock(lock_);

  DCHECK_EQ(this, observer->net_log_);
  DCHECK(HasCaptureModeObserver(observer));

  auto it = std::find(capture_mode_observers_.begin(),
                      capture_mode_observers_.end(), observer);
  DCHECK(it != capture_mode_observers_.end());
  capture_mode_observers_.erase(it);

  observer->net_log_ = nullptr;
}

void NetLog::UpdateObserverCaptureModes() {
  lock_.AssertAcquired();

  NetLogCaptureModeSet capture_mode_set = 0;
  for (const auto* observer : observers_)
    NetLogCaptureModeSetAdd(observer->capture_mode_, &capture_mode_set);

  base::subtle::NoBarrier_Store(&observer_capture_modes_, capture_mode_set);

  // Notify any capture mode observers with the new |capture_mode_set|.
  for (auto* capture_mode_observer : capture_mode_observers_)
    capture_mode_observer->OnCaptureModeUpdated(capture_mode_set);
}

bool NetLog::HasObserver(ThreadSafeObserver* observer) {
  lock_.AssertAcquired();
  return base::Contains(observers_, observer);
}

bool NetLog::HasCaptureModeObserver(ThreadSafeCaptureModeObserver* observer) {
  lock_.AssertAcquired();
  return base::Contains(capture_mode_observers_, observer);
}

// static
std::string NetLog::TickCountToString(const base::TimeTicks& time) {
  int64_t delta_time = time.since_origin().InMilliseconds();
  // TODO(https://crbug.com/915391): Use NetLogNumberValue().
  return base::NumberToString(delta_time);
}

// static
std::string NetLog::TimeToString(const base::Time& time) {
  // Convert the base::Time to its (approximate) equivalent in base::TimeTicks.
  base::TimeTicks time_ticks =
      base::TimeTicks::UnixEpoch() + (time - base::Time::UnixEpoch());
  return TickCountToString(time_ticks);
}

// static
const char* NetLog::EventTypeToString(NetLogEventType event) {
  switch (event) {
#define EVENT_TYPE(label)      \
  case NetLogEventType::label: \
    return #label;
#include "net/log/net_log_event_type_list.h"
#undef EVENT_TYPE
    default:
      NOTREACHED();
      return nullptr;
  }
}

// static
base::Value NetLog::GetEventTypesAsValue() {
  base::Value dict(base::Value::Type::DICTIONARY);
  for (int i = 0; i < static_cast<int>(NetLogEventType::COUNT); ++i) {
    dict.SetIntKey(EventTypeToString(static_cast<NetLogEventType>(i)), i);
  }
  return dict;
}

// static
const char* NetLog::SourceTypeToString(NetLogSourceType source) {
  switch (source) {
#define SOURCE_TYPE(label)      \
  case NetLogSourceType::label: \
    return #label;
#include "net/log/net_log_source_type_list.h"
#undef SOURCE_TYPE
    default:
      NOTREACHED();
      return nullptr;
  }
}

// static
base::Value NetLog::GetSourceTypesAsValue() {
  base::Value dict(base::Value::Type::DICTIONARY);
  for (int i = 0; i < static_cast<int>(NetLogSourceType::COUNT); ++i) {
    dict.SetIntKey(SourceTypeToString(static_cast<NetLogSourceType>(i)), i);
  }
  return dict;
}

// static
const char* NetLog::EventPhaseToString(NetLogEventPhase phase) {
  switch (phase) {
    case NetLogEventPhase::BEGIN:
      return "PHASE_BEGIN";
    case NetLogEventPhase::END:
      return "PHASE_END";
    case NetLogEventPhase::NONE:
      return "PHASE_NONE";
  }
  NOTREACHED();
  return nullptr;
}

void NetLog::InitializeSourceIdPartition() {
  int32_t old_value = base::subtle::NoBarrier_AtomicExchange(
      &last_id_, std::numeric_limits<base::subtle::Atomic32>::min());
  DCHECK_EQ(old_value, 0) << " NetLog::InitializeSourceIdPartition() called "
                             "after NextID() or called multiple times";
}

void NetLog::AddEntryInternal(NetLogEventType type,
                              const NetLogSource& source,
                              NetLogEventPhase phase,
                              const GetParamsInterface* get_params) {
  NetLogCaptureModeSet observer_capture_modes = GetObserverCaptureModes();

  for (int i = 0; i <= static_cast<int>(NetLogCaptureMode::kLast); ++i) {
    NetLogCaptureMode capture_mode = static_cast<NetLogCaptureMode>(i);
    if (!NetLogCaptureModeSetContains(capture_mode, observer_capture_modes))
      continue;

    NetLogEntry entry(type, source, phase, base::TimeTicks::Now(),
                      get_params->GetParams(capture_mode));

    // Notify all of the log observers with |capture_mode|.
    base::AutoLock lock(lock_);
    for (auto* observer : observers_) {
      if (observer->capture_mode() == capture_mode)
        observer->OnAddEntry(entry);
    }
  }
}

void NetLog::AddEntryWithMaterializedParams(NetLogEventType type,
                                            const NetLogSource& source,
                                            NetLogEventPhase phase,
                                            base::Value&& params) {
  AddEntryAtTimeWithMaterializedParams(
      type, source, phase, base::TimeTicks::Now(), std::move(params));
}

void NetLog::AddEntryAtTimeWithMaterializedParams(NetLogEventType type,
                                                  const NetLogSource& source,
                                                  NetLogEventPhase phase,
                                                  base::TimeTicks time,
                                                  base::Value&& params) {
  NetLogEntry entry(type, source, phase, time, std::move(params));

  // Notify all of the log observers, regardless of capture mode.
  base::AutoLock lock(lock_);
  for (auto* observer : observers_) {
    observer->OnAddEntry(entry);
  }
}

}  // namespace net
