#pragma once
#include <string>
#include <vector>
#include <cstdint>
#include <memory>
#include <shared_mutex>
#include <type_traits>
#include <cstring>      // memcpy

// Windows must come before vmmdll / VolkDMA headers.
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h>

// VolkDMA wrapper classes (DMA handles FPGA init, Process handles per-process reads)
#include <VolkDMA/dma.hh>
#include <VolkDMA/process.hh>

#include "MemoryValidation.h"

// -------------------------------------------------------------------------
// VmmAccessor
// Thin wrapper around VolkDMA's DMA + Process classes providing the same
// read-oriented API used throughout this project.
// -------------------------------------------------------------------------

class VmmAccessor {
public:
    VmmAccessor() = default;
    ~VmmAccessor() = default;

    // Non-copyable, movable
    VmmAccessor(const VmmAccessor&)            = delete;
    VmmAccessor& operator=(const VmmAccessor&) = delete;
    VmmAccessor(VmmAccessor&&)                 = default;
    VmmAccessor& operator=(VmmAccessor&&)      = default;

    // ------------------------------------------------------------------
    // Lifecycle
    // ------------------------------------------------------------------

    /// Initialise the DMA handle.
    /// @param useMemoryMap  When true, VolkDMA auto-generates / loads memory_map.txt
    ///                      for faster physical-memory resolution (recommended).
    bool Initialize(bool useMemoryMap = true);

    /// Find a running process by name, store its PID, and create the
    /// internal Process object.  Must be called before any reads.
    bool TryFindProcess(const std::string& processName, uint32_t& outPid);

    /// Get the base virtual address of a named module inside the attached process.
    bool TryGetModuleBase(uint32_t           pid,
                          const std::string& moduleName,
                          uint64_t&          outBase);

    /// Returns true when the process is still alive (re-queries the DMA layer).
    bool IsProcessAlive(uint32_t pid, const std::string& processName);

    /// Force a full refresh of VMMDLL's internal caches.
    void ForceRefresh();

    // ------------------------------------------------------------------
    // Memory reads
    // ------------------------------------------------------------------

    /// Read a 64-bit pointer.  Validates both the source and result address.
    bool TryReadPointer(uint32_t pid, uint64_t address, uint64_t& outPointer);

    /// Read a plain-old-data value of type T.
    template<typename T>
    bool TryReadValue(uint32_t pid, uint64_t address, T& outValue);

    /// Read raw bytes into a caller-supplied vector.
    bool ReadBytes(uint32_t              pid,
                   uint64_t              address,
                   size_t                count,
                   std::vector<uint8_t>& outBytes);

    // ------------------------------------------------------------------
    // Scatter reads — batch multiple reads into one DMA round-trip.
    // ------------------------------------------------------------------

    struct ScatterEntry {
        uint64_t address;
        void*    buffer;
        size_t   size;
    };

    /// Queue all entries and execute them in a single DMA transaction.
    /// Returns false only if the scatter handle itself couldn't be created.
    /// Individual failed reads zero-fill their buffer (VMMDLL guarantee).
    bool ScatterRead(uint32_t pid, std::vector<ScatterEntry>& entries);

    /// Read an Arma/DayZ engine string (uint16_t length at addr+0x8,
    /// data at addr+0x10).  Falls back from UTF-8 to CP1251 if needed.
    std::string ReadArmaString(uint32_t pid, uint64_t address,
                               size_t maxLength = 256);

    /// Read a null-terminated C string.
    std::string ReadCString(uint32_t pid, uint64_t address,
                            size_t maxLength = 256);

    // ------------------------------------------------------------------
    // State
    // ------------------------------------------------------------------

    [[nodiscard]] bool IsInitialized() const noexcept {
        return m_dma && m_dma->is_initialized();
    }

    [[nodiscard]] bool IsAttached() const noexcept {
        return m_process != nullptr;
    }


private:
    std::unique_ptr<DMA>     m_dma;
    std::unique_ptr<Process> m_process;
    uint32_t                 m_attachedPid = 0;

    // Guards m_process / m_dma lifetime against concurrent reads.
    // Reads (ReadRaw / ScatterRead) take a SHARED lock so they run in parallel;
    // Initialize / TryFindProcess take a UNIQUE lock while swapping the handle.
    // This lets the dedicated camera thread read concurrently with the main
    // runtime thread without a use-after-free when a reconnect re-creates Process.
    mutable std::shared_mutex m_accessMutex;

    /// Low-level read via VolkDMA Process.  Returns false on read error.
    bool ReadRaw(uint32_t pid, uint64_t address, void* buf, size_t size);

    /// Attempt to decode a byte buffer as UTF-8; if that fails,
    /// re-interpret as Windows CP1251 and convert to UTF-8.
    static std::string DecodeArmaBytes(const std::vector<uint8_t>& bytes);
};

// -------------------------------------------------------------------------
// Template implementation (must live in the header)
// -------------------------------------------------------------------------

template<typename T>
bool VmmAccessor::TryReadValue(uint32_t pid, uint64_t address, T& outValue) {
    static_assert(std::is_trivially_copyable_v<T>,
                  "TryReadValue requires a trivially-copyable type");
    T tmp{};
    if (!ReadRaw(pid, address, &tmp, sizeof(T))) return false;
    outValue = tmp;
    return true;
}
