#include <Controller/InventoryController/GrindingBowlController/GrindingBowlController.hpp>
#include <Material/Material.hpp>
#include <Widget/Label/Label.hpp>
#include <Widget/Layout/CompoundWidget/IconText/IconText.hpp>
#include <Widget/Layout/CompoundWidget/ItemProcRuleExplorer/ItemProcRuleExplorer.hpp>
#include <Widget/StaticItemSlot/ItemSlot/ItemSlot.hpp>
#include <Widget/Layout/Row/Row.hpp>
#include <Widget/Layout/Column/Column.hpp>
#include <Widget/Layout/Center/Center.hpp>
#include <Widget/Builder/Builder.hpp>
#include <Utilities/Utilities.hpp>
#include <Entity/PhysicsEntity/CameraEntity/Player/Player.hpp>
#include <Widget/TabList/MachineHelpTab/MachineHelpTab.hpp>
#include <Widget/TabList/TabList.hpp>
#include <World/World.hpp>
#include <Widget/ProgressIndicator/ProgressIndicator.hpp>
#include <Widget/Panel/Panel.hpp>
#include <Widget/Button/Button.hpp>
#include <World/World.hpp>
#include <FluidRegistry/FluidRegistry.hpp>
#include <BlockRegistry/BlockRegistry.hpp>

GrindingBowlController::GrindingBowlController(ControllerChannel &channel) :
    InventoryController(channel),
    updateGrindingProgress(this, RunsOn::Client,
        [this](uint32_t progress) -> uint32_t
    {
        this->progress = progress;

        return 0;
    }),
    grind(this, RunsOn::Server,
        [this](uint32_t ignore) -> uint32_t
    {
        if (this->bowl != nullptr)
        {
            this->bowl->grind();
        }

        return ignore;
    })
{}

void GrindingBowlController::onConnect(std::shared_ptr<Machine> bowlMachine)
{
    bindMachine(bowlMachine);
    bowl = std::dynamic_pointer_cast<MGrindingBowl>(bowlMachine);
}

Widget* GrindingBowlController::getRightPanel()
{
    static uint32_t staticCompletionProgress = MGrindingBowl::completionProgress;

    return new Column(
        new Center(new IconText(blockRegistry.getBlockByName("main:grindingBowl").iconMaterial, "Grinding bowl")),

        Layout::WithProportion<1>(),
        new TabList(
            "/grinding-bowl/tab-list",
            new TabList::Page(
                blockRegistry.getBlockByName("main:grindingBowl").iconMaterial,
                "Grind",
                new Center(
                    new Column(
                        new Row(
                            (new ItemSlot(this, "machine", 0))
                                ->withEmptyToolTip(GraphicsContext::colorstr(Color::inputHighlight) + "Input"),
                            new Center(new ProgressIndicator<uint32_t>(progress, staticCompletionProgress)),
                            (new ItemSlot(this, "machine", 1))
                                ->withEmptyToolTip(GraphicsContext::colorstr(Color::faded) + "Output")
                        ),
                        new Center(new Button("Grind", [this]()
                        {
                            this->grind(0);
                        }))
                    )
                )
            ),

            new TabList::Page(
                Material::recipeBookIcon,
                "Recipes",
                new ItemProcRuleExplorer(
                    "/grinding-rule-explorer",
                    [](ItemStack &input) -> ItemStack
                    {
                        return input.getItem()->getGrindingOutput(input);
                    }
                )
            ),

            new MachineHelpTab("articles/grinding-bowl", "/grinding-bowl/help")
        )
    );
}

void GrindingBowlController::update()
{
    InventoryController::update();

    if (world.isLocal() && bowl != nullptr)
    {
        if (progress != bowl->getProgress())
        {
            progress = bowl->getProgress();
            
            updateGrindingProgress(bowl->getProgress());
        }
    }
}

glm::vec4 GrindingBowlController::getSlotHighlight(const std::string &id, int slot, ItemStack &stack)
{
    if (id == "machine" && slot == 0)
    {
        if (!player->movingStack.isEmpty() && !player->movingStack.getItem()->getGrindingOutput(player->movingStack).isEmpty())
        {
            return Color::inputHighlight;
        }
    }

    if (id == "machine" || stack.isEmpty())
    {
        return InventoryController::getSlotHighlight(id, slot, stack);
    }

    ItemStack outputStack = stack.getItem()->getGrindingOutput(stack);
    if (outputStack.isEmpty())
    {
        return InventoryController::getSlotHighlight(id, slot, stack);
    }

    return Color::inputHighlight;
}

void GrindingBowlController::getExtraToolTipLines(const std::string &id, int slot, ItemStack &stack, ToolTip &tip)
{
    if (id == "machine" || stack.isEmpty())
    {
        return;
    }

    ItemStack outputStack = stack.getItem()->getGrindingOutput(stack);
    if (outputStack.isEmpty())
    {
        return;
    }

    tip.addLine(
        Color::inputHighlight,
        formatString(
            "Grinds to $+ $+",
            outputStack.getStackSize(),
            outputStack.getDisplayName()
        )
    );
}