#include "ScenePacker.h"
#include "../../core/utils/Logging.h"
#include "../../core/utils/ExceptionHandler.h"

#include <zlib.h>
#include <cstring>

using namespace Create;

static constexpr size_t HEADER_SIZE = 8; // 4 bytes CRC32 + 4 bytes decompressed size


// CRC32 via zlib
uint32_t ScenePacker::ComputeCRC32(const void* data, size_t length)
{
    return crc32(0L, static_cast<const Bytef*>(data), static_cast<uInt>(length));
}


std::vector<uint8_t> ScenePacker::Pack(const YAML::Node& node)
{
    YAML::Emitter out;
    out << node;
    return PackText(out.c_str());
}

std::vector<uint8_t> ScenePacker::PackText(const std::string& yamlText)
{
    const uint8_t* input = reinterpret_cast<const uint8_t*>(yamlText.data());
    uLongf maxCompressedSize = compressBound(static_cast<uLongf>(yamlText.size()));

    std::vector<uint8_t> compressed(HEADER_SIZE + maxCompressedSize);
    uLongf actualSize = maxCompressedSize;

    if (compress(&compressed[HEADER_SIZE], &actualSize, input, yamlText.size()) != Z_OK) {
        throw FatalError("[ScenePacker] Compression failed.", Exceptions::SystemFailure);
    }

    uint32_t crc = ComputeCRC32(&compressed[HEADER_SIZE], actualSize);
    uint32_t originalSize = static_cast<uint32_t>(yamlText.size());

    std::memcpy(&compressed[0], &crc, sizeof(uint32_t));
    std::memcpy(&compressed[4], &originalSize, sizeof(uint32_t));

    compressed.resize(HEADER_SIZE + actualSize);
    return compressed;
}

std::string ScenePacker::UnpackText(const std::vector<uint8_t>& binary)
{
    if (binary.size() < HEADER_SIZE) {
        throw RecoverableError("[ScenePacker] Binary too small for header.", Exceptions::InvalidFormat);
    }

    uint32_t storedCRC, decompressedSize;
    std::memcpy(&storedCRC,        &binary[0], sizeof(uint32_t));
    std::memcpy(&decompressedSize, &binary[4], sizeof(uint32_t));

    const uint8_t* compressedData = binary.data() + HEADER_SIZE;
    size_t compressedSize = binary.size() - HEADER_SIZE;

    uint32_t actualCRC = ComputeCRC32(compressedData, compressedSize);
    if (actualCRC != storedCRC) {
        throw RecoverableError("[ScenePacker] CRC32 mismatch — file corrupted.", Exceptions::InvalidFormat);
    }

    std::vector<uint8_t> output(decompressedSize);
    uLongf outSize = decompressedSize;

    if (uncompress(output.data(), &outSize, compressedData, compressedSize) != Z_OK) {
        throw RecoverableError("[ScenePacker] Failed to decompress scene.", Exceptions::InvalidFormat);
    }

    return std::string(output.begin(), output.end());
}

YAML::Node ScenePacker::Unpack(const std::vector<uint8_t>& binary)
{
    return YAML::Load(UnpackText(binary));
}
