#include "Readers/BulletTableReader.h"

#include <algorithm>
#include <cmath>
#include <cstring>
#include <numbers>
#include <unordered_set>

#include "Offsets.h"
#include "RuntimeOffsets.h"
#include "Memory/MemoryValidation.h"

// -------------------------------------------------------------------------
// TryRead
// -------------------------------------------------------------------------

bool BulletTableReader::TryRead(VmmAccessor&                  mem,
                                const RuntimeSession&         session,
                                std::vector<DayZBulletEntry>& entries)
{
    entries.clear();
    const uint32_t pid = session.processId;

    // 1. Bullet count — read first; the table pointer is null when count==0
    int32_t rawCount = 0;
    if (!mem.TryReadValue<int32_t>(pid,
                                   session.worldAddress + RuntimeOffsets::World::BulletCount,
                                   rawCount))
    {
        return false;
    }

    const int count = std::clamp(rawCount, 0, kMaxBullets);

    // 2. Nothing alive — table pointer may be null, safe to return early
    if (count == 0) {
        m_tableUnavailable = false;
        m_lastPositions.clear();
        return true;
    }

    // 3. Bullet table pointer (only valid when count > 0)
    uint64_t bulletTablePtr = 0;
    if (!mem.TryReadPointer(pid,
                            session.worldAddress + RuntimeOffsets::World::BulletTable,
                            bulletTablePtr))
    {
        m_tableUnavailable = true;
        return false;
    }
    m_tableUnavailable = false;

    // 4. Bulk-read the pointer table (count * 8 bytes)
    const size_t tableBytes = static_cast<size_t>(count) * 8;
    std::vector<uint8_t> buf;
    buf.reserve(tableBytes);
    if (!mem.ReadBytes(pid, bulletTablePtr, tableBytes, buf)) {
        return false;
    }

    // 5. Iterate pointers, build entries for valid distinct addresses
    std::unordered_set<uint64_t> seen;
    seen.reserve(static_cast<size_t>(count));

    for (int i = 0; i < count; ++i) {
        uint64_t addr = 0;
        std::memcpy(&addr, buf.data() + static_cast<size_t>(i) * 8, sizeof(uint64_t));

        if (!MemoryValidation::IsValidUserAddress(addr)) continue;
        if (!seen.insert(addr).second) continue; // skip duplicates

        entries.push_back(BuildBulletEntry(mem, session, addr));
    }

    // 6. Sort by address for deterministic ordering
    std::sort(entries.begin(), entries.end(),
              [](const DayZBulletEntry& a, const DayZBulletEntry& b) {
                  return a.address < b.address;
              });

    // 7. Sync last-position cache
    UpdateLastPositions(entries);

    return true;
}

// -------------------------------------------------------------------------
// Reset
// -------------------------------------------------------------------------

void BulletTableReader::Reset()
{
    m_lastPositions.clear();
    m_tableUnavailable = false;
}

// -------------------------------------------------------------------------
// BuildBulletEntry
// -------------------------------------------------------------------------

DayZBulletEntry BulletTableReader::BuildBulletEntry(VmmAccessor&          mem,
                                                     const RuntimeSession& session,
                                                     uint64_t              address)
{
    DayZBulletEntry entry;
    entry.address = address;

    const uint32_t pid = session.processId;

    // 1. VisualState pointer
    uint64_t visualStateAddr = 0;
    if (!mem.TryReadPointer(pid, address + Offsets::Common::VisualState, visualStateAddr)) {
        return entry;
    }

    // 2. Position
    Vector3 pos{};
    if (TryReadBulletPosition(mem, pid, visualStateAddr, pos)) {
        entry.position = pos;
    }

    // 3. Direction from stored visual vectors
    Vector3 dir{};
    float   heading = 0.0f;
    if (TryReadBulletDirection(mem, pid, visualStateAddr, dir, heading)) {
        entry.direction      = dir;
        entry.headingDegrees = heading;
        entry.directionSource = "visual";
        return entry;
    }

    // 4. Direction fallback: flight delta from previous position
    if (entry.position.has_value()) {
        auto prevIt = m_lastPositions.find(address);
        if (prevIt != m_lastPositions.end()) {
            Vector3 flightDir{};
            float   flightHeading = 0.0f;
            if (TryGetFlightDirection(prevIt->second, *entry.position,
                                      flightDir, flightHeading))
            {
                entry.direction      = flightDir;
                entry.headingDegrees = flightHeading;
                entry.directionSource = "raw";
            }
        }
    }

    return entry;
}

