#include "resource_pack.hpp"

#include <algorithm>
#include <cstring>
#include <filesystem>
#include <iostream>

namespace fs = std::filesystem;

// Clave XOR para ofuscación simple (puede cambiarse)
constexpr uint8_t XOR_KEY = 0x5A;

ResourcePack::ResourcePack() : isLoaded_(false) {}

ResourcePack::~ResourcePack() {
    clear();
}

// ============================================================================
// EMPAQUETADO (herramienta pack_resources)
// ============================================================================

bool ResourcePack::addDirectory(const std::string& dirPath, const std::string& prefix) {
    if (!fs::exists(dirPath) || !fs::is_directory(dirPath)) {
        std::cerr << "Error: Directorio no existe: " << dirPath << std::endl;
        return false;
    }

    for (const auto& entry : fs::recursive_directory_iterator(dirPath)) {
        if (entry.is_regular_file()) {
            // Construir ruta relativa desde data/ (ej: "data/ball.png" → "ball.png")
            std::string relativePath = fs::relative(entry.path(), dirPath).string();
            std::string fullPath = prefix.empty() ? relativePath : prefix + "/" + relativePath;
            fullPath = normalizePath(fullPath);

            // Leer archivo completo
            std::ifstream file(entry.path(), std::ios::binary);
            if (!file) {
                std::cerr << "Error: No se pudo abrir: " << entry.path() << std::endl;
                continue;
            }

            file.seekg(0, std::ios::end);
            size_t fileSize = file.tellg();
            file.seekg(0, std::ios::beg);

            std::vector<unsigned char> buffer(fileSize);
            file.read(reinterpret_cast<char*>(buffer.data()), fileSize);
            file.close();

            // Crear entrada de recurso
            ResourceEntry resource;
            resource.path = fullPath;
            resource.offset = 0;  // Se calculará al guardar
            resource.size = static_cast<uint32_t>(fileSize);
            resource.checksum = calculateChecksum(buffer.data(), fileSize);

            resources_[fullPath] = resource;

            std::cout << "  Añadido: " << fullPath << " (" << fileSize << " bytes)" << std::endl;
        }
    }

    return !resources_.empty();
}

bool ResourcePack::savePack(const std::string& packFilePath) {
    std::ofstream packFile(packFilePath, std::ios::binary);
    if (!packFile) {
        std::cerr << "Error: No se pudo crear pack: " << packFilePath << std::endl;
        return false;
    }

    // 1. Escribir header
    PackHeader header;
    std::memcpy(header.magic, "VBE3", 4);
    header.version = 1;
    header.fileCount = static_cast<uint32_t>(resources_.size());
    packFile.write(reinterpret_cast<const char*>(&header), sizeof(PackHeader));

    // 2. Calcular offsets (después del header + índice)
    uint32_t currentOffset = sizeof(PackHeader);

    // Calcular tamaño del índice (cada entrada: uint32_t pathLen + path + 3*uint32_t)
    for (const auto& [path, entry] : resources_) {
        currentOffset += sizeof(uint32_t);                // pathLen
        currentOffset += static_cast<uint32_t>(path.size());  // path
        currentOffset += sizeof(uint32_t) * 3;            // offset, size, checksum
    }

    // 3. Escribir índice
    for (auto& [path, entry] : resources_) {
        entry.offset = currentOffset;

        uint32_t pathLen = static_cast<uint32_t>(path.size());
        packFile.write(reinterpret_cast<const char*>(&pathLen), sizeof(uint32_t));
        packFile.write(path.c_str(), pathLen);
        packFile.write(reinterpret_cast<const char*>(&entry.offset), sizeof(uint32_t));
        packFile.write(reinterpret_cast<const char*>(&entry.size), sizeof(uint32_t));
        packFile.write(reinterpret_cast<const char*>(&entry.checksum), sizeof(uint32_t));

        currentOffset += entry.size;
    }

    // 4. Escribir datos de archivos (sin encriptar en pack, se encripta al cargar)
    for (const auto& [path, entry] : resources_) {
        // Encontrar archivo original en disco
        fs::path originalPath = fs::current_path() / "data" / path;
        std::ifstream file(originalPath, std::ios::binary);
        if (!file) {
            std::cerr << "Error: No se pudo re-leer: " << originalPath << std::endl;
            continue;
        }

        std::vector<unsigned char> buffer(entry.size);
        file.read(reinterpret_cast<char*>(buffer.data()), entry.size);
        file.close();

        packFile.write(reinterpret_cast<const char*>(buffer.data()), entry.size);
    }

    packFile.close();
    return true;
}

