/*
 * Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/String.h>
#include <LibCore/EventLoop.h>
#include <LibCore/Process.h>
#include <LibWebView/ProcessManager.h>

namespace WebView {

ProcessType process_type_from_name(StringView name)
{
    if (name == "Browser"sv)
        return ProcessType::Browser;
    if (name == "Compositor"sv)
        return ProcessType::Compositor;
    if (name == "WebContent"sv)
        return ProcessType::WebContent;
    if (name == "WebWorker"sv)
        return ProcessType::WebWorker;
    if (name == "RequestServer"sv)
        return ProcessType::RequestServer;
    if (name == "ImageDecoder"sv)
        return ProcessType::ImageDecoder;

    dbgln("Unknown process type: '{}'", name);
    VERIFY_NOT_REACHED();
}

StringView process_name_from_type(ProcessType type)
{
    switch (type) {
    case ProcessType::Browser:
        return "Browser"sv;
    case ProcessType::Compositor:
        return "Compositor"sv;
    case ProcessType::WebContent:
        return "WebContent"sv;
    case ProcessType::WebWorker:
        return "WebWorker"sv;
    case ProcessType::RequestServer:
        return "RequestServer"sv;
    case ProcessType::ImageDecoder:
        return "ImageDecoder"sv;
    }
    VERIFY_NOT_REACHED();
}

ProcessManager::ProcessManager()
    : on_process_added([](Process&) {})
    , on_process_exited([](Process&&, Optional<int>) {})
    , m_process_monitor(ProcessMonitor([this](pid_t pid, Optional<int> exit_status) {
        if (auto process = remove_process(pid); process.has_value())
            on_process_exited(process.release_value(), exit_status);
    }))
{
    add_process(Process(WebView::ProcessType::Browser, nullptr, Core::Process::current()));

#ifdef AK_OS_MACH
    auto self_send_port = mach_task_self();
    auto res = mach_port_mod_refs(mach_task_self(), self_send_port, MACH_PORT_RIGHT_SEND, +1);
    VERIFY(res == KERN_SUCCESS);
    set_process_mach_port(getpid(), Core::MachPort::adopt_right(self_send_port, Core::MachPort::PortRight::Send));
#endif
}

Optional<Process&> ProcessManager::find_process(pid_t pid)
{
    verify_event_loop();
    return m_processes.get(pid);
}

void ProcessManager::add_process(WebView::Process&& process)
{
    verify_event_loop();
    auto pid = process.pid();
    on_process_added(process);
    auto result = m_processes.set(pid, move(process));
    VERIFY(result == AK::HashSetResult::InsertedNewEntry);
    m_statistics.processes.append(make<Core::Platform::ProcessInfo>(pid));
    m_process_monitor.add_process(pid);
}

void ProcessManager::for_each_process(Function<void(Process&)> callback)
{
    verify_event_loop();
    for (auto& entry : m_processes)
        callback(entry.value);
}

void ProcessManager::for_each_process_statistics(Function<void(Process&, Core::Platform::ProcessInfo const&)> callback)
{
    verify_event_loop();
    m_statistics.for_each_process([&](auto const& process) {
        auto process_handle = m_processes.get(process.pid);
        if (!process_handle.has_value())
            return;

        callback(*process_handle, process);
    });
}

#if defined(AK_OS_MACH)
void ProcessManager::set_process_mach_port(pid_t pid, Core::MachPort&& port)
{
    verify_event_loop();
    for (auto const& info : m_statistics.processes) {
        if (info->pid == pid) {
            info->child_task_port = move(port);
            return;
        }
    }
}
#endif

Optional<Process> ProcessManager::remove_process(pid_t pid)
{
    verify_event_loop();
    cancel_forced_exit(pid);
    m_statistics.processes.remove_first_matching([&](auto const& info) {
        return (info->pid == pid);
    });
    return m_processes.take(pid);
}

void ProcessManager::cancel_forced_exit(pid_t pid)
{
    verify_event_loop();
    if (auto timer = m_forced_exit_timers.take(pid); timer.has_value())
        (*timer)->stop();
}

void ProcessManager::force_exit_after_timeout(pid_t pid, int timeout_ms)
{
    verify_event_loop();
    if (!m_processes.contains(pid))
        return;
    if (m_forced_exit_timers.contains(pid))
        return;

    auto timer = Core::Timer::create_single_shot(timeout_ms, [this, pid] {
        m_forced_exit_timers.remove(pid);
        auto process = m_processes.get(pid);
        if (!process.has_value())
            return;

        dbgln("Force-killing unresponsive {} process {}", process_name_from_type(process->type()), pid);
        auto result = Core::Process::terminate_process(pid, Core::Process::TerminationMode::Forceful);
        if (result.is_error())
            dbgln("Failed to force-kill process {}: {}", pid, result.error());
    });
    timer->start();
    m_forced_exit_timers.set(pid, move(timer));
}

void ProcessManager::update_all_process_statistics()
{
    verify_event_loop();
    (void)update_process_statistics(m_statistics);
}

void ProcessManager::verify_event_loop() const
{
    if (Core::EventLoop::is_running())
        VERIFY(&Core::EventLoop::current() == m_creation_event_loop);
}

}
