/*
 * Copyright (c) 2021, Andreas Kling <andreas@ladybird.org>
 * Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech>
 * Copyright (c) 2023-2024, Sam Atkins <atkinssj@serenityos.org>
 * Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
 * Copyright (c) 2024, stasoid <stasoid@yahoo.com>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/Assertions.h>
#include <AK/ScopeGuard.h>
#include <AK/String.h>
#include <AK/Utf16View.h>
#include <AK/Vector.h>
#include <AK/Windows.h>
#include <LibCore/Process.h>

namespace Core {

Process::Process(Process&& other)
    : m_handle(exchange(other.m_handle, nullptr))
{
}

Process& Process::operator=(Process&& other)
{
    m_handle = exchange(other.m_handle, nullptr);
    return *this;
}

Process::~Process()
{
    if (m_handle)
        CloseHandle(m_handle);
}

Process Process::current()
{
    return GetCurrentProcess();
}

ErrorOr<Process> Process::spawn(ProcessSpawnOptions const& options)
{
    // file actions are not supported
    VERIFY(options.file_actions.is_empty());

    StringBuilder builder;
    if (!options.search_for_executable_in_path && !options.executable.find_any_of("\\/:"sv).has_value())
        builder.appendff("\"./{}\" ", options.executable);
    else
        builder.appendff("\"{}\" ", options.executable);

    for (auto arg : options.arguments)
        builder.appendff("\"{}\" ", arg);

    builder.append('\0');
    ByteBuffer command_line = TRY(builder.to_byte_buffer());

    STARTUPINFO startup_info = {};
    PROCESS_INFORMATION process_info = {};

    BOOL result = CreateProcess(
        NULL,
        (char*)command_line.data(),
        NULL, // process security attributes
        NULL, // primary thread security attributes
        TRUE, // handles are inherited
        0,    // creation flags
        NULL, // use parent's environment
        NULL, // working directory
        &startup_info,
        &process_info);

    if (!result)
        return Error::from_windows_error();

    CloseHandle(process_info.hThread);

    return Process(process_info.hProcess);
}

ErrorOr<Process> Process::spawn(StringView path, ReadonlySpan<ByteString> arguments)
{
    return spawn({
        .executable = path,
        .arguments = Vector<ByteString> { arguments },
    });
}

ErrorOr<Process> Process::spawn(StringView path, ReadonlySpan<StringView> arguments)
{
    Vector<ByteString> backing_strings;
    backing_strings.ensure_capacity(arguments.size());
    for (auto argument : arguments)
        backing_strings.append(argument);

    return spawn({
        .executable = path,
        .arguments = backing_strings,
    });
}

void Process::terminate_immediately(int status)
{
    // TerminateProcess() is stronger than the CRT's _exit(), since it skips
    // DLL detach notifications and the other ExitProcess() teardown paths.
    TerminateProcess(GetCurrentProcess(), static_cast<UINT>(status));
    VERIFY_NOT_REACHED();
}

ErrorOr<void> Process::terminate_process(pid_t pid, TerminationMode mode)
{
    if (mode == TerminationMode::Graceful) {
        // There is no SIGTERM equivalent on Windows; the closest honest approximation is
        // asking every top-level window of the process to close. Console or windowless
        // processes will not observe this.
        if (!EnumWindows([](HWND hwnd, LPARAM l_param) -> BOOL {
                DWORD window_pid = 0;
                GetWindowThreadProcessId(hwnd, &window_pid);
                if (window_pid == static_cast<DWORD>(l_param))
                    PostMessage(hwnd, WM_CLOSE, 0, 0);
                return TRUE;
            },
                pid))
            return Error::from_windows_error();
        return {};
    }

    HANDLE handle = OpenProcess(PROCESS_TERMINATE, FALSE, static_cast<DWORD>(pid));
    if (!handle)
        return Error::from_windows_error();
    ScopeGuard close_handle = [&] { CloseHandle(handle); };
    if (!TerminateProcess(handle, 1))
        return Error::from_windows_error();
    return {};
}

// Get the full path of the executable file of the current process
ErrorOr<String> Process::get_name()
{
    Vector<wchar_t, MAX_PATH> path;
    path.resize(MAX_PATH);

    DWORD length = GetModuleFileNameW(NULL, path.data(), MAX_PATH);

    if (length == path.size() && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
        path.resize(UNICODE_STRING_MAX_CHARS);
        length = GetModuleFileNameW(NULL, path.data(), UNICODE_STRING_MAX_CHARS);
    }

    if (!length)
        return Error::from_windows_error();

    return MUST(Utf16View { reinterpret_cast<char16_t const*>(path.data()), length }.to_utf8());
}

ErrorOr<bool> Process::is_being_debugged()
{
    return IsDebuggerPresent();
}

// Forces the process to sleep until a debugger is attached, then breaks.
void Process::wait_for_debugger_and_break()
{
    bool print_message = true;
    for (;;) {
        if (IsDebuggerPresent()) {
            DebugBreak();
            return;
        }
        if (print_message) {
            dbgln("Process {} with pid {} is sleeping, waiting for debugger.", Process::get_name(), GetCurrentProcessId());
            print_message = false;
        }
        Sleep(100);
    }
}

pid_t Process::pid() const
{
    return GetProcessId(m_handle);
}

ErrorOr<int> Process::wait_for_termination() const
{
    auto result = WaitForSingleObject(m_handle, INFINITE);
    if (result == WAIT_FAILED)
        return Error::from_windows_error();

    DWORD exit_code = 0;
    if (!GetExitCodeProcess(m_handle, &exit_code))
        return Error::from_windows_error();

    return exit_code;
}

}