// ============================================================================
// DESEMPAQUETADO (juego)
// ============================================================================

bool ResourcePack::loadPack(const std::string& packFilePath) {
    clear();

    packFile_.open(packFilePath, std::ios::binary);
    if (!packFile_) {
        return false;
    }

    // 1. Leer header
    PackHeader header;
    packFile_.read(reinterpret_cast<char*>(&header), sizeof(PackHeader));

    if (std::memcmp(header.magic, "VBE3", 4) != 0) {
        std::cerr << "Error: Pack inválido (magic incorrecto)" << std::endl;
        packFile_.close();
        return false;
    }

    if (header.version != 1) {
        std::cerr << "Error: Versión de pack no soportada: " << header.version << std::endl;
        packFile_.close();
        return false;
    }

    // 2. Leer índice
    for (uint32_t i = 0; i < header.fileCount; i++) {
        ResourceEntry entry;

        uint32_t pathLen;
        packFile_.read(reinterpret_cast<char*>(&pathLen), sizeof(uint32_t));

        std::vector<char> pathBuffer(pathLen + 1, '\0');
        packFile_.read(pathBuffer.data(), pathLen);
        entry.path = std::string(pathBuffer.data());

        packFile_.read(reinterpret_cast<char*>(&entry.offset), sizeof(uint32_t));
        packFile_.read(reinterpret_cast<char*>(&entry.size), sizeof(uint32_t));
        packFile_.read(reinterpret_cast<char*>(&entry.checksum), sizeof(uint32_t));

        resources_[entry.path] = entry;
    }

    isLoaded_ = true;
    return true;
}

ResourcePack::ResourceData ResourcePack::loadResource(const std::string& resourcePath) {
    ResourceData result = {nullptr, 0};

    if (!isLoaded_) {
        return result;
    }

    std::string normalizedPath = normalizePath(resourcePath);
    auto it = resources_.find(normalizedPath);
    if (it == resources_.end()) {
        return result;
    }

    const ResourceEntry& entry = it->second;

    // Leer datos desde el pack
    packFile_.seekg(entry.offset);
    result.data = new unsigned char[entry.size];
    result.size = entry.size;
    packFile_.read(reinterpret_cast<char*>(result.data), entry.size);

    // Verificar checksum
    uint32_t checksum = calculateChecksum(result.data, entry.size);
    if (checksum != entry.checksum) {
        std::cerr << "Warning: Checksum incorrecto para: " << resourcePath << std::endl;
    }

    return result;
}

// ============================================================================
// UTILIDADES
// ============================================================================

std::vector<std::string> ResourcePack::getResourceList() const {
    std::vector<std::string> list;
    for (const auto& [path, entry] : resources_) {
        list.push_back(path);
    }
    return list;
}

size_t ResourcePack::getResourceCount() const {
    return resources_.size();
}

void ResourcePack::clear() {
    resources_.clear();
    if (packFile_.is_open()) {
        packFile_.close();
    }
    isLoaded_ = false;
}

// ============================================================================
// FUNCIONES AUXILIARES
// ============================================================================

void ResourcePack::encryptData(unsigned char* data, size_t size) {
    for (size_t i = 0; i < size; i++) {
        data[i] ^= XOR_KEY;
    }
}

void ResourcePack::decryptData(unsigned char* data, size_t size) {
    // XOR es simétrico
    encryptData(data, size);
}

uint32_t ResourcePack::calculateChecksum(const unsigned char* data, size_t size) {
    uint32_t checksum = 0;
    for (size_t i = 0; i < size; i++) {
        checksum ^= static_cast<uint32_t>(data[i]);
        checksum = (checksum << 1) | (checksum >> 31);  // Rotate left
    }
    return checksum;
}

std::string ResourcePack::normalizePath(const std::string& path) {
    std::string normalized = path;

    // Reemplazar \ por /
    std::replace(normalized.begin(), normalized.end(), '\\', '/');

    // Buscar "data/" en cualquier parte del path y extraer lo que viene después
    size_t data_pos = normalized.find("data/");
    if (data_pos != std::string::npos) {
        normalized = normalized.substr(data_pos + 5);  // +5 para saltar "data/"
    }

    // Eliminar ./ del inicio si quedó
    if (normalized.substr(0, 2) == "./") {
        normalized = normalized.substr(2);
    }

    return normalized;
}
