#include "Readers/EntityTypeCache.h"

#include <cctype>
#include <cstdio>
#include <string>

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

// -------------------------------------------------------------------------
// FormatEntityName
// -------------------------------------------------------------------------

std::string FormatEntityName(const std::string& raw)
{
    if (raw.empty()) return raw;

    // Strip known prefixes (case-sensitive — DayZ uses "Dayz_" and "DayZ_")
    std::string s = raw;
    for (const char* prefix : { "DayZ_", "Dayz_", "dayz_", "DAYZ_" }) {
        size_t len = std::strlen(prefix);
        if (s.size() > len && s.compare(0, len, prefix) == 0) {
            s = s.substr(len);
            break;
        }
    }

    // Split PascalCase: insert space before an uppercase letter that is
    // preceded by a lowercase letter or digit.
    std::string result;
    result.reserve(s.size() + 8);
    for (size_t i = 0; i < s.size(); ++i) {
        unsigned char c = static_cast<unsigned char>(s[i]);
        if (i > 0 && std::isupper(c)) {
            unsigned char prev = static_cast<unsigned char>(s[i - 1]);
            if (std::islower(prev) || std::isdigit(prev))
                result += ' ';
        }
        result += static_cast<char>(c == '_' ? ' ' : c);
    }

    // Collapse multiple consecutive spaces (e.g. from "CQB_Rifle" → "CQB  Rifle")
    std::string out;
    out.reserve(result.size());
    bool prevSpace = false;
    for (char c : result) {
        if (c == ' ') { if (!prevSpace) { out += ' '; prevSpace = true; } }
        else          { out += c; prevSpace = false; }
    }
    // Trim leading/trailing space
    size_t start = out.find_first_not_of(' ');
    size_t end   = out.find_last_not_of(' ');
    return (start == std::string::npos) ? "" : out.substr(start, end - start + 1);
}

// -------------------------------------------------------------------------
// SanitizeEntityString
// -------------------------------------------------------------------------

std::string SanitizeEntityString(const std::string& value)
{
    if (value.empty()) return {};

    // Reject strings containing unwanted control characters
    for (unsigned char c : value) {
        if (c < 0x20 && c != '\t' && c != '\r' && c != '\n') {
            return {};
        }
    }

    // Count "readable" characters: letters, digits, whitespace, and common
    // punctuation used in DayZ type names.
    int readableCount = 0;
    for (unsigned char c : value) {
        if (std::isalpha(c)  ||
            std::isdigit(c)  ||
            std::isspace(c)  ||
            c == '.'  || c == ','  || c == ':' || c == ';' ||
            c == '-'  || c == '_'  || c == '+' || c == '/' ||
            c == '\\' || c == '('  || c == ')' || c == '[' ||
            c == ']'  || c == '{'  || c == '}' || c == '#')
        {
            ++readableCount;
        }
    }

    // Require readableCount * 2 >= length  (i.e. >= 50 % readable)
    if (readableCount * 2 < static_cast<int>(value.size())) {
        return {};
    }

    return value;
}

// -------------------------------------------------------------------------
// GetBestEntityName
// -------------------------------------------------------------------------

std::string GetBestEntityName(const std::string& typeName,
                               const std::string& configName,
                               const std::string& cleanName,
                               const std::string& modelName,
                               uint64_t            entityAddress)
{
    if (!typeName.empty())   return typeName;
    if (!configName.empty()) return configName;
    if (!cleanName.empty())  return cleanName;
    if (!modelName.empty())  return modelName;

    // Last resort: hex address
    char buf[32];
    std::snprintf(buf, sizeof(buf), "0x%016llX",
                  static_cast<unsigned long long>(entityAddress));
    return buf;
}

// -------------------------------------------------------------------------
// ReadEntityTypeString
// -------------------------------------------------------------------------

std::string ReadEntityTypeString(VmmAccessor& mem,
                                 uint32_t     pid,
                                 uint64_t     typeAddress,
                                 uint64_t     offset)
{
    uint64_t strPtr = 0;
    if (!mem.TryReadPointer(pid, typeAddress + offset, strPtr)) {
        return {};
    }
    return mem.ReadArmaString(pid, strPtr);
}

// -------------------------------------------------------------------------
// ReadEntityTypeMetadata
// -------------------------------------------------------------------------

EntityTypeMetadata ReadEntityTypeMetadata(VmmAccessor& mem,
                                          uint32_t     pid,
                                          uint64_t     typeAddress)
{
    EntityTypeMetadata meta;

    meta.typeName   = SanitizeEntityString(
        ReadEntityTypeString(mem, pid, typeAddress, Offsets::EntityType::TypeName));
    meta.configName = SanitizeEntityString(
        ReadEntityTypeString(mem, pid, typeAddress, Offsets::EntityType::ConfigName));
    meta.cleanName  = SanitizeEntityString(
        ReadEntityTypeString(mem, pid, typeAddress, Offsets::EntityType::CleanName));
    meta.modelName  = SanitizeEntityString(
        ReadEntityTypeString(mem, pid, typeAddress, Offsets::EntityType::ModelName));

    return meta;
}

// -------------------------------------------------------------------------
// ReadEntityTransform
// -------------------------------------------------------------------------

bool ReadEntityTransform(VmmAccessor&            mem,
                         const RuntimeSession&   session,
                         uint64_t                entityAddress,
                         std::optional<Vector3>& position,
                         std::optional<float>&   headingDegrees)
{
    const uint32_t pid = session.processId;

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

    // Position
    Vector3 pos{};
    if (!mem.TryReadValue<Vector3>(pid,
                                   visualStateAddr + Offsets::Common::Position,
                                   pos))
    {
        return false;
    }

    if (!MemoryValidation::IsValidVector(pos)) {
        return false;
    }

    position = pos;

    // Heading  (non-fatal if direction reads fail)
    float dirX = 0.0f;
    float dirY = 0.0f;
    if (mem.TryReadValue<float>(pid, visualStateAddr + Offsets::Common::DirectionX, dirX) &&
        mem.TryReadValue<float>(pid, visualStateAddr + Offsets::Common::DirectionY, dirY))
    {
        float heading = 0.0f;
        if (MemoryValidation::TryGetCorrectedHeadingFromDirection(dirX, dirY, heading)) {
            headingDegrees = heading;
        }
    }

    return true;
}
