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

#include "common/scope_exit.h"

#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/svc_types.h"
#include "core/hle/service/os/process.h"
#include "core/loader/loader.h"

namespace Service {

Process::Process(Core::System& system)
    : m_system(system), m_process(), m_main_thread_priority(), m_main_thread_stack_size(),
      m_process_started() {}

Process::~Process() {
    this->Finalize();
}

bool Process::Initialize(Loader::AppLoader& loader, Loader::ResultStatus& out_load_result) {
    // First, ensure we are not holding another process.
    this->Finalize();

    // Create the process.
    auto* const process = Kernel::KProcess::Create(m_system.Kernel());
    Kernel::KProcess::Register(m_system.Kernel(), process);

    // On exit, ensure we free the additional reference to the process.
    SCOPE_EXIT {
        process->Close();
    };

    // Insert process modules into memory.
    const auto [load_result, load_parameters] = loader.Load(*process, m_system);
    out_load_result = load_result;

    // Ensure loading was successful.
    if (load_result != Loader::ResultStatus::Success) {
        return false;
    }

    // TODO: remove this, kernel already tracks this
    m_system.Kernel().AppendNewProcess(process);

    // Note the load parameters from NPDM.
    m_main_thread_priority = load_parameters->main_thread_priority;
    m_main_thread_stack_size = load_parameters->main_thread_stack_size;

    // This process has not started yet.
    m_process_started = false;

    // Take ownership of the process object.
    m_process = process;
    m_process->Open();

    // We succeeded.
    return true;
}

void Process::Finalize() {
    // Terminate, if we are currently holding a process.
    this->Terminate();

    // Close the process.
    if (m_process) {
        m_process->Close();

        // TODO: remove this, kernel already tracks this
        m_system.Kernel().RemoveProcess(m_process);
    }

    // Clean up.
    m_process = nullptr;
    m_main_thread_priority = 0;
    m_main_thread_stack_size = 0;
    m_process_started = false;
}

bool Process::Run() {
    // If we already started the process, don't start again.
    if (m_process_started) {
        return false;
    }

    // Start.
    if (m_process) {
        m_process->Run(m_main_thread_priority, m_main_thread_stack_size);
    }

    // Mark as started.
    m_process_started = true;

    // We succeeded.
    return true;
}

void Process::Terminate() {
    if (m_process) {
        m_process->Terminate();
    }
}

void Process::ResetSignal() {
    if (m_process) {
        m_process->Reset();
    }
}

bool Process::IsRunning() const {
    if (m_process) {
        const auto state = m_process->GetState();
        return state == Kernel::KProcess::State::Running ||
               state == Kernel::KProcess::State::RunningAttached ||
               state == Kernel::KProcess::State::DebugBreak;
    }

    return false;
}

bool Process::IsTerminated() const {
    if (m_process) {
        return m_process->IsTerminated();
    }

    return false;
}

u64 Process::GetProcessId() const {
    if (m_process) {
        return m_process->GetProcessId();
    }

    return 0;
}

u64 Process::GetProgramId() const {
    if (m_process) {
        return m_process->GetProgramId();
    }

    return 0;
}

void Process::Suspend(bool suspended) {
    if (m_process) {
        m_process->SetActivity(suspended ? Kernel::Svc::ProcessActivity::Paused
                                         : Kernel::Svc::ProcessActivity::Runnable);
    }
}

} // namespace Service