#include <WorldIndex/WorldIndex.hpp>
#include <BlockRegistry/BlockRegistry.hpp>
#include <WorldTypeRegistry/WorldTypeRegistry.hpp>

#define WORLD_FLAG_CHEAT                                    (1 << 0)

bool WorldIndex::areCheatsEnabled() const
{
    return (flags & WORLD_FLAG_CHEAT) != 0;
}

void WorldIndex::setSettings(std::string worldType, WorldTypeSettings &settings)
{
    this->worldType = worldType;
    worldTypeSettings.clear();
    settings.serialize(worldTypeSettings);
}

void WorldIndex::syncBlockTable()
{
    if (blockTable.size() == 0)
    {
        blockTable.push_back("main:air");
    }

    blockRegistry.assignBlockIds(blockTable);
}

void WorldIndex::serialize(std::vector<uint8_t> &output) const
{
    Signature sig;
    std::memcpy(sig.magic, WORLD_INDEX_MAGIC, sizeof(sig.magic));
    sig.gameVersion = Version::ofThisBuild;

    serializeInto(
        output,
        sig,
        uniqueID,
        flags,
        avatar,
        worldType,
        worldTypeSettings,
        blockTable
    );
}

size_t WorldIndex::deserialize(const uint8_t *data, size_t size, std::string &errorOut, bool allowOtherVersion)
{
    if (size < sizeof(Signature))
    {
        errorOut = "This is not a Voxelverse world.";
        return 0;
    }

    Signature sig;
    std::memcpy(&sig, data, sizeof(Signature));

    data += sizeof(Signature);
    size -= sizeof(Signature);

    if (std::memcmp(sig.magic, WORLD_INDEX_MAGIC, sizeof(sig.magic)) != 0)
    {
        errorOut = "This is not a Voxelverse world.";
        return 0;
    }

    gameVersion = sig.gameVersion;
    if (gameVersion != Version::ofThisBuild)
    {
        if (!allowOtherVersion)
        {
            errorOut = formatString("This world is for version $+ (you have $+)", gameVersion, Version::ofThisBuild);
            return 0;
        }

        size_t result = ::deserialize(
            data, size,
            uniqueID,
            flags,
            avatar
        );

        if (result == 0)
        {
            errorOut = formatString("The windex.dat file is corrupted.");
            return 0;
        }

        return result;
    }

    size_t result = ::deserialize(
        data, size,
        uniqueID,
        flags,
        avatar,
        worldType,
        worldTypeSettings,
        blockTable
    );

    if (result == 0)
    {
        errorOut = formatString("The windex.dat file is corrupted.");
        return 0;
    }

    if (worldTypeRegistry.getByID(worldType) == nullptr)
    {
        errorOut = formatString("Invalid world type: $+", worldType);
        return 0;
    }

    return result + sizeof(Signature);
}

const std::vector<std::string>& WorldIndex::getBlockTable()
{
    return blockTable;
}

std::shared_ptr<WorldTypeSettings> WorldIndex::loadWorldTypeSettings(std::string &errorOut)
{
    // Load the world type.
    WorldType *worldType = worldTypeRegistry.getByID(this->worldType);

    if (worldType == nullptr)
    {
        errorOut = formatString("Invalid world type ID: $+", this->worldType);
        return nullptr;
    }

    // Load the world type settings.
    std::shared_ptr<WorldTypeSettings> settings = worldType->deserializeSettings(
        worldTypeSettings.data(),
        worldTypeSettings.size()
    );

    if (settings == nullptr)
    {
        errorOut = "Failed to decode the world type settings.";
        return nullptr;
    }

    return settings;
}

UniqueID WorldIndex::getID()
{
    return uniqueID;
}

void WorldIndex::generateNewID()
{
    uniqueID = UniqueID::generate();
}

void WorldIndex::enableCheats()
{
    flags |= WORLD_FLAG_CHEAT;
}

void WorldIndex::setAvatar(WorldAvatar avatar)
{
    this->avatar = avatar;
}

WorldAvatar WorldIndex::getAvatar() const
{
    return avatar;
}

std::string WorldIndex::getWorldTypeLabel() const
{
    return std::string(worldTypeRegistry.getByID(worldType)->getName());
}

Version WorldIndex::getGameVersion() const
{
    return gameVersion;
}

WorldIndex WorldIndex::create(bool cheats, WorldAvatar avatar)
{
    WorldIndex result;

    result.uniqueID = UniqueID::generate();
    result.flags = cheats ? WORLD_FLAG_CHEAT : 0;
    result.avatar = avatar;

    return result;
}