// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <functional>
#include <map>
#include <mutex>
#include <thread>
#include <vector>

#include "common/polyfill_thread.h"
#include "common/scope_exit.h"
#include "common/thread.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_session.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/service_thread.h"

namespace Kernel {

class ServiceThread::Impl final {
public:
    explicit Impl(KernelCore& kernel, const std::string& service_name);
    ~Impl();

    void WaitAndProcessImpl();
    void SessionClosed(KServerSession* server_session,
                       std::shared_ptr<SessionRequestManager> manager);
    void LoopProcess();

    void RegisterServerSession(KServerSession* session,
                               std::shared_ptr<SessionRequestManager> manager);

private:
    KernelCore& kernel;
    const std::string m_service_name;

    std::jthread m_host_thread{};
    std::mutex m_session_mutex{};
    std::map<KServerSession*, std::shared_ptr<SessionRequestManager>> m_sessions{};
    KEvent* m_wakeup_event{};
    KThread* m_thread{};
    std::atomic<bool> m_shutdown_requested{};
};

void ServiceThread::Impl::WaitAndProcessImpl() {
    // Create local list of waitable sessions.
    std::vector<KSynchronizationObject*> objs;
    std::vector<std::shared_ptr<SessionRequestManager>> managers;

    {
        // Lock to get the set.
        std::scoped_lock lk{m_session_mutex};

        // Reserve the needed quantity.
        objs.reserve(m_sessions.size() + 1);
        managers.reserve(m_sessions.size());

        // Copy to our local list.
        for (const auto& [session, manager] : m_sessions) {
            objs.push_back(session);
            managers.push_back(manager);
        }

        // Insert the wakeup event at the end.
        objs.push_back(&m_wakeup_event->GetReadableEvent());
    }

    // Wait on the list of sessions.
    s32 index{-1};
    Result rc = KSynchronizationObject::Wait(kernel, &index, objs.data(),
                                             static_cast<s32>(objs.size()), -1);
    ASSERT(!rc.IsFailure());

    // If this was the wakeup event, clear it and finish.
    if (index >= static_cast<s64>(objs.size() - 1)) {
        m_wakeup_event->Clear();
        return;
    }

    // This event is from a server session.
    auto* server_session = static_cast<KServerSession*>(objs[index]);
    auto& manager = managers[index];

    // Fetch the HLE request context.
    std::shared_ptr<HLERequestContext> context;
    rc = server_session->ReceiveRequest(&context, manager);

    // If the session was closed, handle that.
    if (rc == ResultSessionClosed) {
        SessionClosed(server_session, manager);

        // Finish.
        return;
    }

    // TODO: handle other cases
    ASSERT(rc == ResultSuccess);

    // Perform the request.
    Result service_rc = manager->CompleteSyncRequest(server_session, *context);

    // Reply to the client.
    rc = server_session->SendReplyHLE();

    if (rc == ResultSessionClosed || service_rc == IPC::ERR_REMOTE_PROCESS_DEAD) {
        SessionClosed(server_session, manager);
        return;
    }

    // TODO: handle other cases
    ASSERT(rc == ResultSuccess);
    ASSERT(service_rc == ResultSuccess);
}

void ServiceThread::Impl::SessionClosed(KServerSession* server_session,
                                        std::shared_ptr<SessionRequestManager> manager) {
    {
        // Lock to get the set.
        std::scoped_lock lk{m_session_mutex};

        // Erase the session.
        ASSERT(m_sessions.erase(server_session) == 1);
    }

    // Close our reference to the server session.
    server_session->Close();
}

void ServiceThread::Impl::LoopProcess() {
    Common::SetCurrentThreadName(m_service_name.c_str());

    kernel.RegisterHostThread(m_thread);

    while (!m_shutdown_requested.load()) {
        WaitAndProcessImpl();
    }
}

void ServiceThread::Impl::RegisterServerSession(KServerSession* server_session,
                                                std::shared_ptr<SessionRequestManager> manager) {
    // Open the server session.
    server_session->Open();

    {
        // Lock to get the set.
        std::scoped_lock lk{m_session_mutex};

        // Insert the session and manager.
        m_sessions[server_session] = manager;
    }

    // Signal the wakeup event.
    m_wakeup_event->Signal();
}

ServiceThread::Impl::~Impl() {
    // Shut down the processing thread.
    m_shutdown_requested.store(true);
    m_wakeup_event->Signal();
    m_host_thread.join();

    // Close all remaining sessions.
    for (const auto& [server_session, manager] : m_sessions) {
        server_session->Close();
    }

    // Destroy remaining managers.
    m_sessions.clear();

    // Close event.
    m_wakeup_event->GetReadableEvent().Close();
    m_wakeup_event->Close();

    // Close thread.
    m_thread->Close();
}

ServiceThread::Impl::Impl(KernelCore& kernel_, const std::string& service_name)
    : kernel{kernel_}, m_service_name{service_name} {
    // Initialize event.
    m_wakeup_event = KEvent::Create(kernel);
    m_wakeup_event->Initialize(nullptr);

    // Initialize thread.
    m_thread = KThread::Create(kernel);
    ASSERT(KThread::InitializeDummyThread(m_thread, nullptr).IsSuccess());

    // Start thread.
    m_host_thread = std::jthread([this] { LoopProcess(); });
}

ServiceThread::ServiceThread(KernelCore& kernel, const std::string& name)
    : impl{std::make_unique<Impl>(kernel, name)} {}

ServiceThread::~ServiceThread() = default;

void ServiceThread::RegisterServerSession(KServerSession* session,
                                          std::shared_ptr<SessionRequestManager> manager) {
    impl->RegisterServerSession(session, manager);
}

} // namespace Kernel