// -------------------------------------------------------------------------
// TryReadBulletPosition
// -------------------------------------------------------------------------

bool BulletTableReader::TryReadBulletPosition(VmmAccessor& mem,
                                               uint32_t     pid,
                                               uint64_t     visualStateAddr,
                                               Vector3&     pos)
{
    if (!mem.TryReadValue<Vector3>(pid,
                                   visualStateAddr + Offsets::Common::Position,
                                   pos))
    {
        return false;
    }
    return MemoryValidation::IsValidVector(pos);
}

// -------------------------------------------------------------------------
// TryReadBulletDirection
// -------------------------------------------------------------------------

bool BulletTableReader::TryReadBulletDirection(VmmAccessor& mem,
                                                uint32_t     pid,
                                                uint64_t     visualStateAddr,
                                                Vector3&     dir,
                                                float&       heading)
{
    // DirectionX and DirectionY are stored as float[3] each (x,y,z components
    // of the engine's view-matrix row).  We use X-component from each row to
    // reconstruct a 2-D horizontal direction that matches the C# original.
    float dirX = 0.0f;
    float dirY = 0.0f;
    if (!mem.TryReadValue<float>(pid, visualStateAddr + Offsets::Common::DirectionX, dirX))
        return false;
    if (!mem.TryReadValue<float>(pid, visualStateAddr + Offsets::Common::DirectionY, dirY))
        return false;

    // Construct a normalized 3-D direction in the horizontal plane
    const float len = std::sqrt(dirX * dirX + dirY * dirY);
    if (len < kMinDirectionDelta) return false;

    dir.x = dirX / len;
    dir.y = 0.0f;
    dir.z = dirY / len;

    constexpr float kRadToDeg = 180.0f / static_cast<float>(std::numbers::pi);
    heading = MemoryValidation::NormalizeHeading(
        std::atan2(dirY, dirX) * kRadToDeg + 90.0f);

    return true;
}

// -------------------------------------------------------------------------
// TryGetFlightDirection
// -------------------------------------------------------------------------

bool BulletTableReader::TryGetFlightDirection(const Vector3& prev,
                                               const Vector3& curr,
                                               Vector3&       dir,
                                               float&         heading)
{
    const float dx = curr.x - prev.x;
    const float dy = curr.y - prev.y;
    const float dz = curr.z - prev.z;

    const float lenSq = dx * dx + dy * dy + dz * dz;
    if (lenSq < kMinDirectionDelta * kMinDirectionDelta) return false;

    // Horizontal components must also be meaningful
    const float hDelta = std::sqrt(dx * dx + dz * dz);
    if (hDelta < kMinDirectionDelta) return false;

    const float len = std::sqrt(lenSq);
    dir.x = dx / len;
    dir.y = dy / len;
    dir.z = dz / len;

    constexpr float kRadToDeg = 180.0f / static_cast<float>(std::numbers::pi);
    heading = MemoryValidation::NormalizeHeading(
        std::atan2(dz, dx) * kRadToDeg + 90.0f);

    return true;
}

// -------------------------------------------------------------------------
// UpdateLastPositions
// -------------------------------------------------------------------------

void BulletTableReader::UpdateLastPositions(const std::vector<DayZBulletEntry>& entries)
{
    // Collect the set of currently-alive bullet addresses
    std::unordered_set<uint64_t> alive;
    alive.reserve(entries.size());

    for (const auto& e : entries) {
        alive.insert(e.address);
        if (e.position.has_value()) {
            m_lastPositions[e.address] = *e.position;
        }
    }

    // Remove entries that are no longer alive
    for (auto it = m_lastPositions.begin(); it != m_lastPositions.end(); ) {
        if (alive.find(it->first) == alive.end()) {
            it = m_lastPositions.erase(it);
        } else {
            ++it;
        }
    }
}
