#include "adapters/windows/ModulePathCache.h"

#include <array>
#include <string>

namespace
{
    constexpr DWORD path_capacity = 32768;

    enum CacheState : LONG
    {
        NotCaptured = 0,
        Captured = 1,
        CaptureFailed = 2
    };

    std::array<wchar_t, path_capacity> g_path{};
    DWORD g_length = 0;
    DWORD g_error = ERROR_SUCCESS;
    volatile LONG g_state = NotCaptured;

    LONG state() noexcept
    {
        return InterlockedCompareExchange(&g_state, NotCaptured, NotCaptured);
    }
}

namespace ta::adapters::windows::module_path_cache
{
    bool capture(const HMODULE module) noexcept
    {
        const LONG current = state();
        if (current != NotCaptured)
            return current == Captured;

        if (!module)
        {
            g_error = ERROR_INVALID_HANDLE;
            InterlockedExchange(&g_state, CaptureFailed);
            return false;
        }

        SetLastError(ERROR_SUCCESS);
        const DWORD length = GetModuleFileNameW(
            module,
            g_path.data(),
            static_cast<DWORD>(g_path.size()));

        if (length == 0 || length >= g_path.size())
        {
            g_error = length == 0 ? GetLastError() : ERROR_INSUFFICIENT_BUFFER;
            g_length = 0;
            g_path[0] = L'\0';
            InterlockedExchange(&g_state, CaptureFailed);
            return false;
        }

        g_length = length;
        g_path[length] = L'\0';
        g_error = ERROR_SUCCESS;
        MemoryBarrier();
        InterlockedExchange(&g_state, Captured);
        return true;
    }

    bool available() noexcept
    {
        return state() == Captured;
    }

    DWORD error_code() noexcept
    {
        return g_error;
    }

    std::optional<std::filesystem::path> file_path()
    {
        if (!available() || g_length == 0)
            return std::nullopt;

        return std::filesystem::path(std::wstring(g_path.data(), g_length));
    }

    std::optional<std::filesystem::path> directory()
    {
        const auto path = file_path();
        if (!path)
            return std::nullopt;
        return path->parent_path();
    }
}
