#include <Item/StatefulItem/BlockItem/BlockItem.hpp>
#include <BlockRegistry/BlockRegistry.hpp>
#include <PlayerTriggerRegistry/PlayerTriggerRegistry.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <Vk/ShaderConst.hpp>
#include <Display/WidgetDisplay/WorldDisplay/WorldDisplay.hpp>

bool operator==(const BlockItemState &a, const BlockItemState &b)
{
    return a.block == b.block;
}

BlockItem::BlockItem(std::string name) : StatefulItem(name)
{
    setMaxStackSize(256);
}

Block& BlockItem::getBlockInStack(ItemStack &stack)
{
    return *state(stack).block;
}

void BlockItem::onUse(LocalWorld &world, IWorldActor &actor, ItemStack &stack, glm::ivec3 pos, Side side)
{
    pos += side.out();

    bool isFree = true;
    Block &block = *state(stack).block;

    world.forEachEntity([&isFree, &block, pos](std::shared_ptr<Entity> entity)
    {
        isFree = isFree && !entity->wouldCollideWith(block, pos);
    });

    if (!isFree)
    {
        return;
    }

    if (!world.getBlockAt(pos)->canReplaceWithPlacedBlock())
    {
        return;
    }

    uint8_t meta = 0;
    if (!block.canPlace(world, actor, pos, side.opposite(), meta))
    {
        return;
    }

    world.playSound(block.getPlacingSound(), pos);
    world.setBlockAt(pos, block.name, meta);
    stack.extractOne();

    actor.onBlockPlaced(block.name, pos);
}

void BlockItem::draw(GraphicsContext &context, int x, int y, const ItemStack &stack)
{
    Block &block = *state(stack).block;
    
    context.color = glm::vec4(1.0f);
    context.drawMaterial(block.iconMaterial, x, y);
}

std::string BlockItem::getDisplayName(const ItemStack &stack)
{
    Block &block = *state(stack).block;
    return block.displayName;
}

bool BlockItem::canMerge(const ItemStack &a, const ItemStack &b)
{
    if (b.getItem() != this)
    {
        return false;
    }

    return state(a).block == state(b).block;
}

void SerialCodec<BlockItemState>::encode(std::vector<uint8_t> &outBytes, const BlockItemState &state)
{
    serializeInto(outBytes, state.block->name);
}

size_t SerialCodec<BlockItemState>::decode(const uint8_t *data, size_t size, BlockItemState &state)
{
    std::string name;
    size_t strSize = deserialize(data, size, name);

    if (strSize == 0)
    {
        return 0;
    }

    state.block = &blockRegistry.getBlockByName(name);
    return strSize;
}

void BlockItem::draw3D(GraphicsContext &context, WorldDisplay &worldDisplay, glm::vec3 pos, ItemStack &stack)
{
    Block &block = *state(stack).block;

    glm::mat4 modelMatrix =
        glm::translate(glm::mat4(1.0f), pos) *
        glm::rotate(glm::mat4(1.0f), (float) SDL_GetTicks() / 300.0f, glm::vec3(0.0f, 0.0f, 1.0f)) *
        glm::scale(glm::mat4(1.0f), glm::vec3(0.5f, 0.5f, 0.5f)) *
        glm::translate(glm::mat4(1.0f), glm::vec3(-0.5f));

    worldDisplay.getFrameDrawer().setModelMatrix(modelMatrix);
    block.defaultModel->draw(context, worldDisplay);
}

void BlockItem::drawFlat(GraphicsContext &context, WorldDisplay &worldDisplay, glm::mat4 matrix, ItemStack &stack)
{
    Block &block = *state(stack).block;

    glm::mat4 modelMatrix =
        matrix *
        glm::scale(glm::mat4(1.0f), glm::vec3(0.5f, 0.5f, 0.5f)) *
        glm::translate(glm::mat4(1.0f), glm::vec3(-0.5f, -0.5f, 0.0f));

    worldDisplay.getFrameDrawer().setModelMatrix(modelMatrix);
    block.defaultModel->draw(context, worldDisplay);
}

ItemStack BlockItem::getGrindingOutput(ItemStack &stack)
{
    return state(stack).block->getGrindingOutput();
}

glm::vec3 BlockItem::getGlowColor(ItemStack &stack)
{
    return state(stack).block->getLightColor(0);
}

void BlockItem::onCollectedBy(Player &player, const ItemStack &stack)
{
    Item::onCollectedBy(player, stack);
    playerTriggerRegistry.raise(player, formatString("main:BlockCollected::$+", state(stack).block->name));
}

void BlockItem::onMovedBy(Player &player, const ItemStack &stack)
{
    Item::onMovedBy(player, stack);
    playerTriggerRegistry.raise(player, formatString("main:BlockMoved::$+", state(stack).block->name));
}