// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/viz/service/gl/gpu_log_message_manager.h"

#include "base/check.h"
#include "base/logging.h"
#include "base/process/process.h"
#include "base/synchronization/waitable_event.h"

namespace viz {

namespace {
bool PreInitializeLogHandler(int severity,
                             const char* file,
                             int line,
                             size_t message_start,
                             const std::string& message) {
  GpuLogMessageManager::GetInstance()->AddDeferredMessage(
      severity, message.substr(0, message_start),
      message.substr(message_start));
  return false;
}

bool PostInitializeLogHandler(int severity,
                              const char* file,
                              int line,
                              size_t message_start,
                              const std::string& message) {
  GpuLogMessageManager::GetInstance()->RouteMessage(
      severity, message.substr(0, message_start),
      message.substr(message_start));
  return false;
}
}  // namespace

GpuLogMessageManager* GpuLogMessageManager::GetInstance() {
  static base::NoDestructor<GpuLogMessageManager> message_manager;
  return message_manager.get();
}

GpuLogMessageManager::GpuLogMessageManager() = default;

void GpuLogMessageManager::AddDeferredMessage(int severity,
                                              const std::string& header,
                                              const std::string& message) {
  base::AutoLock lock(message_lock_);
  deferred_messages_.emplace_back(severity, header, message);
}

void GpuLogMessageManager::RouteMessage(int severity,
                                        const std::string& header,
                                        const std::string& message) {
  // This can be run from any thread, but mojo messages are sent on the IO
  // thread.
  if (io_task_runner_->BelongsToCurrentThread()) {
    if (!gpu_logging_.is_bound()) {
      // |InstallPostInitializeLogHandler| set a new log message handler, which
      // will call |RouteMessage|. When |RouteMessage| handling log message
      // GPULogging may not yet be bound. Cache those log messages until the
      // Remote is bound.
      base::AutoLock lock(message_lock_);
      deferred_messages_.emplace_back(severity, header, message);
      return;
    }

    gpu_logging_->RecordLogMessage(severity, header, message);
  } else {
    // Unretained is safe because |this| is valid until the process exits.
    io_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&GpuLogMessageManager::RouteMessage,
                                  base::Unretained(this), severity,
                                  std::move(header), std::move(message)));
  }
}

void GpuLogMessageManager::FlushMessages(mojom::GpuLogging* gpu_logging) {
  base::AutoLock lock(message_lock_);
  for (auto& log : deferred_messages_) {
    gpu_logging->RecordLogMessage(log.severity, std::move(log.header),
                                  std::move(log.message));
  }
  deferred_messages_.clear();
}

void GpuLogMessageManager::InstallPreInitializeLogHandler() {
  logging::SetLogMessageHandler(PreInitializeLogHandler);
}

void GpuLogMessageManager::InstallPostInitializeLogHandler(
    mojo::PendingRemote<mojom::GpuLogging> pending_remote,
    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) {
  Bind(std::move(pending_remote), std::move(io_task_runner));

  logging::SetLogMessageHandler(PostInitializeLogHandler);
}

void GpuLogMessageManager::ShutdownLogging() {
  logging::SetLogMessageHandler(nullptr);

  // |io_task_runner_| may be null if GPULogMessageManager hasn't been bound.
  // Destroy the remote on the IO thread.
  // base::Unretained is safe because this is a singleton.
  if (io_task_runner_) {
    io_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&GpuLogMessageManager::ResetLoggingOnIOThread,
                                  base::Unretained(this)));
  }
}

void GpuLogMessageManager::Bind(
    mojo::PendingRemote<mojom::GpuLogging> pending_remote,
    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) {
  DCHECK(!io_task_runner->BelongsToCurrentThread());
  io_task_runner_ = std::move(io_task_runner);

  // base::Unretained is safe because this is a singleton.
  io_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&GpuLogMessageManager::BindOnIOThread,
                     base::Unretained(this), std::move(pending_remote)));
}

void GpuLogMessageManager::BindOnIOThread(
    mojo::PendingRemote<mojom::GpuLogging> pending_remote) {
  DCHECK(io_task_runner_->BelongsToCurrentThread());
  DCHECK(!gpu_logging_.is_bound());
  gpu_logging_.Bind(std::move(pending_remote));

  FlushMessages(gpu_logging_.get());
}

void GpuLogMessageManager::ResetLoggingOnIOThread() {
  DCHECK(io_task_runner_->BelongsToCurrentThread());
  gpu_logging_.reset();
}

void GpuLogMessageManager::TerminateProcess(int exit_code) {
  // Block the calling thread so it doesn't execute any more code before the
  // process exits. This function cannot be called from the IO thread.
  CHECK(!io_task_runner_->BelongsToCurrentThread());
  base::WaitableEvent wait;

  // base::Unretained is safe because this is a singleton.
  io_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&GpuLogMessageManager::TerminateProcessOnIO,
                                base::Unretained(this), exit_code));

  wait.Wait();
}

void GpuLogMessageManager::TerminateProcessOnIO(int exit_code) {
  DCHECK(io_task_runner_->BelongsToCurrentThread());
  base::Process::TerminateCurrentProcessImmediately(exit_code);
}

}  // namespace viz
