#include <BlockRegistry/BlockRegistry.hpp>
#include <Controller/InventoryController/PumpController/PumpController.hpp>
#include <FluidRegistry/FluidRegistry.hpp>
#include <Milestone/HydraulicsMilestone/HydraulicsMilestone.hpp>
#include <Widget/Layout/Center/Center.hpp>
#include <Widget/Layout/CompoundWidget/FluidPicker/FluidPicker.hpp>
#include <Widget/Layout/CompoundWidget/IconText/IconText.hpp>
#include <Widget/Layout/CompoundWidget/MachineConfigPanel/MachineConfigPanel.hpp>
#include <Widget/Layout/Frame/Frame.hpp>
#include <Widget/Label/Label.hpp>
#include <Widget/Button/Button.hpp>
#include <Widget/Panel/Panel.hpp>
#include <Widget/Layout/Column/Column.hpp>
#include <Widget/Builder/Builder.hpp>
#include <Utilities/Utilities.hpp>
#include <Widget/Slider/Slider.hpp>
#include <Widget/Switch/Switch.hpp>
#include <Widget/Layout/Row/Row.hpp>
#include <Widget/Layout/Column/HorizontalSeparator/HorizontalSeparator.hpp>
#include <Widget/TabList/MachineHelpTab/MachineHelpTab.hpp>
#include <Widget/TabList/TabList.hpp>

PumpController::PumpController(ControllerChannel &channel) :
    InventoryController(channel),
    pumpManually(this, RunsOn::Server,
        [this](uint32_t ignore) -> uint32_t
    {
        if (pump != nullptr)
        {
            pump->pumpIn();

            if (pump->pumpOut())
            {
                this->player->achieve(HydraulicsMilestone::ID, HydraulicsMilestone::ManualPump);
            }
        }

        return ignore;
    }),
    updatePumpState(this, RunsOn::Client,
        [this](
            std::string bufferedFluidName,
            uint32_t pumpRate,
            uint32_t bufferedAmount,
            bool isSwitchedOn,
            std::optional<std::string> fluidFilter
        ) -> uint32_t
    {
        this->bufferedFluidName = bufferedFluidName;
        this->pumpRate = pumpRate;
        this->bufferedAmount = bufferedAmount;
        this->isSwitchedOnPriv = isSwitchedOn;
        this->fluidFilter = fluidFilter;

        return 0;
    }),
    setSwitch(this, RunsOn::Server,
        [this](bool on) -> uint32_t
    {
        if (this->pump == nullptr)
        {
            return 1;
        }

        pump->setSwitch(on);
        return 0;
    }),
    setPumpRate(this, RunsOn::Server,
        [this](uint32_t pumpRate) -> uint32_t
    {
        if (this->pump == nullptr)
        {
            return 1;
        }

        if (pumpRate < minPumpRate || pumpRate > maxPumpRate)
        {
            return 2;
        }

        this->pumpRate = pumpRate;
        pump->pumpRate = pumpRate;
        return 0;
    }),
    setFluidFilter(this, RunsOn::Server,
        [this](std::optional<std::string> fluidFilter) -> uint32_t
    {
        if (this->pump == nullptr)
        {
            return 1;
        }

        if (fluidFilter.has_value())
        {
            if (fluidRegistry.getFluidByName(*fluidFilter) == nullptr)
            {
                return 2;
            }
        }

        this->fluidFilter = fluidFilter;
        pump->fluidFilter = fluidFilter;
        return 0;
    })
{}

void PumpController::onConnect(std::shared_ptr<Machine> pumpMachine)
{
    bindMachine(pump = std::dynamic_pointer_cast<MPump>(pumpMachine));
}

Widget* PumpController::getRightPanel()
{
    return new Column(
        new Row(
            new IconText(blockRegistry.getBlockByName("main:pump").iconMaterial, "Pump"),

            Layout::WithProportion<1>(),
            new Panel(),

            new Switch(isSwitchedOnPriv, [this](bool on)
            {
                this->setSwitch(on);
            })
        ),

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

        Layout::WithProportion<1>(),
        new TabList(
            new TabList::Page(
                blockRegistry.getBlockByName("main:pump").iconMaterial,
                "Pump",
                new Column(
                    new Builder<std::string>(bufferedFluidName, [this](std::string fluidName) -> Widget*
                    {
                        std::string fluidDisplayName;
                        if (!fluidName.empty())
                        {
                            fluidDisplayName = fluidRegistry.getFluidByName(fluidName)->displayName;
                        }

                        return new Builder<uint32_t>(bufferedAmount, [fluidDisplayName](uint32_t bufferedAmount) -> Widget*
                        {
                            return new Label(formatString("Buffered fluid: $+ ml $+", bufferedAmount, fluidDisplayName));
                        });
                    }),

                    new Label("Fluid to pump:"),
                    new FluidPicker(
                        fluidFilter,
                        [this]()
                        {
                            setFluidFilter(fluidFilter);
                        }
                    ),

                    new Label("Pumping rate:"),
                    new Slider<uint32_t>(
                        pumpRate,
                        minPumpRate, maxPumpRate,
                        "$+ ml/t",
                        [this]()
                        {
                            setPumpRate(pumpRate);
                        }
                    ),

                    new HorizontalSeparator(),

                    new Center(new Button("Pump manually", [this]()
                    {
                        this->pumpManually(0);
                    }))
                )
            ),

            MachineConfigPanel::page(
                player,
                "main:pump",
                "/pump/config",
                blockRegistry.getBlockByName("main:pump").iconMaterial,
                *this
            ),

            new MachineHelpTab("articles/pump", "/pump/help")
        )
    );
}

bool PumpController::isBusy()
{
    return false;
}

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

    if (world.isLocal() && pump != nullptr)
    {
        if (
            bufferedFluidName != pump->bufferedFluidName ||
            pumpRate != pump->pumpRate ||
            bufferedAmount != pump->getBufferedAmount() ||
            isSwitchedOnPriv != pump->isSwitchedOn() ||
            fluidFilter != pump->fluidFilter
        )
        {
            bufferedFluidName = pump->bufferedFluidName;
            pumpRate = pump->pumpRate;
            bufferedAmount = pump->getBufferedAmount();
            isSwitchedOnPriv = pump->isSwitchedOn();
            fluidFilter = pump->fluidFilter;
            
            updatePumpState(bufferedFluidName, pumpRate, bufferedAmount, isSwitchedOnPriv, fluidFilter);
        }
    }
}