#include <Utilities/TemperatureScale.hpp>
#include <Widget/Builder/Builder.hpp>
#include <Widget/Layout/CompoundWidget/IconText/IconText.hpp>
#include <Widget/Layout/CompoundWidget/TemperatureScaleSelector/TemperatureScaleSelector.hpp>
#include <Widget/TabList/MachineHelpTab/MachineHelpTab.hpp>
#include <sstream>
#include <Controller/InventoryController/ChemicalReactorController/ChemicalReactorController.hpp>
#include <Widget/Layout/Center/Center.hpp>
#include <Widget/Layout/Row/Row.hpp>
#include <Widget/Layout/Column/Column.hpp>
#include <Widget/Label/Label.hpp>
#include <Widget/TabList/TabList.hpp>
#include <Widget/Button/Button.hpp>
#include <Widget/Panel/Panel.hpp>
#include <Widget/Switch/Switch.hpp>
#include <Widget/StaticItemSlot/ItemSlot/ItemSlot.hpp>
#include <Entity/PhysicsEntity/CameraEntity/Player/Player.hpp>
#include <World/World.hpp>
#include <Widget/Thermometer/Thermometer.hpp>
#include <Item/IChemicalReactorComponent.hpp>
#include <Widget/Layout/CompoundWidget/Explorer/Explorer.hpp>
#include <Widget/StaticItemSlot/StaticItemSlot.hpp>
#include <Widget/InfoIcon/InfoIcon.hpp>
#include <Widget/IconLabel/IconLabel.hpp>

Widget* ChemicalReactorController::makeReactorChamber()
{
    std::list<Widget*> chamberColumns;

    int col;
    for (col=0; col<6; col++)
    {
        std::list<Widget*> slots;
        
        int row;
        for (row=0; row<6; row++)
        {
            int index = col * 6 + row;
            slots.push_back(new ItemSlot(this, "machine", index));
        }

        chamberColumns.push_back(new Column(slots));
    }

    return new Center(new Row(chamberColumns));
}

Widget* ChemicalReactorController::makeRecipeBook()
{
    return new Explorer<MChemicalReactor::Recipe*>(
        "",
        MChemicalReactor::queryRecipes,
        [](MChemicalReactor::Recipe *recipe) -> Widget*
        {
            std::list<Widget*> pieces;

            std::vector<ItemStack>::iterator it = recipe->inputs.begin();
            while (it != recipe->inputs.end())
            {
                ItemStack stack = *it;
                pieces.push_back(
                    new StaticItemSlot([stack]()
                    {
                        return stack;
                    })
                );

                ++it;

                if (it == recipe->inputs.end())
                {
                    pieces.push_back(new Center(new IconLabel(Material::rightArrow)));
                }
                else
                {
                    pieces.push_back(new Center(new Label("+")));
                }
            }

            pieces.push_back(
                new StaticItemSlot([recipe]()
                {
                    return recipe->output;
                })
            );

            if (recipe->fluidType == "")
            {
                return new Row(
                    new Center(
                        new Builder<TemperatureScale>(
                            settings.preferredTemperatureScale,
                            [recipe](TemperatureScale) -> Widget*
                            {
                                return new InfoIcon(formatString("Min. temp: $+", formatTemperature(recipe->minTemp)));
                            }
                        )
                    ),
                    new Panel(glm::ivec2(2, 0)),

                    pieces
                );
            }

            Fluid *fluid = fluidRegistry.getFluidByName(recipe->fluidType);
            return new Row(
                new Center(
                    new Builder<TemperatureScale>(
                        settings.preferredTemperatureScale,
                        [recipe, fluid](TemperatureScale) -> Widget*
                        {
                            return new InfoIcon(
                                formatString(
                                    "Min. temp: $+\n$+: $+ ml",
                                    formatTemperature(recipe->minTemp),
                                    fluid->displayName,
                                    recipe->fluidAmount
                                )
                            );
                        }
                    )
                ),
                new Panel(glm::ivec2(2, 0)),

                pieces
            );
        }
    );
}

ChemicalReactorController::ChemicalReactorController(ControllerChannel &channel) :
    InventoryController(channel),
    updateReactorState(this, RunsOn::Client,
        [this](int temperature, bool isSwitchedOn) -> uint32_t
    {
        this->temperature = temperature;
        this->isSwitchedOn = isSwitchedOn;

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

        return 0;
    })
{}

void ChemicalReactorController::onConnect(std::shared_ptr<Machine> reactorMachine)
{
    bindMachine(reactorMachine);
    reactor = std::dynamic_pointer_cast<MChemicalReactor>(reactorMachine);
}

Widget* ChemicalReactorController::getRightPanel()
{
    return new Column(
        new Row(
            new Center(new IconText(
                blockRegistry.getBlockByName("main:chemicalReactor").iconMaterial,
                "Chemical reactor"
            )),

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

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

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

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

        Layout::WithProportion<1>(),
        new Row(
            Layout::WithProportion<1>(),
            new TabList(
                "/chemical-reactor/tab-list",
                new TabList::Page(
                    blockRegistry.getBlockByName("main:chemicalReactor").iconMaterial,
                    "Reactor chamber",
                    makeReactorChamber()
                ),

                new TabList::Page(
                    Material::recipeBookIcon,
                    "Recipes",
                    makeRecipeBook()
                ),

                new MachineHelpTab("articles/chemical-reactor", "/chemical-reactor/help")
            ),

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

            new Thermometer(temperature, MChemicalReactor::maxTemperature)
        )
    );
}

glm::vec4 ChemicalReactorController::getSlotHighlight(const std::string &id, int slot, ItemStack &stack)
{
    if (stack.isEmpty())
    {
        return InventoryController::getSlotHighlight(id, slot, stack);
    }

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

    IChemicalReactorComponent *component = stack.getItemAs<IChemicalReactorComponent>();
    if (component == nullptr)
    {
        return InventoryController::getSlotHighlight(id, slot, stack);
    }

    return Color::reactorHighlight;
}

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

    IChemicalReactorComponent *component = stack.getItemAs<IChemicalReactorComponent>();
    if (component == nullptr)
    {
        return;
    }

    std::stringstream ss;
    component->describeInChemicalReactor(stack, ss);

    std::string line;
    while (std::getline(ss, line))
    {
        tip.addLine(Color::reactorHighlight, line);
    }
}

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

    if (world.isLocal() && reactor != nullptr)
    {
        if (temperature != reactor->getTemperature() || isSwitchedOn != reactor->isSwitchedOn())
        {
            temperature = reactor->getTemperature();
            isSwitchedOn = reactor->isSwitchedOn();
            
            updateReactorState(temperature, isSwitchedOn);
        }
    }
}