#include "GltfBinInput.h"

#include "Utils/Logging/Log.h"
#include "XModel/Gltf/GltfConstants.h"

#include <exception>
#include <format>
#include <iostream>
#include <nlohmann/json.hpp>

using namespace gltf;

BinInput::BinInput()
    : m_buffer_size(0u)
{
}

bool BinInput::GetEmbeddedBuffer(const void*& buffer, size_t& bufferSize) const
{
    if (!m_buffer || !m_buffer_size)
        return false;

    buffer = m_buffer.get();
    bufferSize = m_buffer_size;

    return true;
}

const nlohmann::json& BinInput::GetJson() const
{
    if (!m_json)
        throw std::exception();

    return *m_json;
}

bool BinInput::ReadGltfData(std::istream& stream)
{
    uint32_t magic;
    if (!Read(stream, &magic, sizeof(magic), "magic"))
        return false;

    if (magic != GLTF_MAGIC)
    {
        con::error("Invalid magic when trying to read GLB");
        return false;
    }

    uint32_t version;
    if (!Read(stream, &version, sizeof(version), "version"))
        return false;

    if (version != GLTF_VERSION)
    {
        con::error("Unsupported version {} when trying to read GLB: Expected version {}", version, GLTF_VERSION);
        return false;
    }

    uint32_t fileLength;
    if (!Read(stream, &fileLength, sizeof(fileLength), "file length"))
        return false;

    while (true)
    {
        uint32_t chunkLength;
        uint32_t chunkMagic;
        if (!Read(stream, &chunkLength, sizeof(chunkLength), "chunk length", false))
            break;

        if (!Read(stream, &chunkMagic, sizeof(chunkMagic), "chunk magic"))
            return false;

        if (chunkMagic == CHUNK_MAGIC_JSON)
        {
            const auto jsonBuffer = std::make_unique<uint8_t[]>(chunkLength + 1);
            if (!Read(stream, jsonBuffer.get(), chunkLength, "json"))
                return false;
            jsonBuffer[chunkLength] = 0u;

            try
            {
                m_json = std::make_unique<nlohmann::json>(nlohmann::json::parse(jsonBuffer.get(), &jsonBuffer[chunkLength]));
            }
            catch (const nlohmann::json::exception& e)
            {
                con::error("Failed trying to parse JSON of GLB: {}", e.what());
                return false;
            }
        }
        else if (chunkMagic == CHUNK_MAGIC_BIN)
        {
            m_buffer = std::make_unique<uint8_t[]>(chunkLength);
            m_buffer_size = chunkLength;

            if (!Read(stream, m_buffer.get(), m_buffer_size, "bin buffer"))
                return false;
        }
        else
            Skip(stream, chunkLength);

        if (chunkLength % 4u > 0)
            Skip(stream, 4u - (chunkLength % 4u));
    }

    if (!m_json)
    {
        con::error("Failed to load GLB due to missing JSON");
        return false;
    }

    return true;
}

bool BinInput::Read(std::istream& stream, void* dest, const size_t dataSize, const char* readTypeName, const bool errorWhenFailed)
{
    stream.read(static_cast<char*>(dest), dataSize);
    if (stream.gcount() != dataSize)
    {
        if (errorWhenFailed)
            con::error("Unexpected EOF while reading GLB {}", readTypeName);
        return false;
    }

    return true;
}

void BinInput::Skip(std::istream& stream, const size_t skipLength)
{
    stream.seekg(skipLength, std::ios::cur);
}
