#include <Display/WidgetDisplay/WorldDisplay/WorldDisplay.hpp>
#include <GraphicsContext/GraphicsContext.hpp>
#include <Item/StatefulItem/Bag/Backpack/Backpack.hpp>
#include <Widget/Builder/Builder.hpp>
#include <Widget/ColorSelector/ColorSelector.hpp>
#include <Widget/IconLabel/IconLabel.hpp>
#include <Widget/Label/Label.hpp>
#include <Widget/Layout/Center/Center.hpp>
#include <Widget/Layout/Column/Column.hpp>
#include <Widget/Layout/CompoundWidget/Deck/Deck.hpp>
#include <Widget/Layout/Frame/Frame.hpp>
#include <Widget/Layout/Row/Row.hpp>
#include <Widget/Panel/Panel.hpp>
#include <Widget/TextField/TextField.hpp>

BackpackExtraState::BackpackExtraState() : label("No name")
{
    label = "No name";
    randomizeColor();
}

void BackpackExtraState::randomizeColor()
{
    static thread_local std::mt19937 rng{std::random_device{}()};
    static std::uniform_real_distribution<float> dist01(0.0f, 1.0f);

    glm::vec3 c(
        dist01(rng),
        0.7f + 0.3f * dist01(rng),
        0.5f + 0.5f * dist01(rng)
    );

    glm::vec4 K = glm::vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    glm::vec3 p = glm::abs(glm::fract(glm::vec3(c.x + K.x, c.x + K.y, c.x + K.z)) * 6.0f - glm::vec3(K.w, K.w, K.w));
    glm::vec3 rgb = c.z * mix(glm::vec3(K.x), glm::clamp(p - glm::vec3(K.x), 0.0f, 1.0f), c.y);

    color.red = rgb.r * 255.0f;
    color.green = rgb.g * 255.0f;
    color.blue = rgb.b * 255.0f;
    color.alpha = 0xFF;
}

bool BackpackExtraState::operator==(const BackpackExtraState &other) const
{
    return label == other.label && color.asInt == other.color.asInt;
}

Backpack::Backpack(std::string name) : Bag(name)
{
    registerBagTransform("setLabel", [this](ItemStack &stack, std::string label)
    {
        state(stack).extra.label = label;
    });

    registerBagTransform("setColor", [this](ItemStack &stack, RGBABytes color)
    {
        state(stack).extra.color = color;
    });
}

std::string Backpack::getDisplayName(const ItemStack &stack)
{
    return "Backpack";
}

void Backpack::draw(GraphicsContext &context, int x, int y, const ItemStack &stack)
{
    const BackpackExtraState &extra = state(stack).extra;

    context.color = glm::vec4(
        static_cast<float>(extra.color.red) / 255.0f,
        static_cast<float>(extra.color.green) / 255.0f,
        static_cast<float>(extra.color.blue) / 255.0f,
        255.0f
    );
    context.drawMaterial(Material::backpack, x, y);
}

void Backpack::draw3D(GraphicsContext &context, WorldDisplay &worldDisplay, glm::vec3 pos, ItemStack &stack)
{
    worldDisplay.drawMeshedIcon(context, Material::backpack, pos);
}

void Backpack::drawFlat(GraphicsContext &context, WorldDisplay &worldDisplay, glm::mat4 matrix, ItemStack &stack)
{
    worldDisplay.drawMeshedIconFlat(context, Material::backpack, matrix);
}

void Backpack::getExtraToolTipLines(const ItemStack &stack, ToolTip &tip)
{
    const auto &extra = state(stack).extra;
    tip.addLine(extra.color.toNormalizedVec(), extra.label);
}

bool Backpack::canMerge(const ItemStack &a, const ItemStack &b)
{
    return false;
}

void Backpack::onPickedUpInCheatBook(ItemStack &stack)
{
    state(stack).extra.randomizeColor();
}

Widget* Backpack::createBagWidget(InventoryController &ctrl, const std::string &bagInvId, ItemStack &stack, int equipmentSlot)
{
    BackpackExtraState &extra = state(stack).extra;

    return new Column(
        new Frame(
            Color::shadow,
            new Row(
                new Center(
                    new Builder<int>(
                        extra.color.asInt,
                        [&extra](int) -> Widget*
                        {
                            return (new IconLabel(Material::backpack))->withColor(extra.color.toNormalizedVec());
                        }
                    )
                ),
                new Panel(glm::ivec2(2, 0)),
                Layout::WithProportion<1>(),
                (new TextField(extra.label))
                    ->withMinWidth(0)
                    ->withAutoSelect()
                    ->withChangedCallback([&ctrl, &extra, equipmentSlot]()
                    {
                        ctrl.transformBag(equipmentSlot, "setLabel", extra.label);
                    }),
                new Panel(glm::ivec2(2, 0)),
                (new ColorSelector(extra.color))
                    ->withChangedCallback([&ctrl, &extra, equipmentSlot]()
                    {
                        ctrl.transformBag(equipmentSlot, "setColor", extra.color);
                    })
            )
        ),

        new Panel(glm::ivec2(0, 2)),

        new Row(
            ctrl.makeItemSlotColumn(bagInvId, 0, 5),
            ctrl.makeItemSlotColumn(bagInvId, 5, 5),
            ctrl.makeItemSlotColumn(bagInvId, 10, 5),
            ctrl.makeItemSlotColumn(bagInvId, 15, 5)
        )
    );
}