#include <RemoteDataComponent/RemoteDataComponent.hpp>
#include <Widget/BatteryView/BatteryView.hpp>
#include <Widget/DownArrow/DownArrow.hpp>
#include <Widget/Layout/CompoundWidget/IconText/IconText.hpp>
#include <sstream>
#include <Controller/InventoryController/NuclearReactorController/NuclearReactorController.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/INuclearReactorComponent.hpp>
#include <Widget/StaticItemSlot/StaticItemSlot.hpp>
#include <Widget/InfoIcon/InfoIcon.hpp>
#include <Widget/IconLabel/IconLabel.hpp>
#include <Widget/StaticItemSlot/ItemSlot/TemperatureItemSlot/TemperatureItemSlot.hpp>
#include <Widget/Layout/CompoundWidget/TemperatureScaleSelector/TemperatureScaleSelector.hpp>
#include <Widget/Layout/CompoundWidget/DataInputTable/DataInputTable.hpp>

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

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

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

    return new Center(
        new Row(
            chamberColumns,
            new Panel(glm::ivec2(4, 0)),
            new Center(
                new Column(
                    new Center(new BatteryView(energy, MNuclearReactor::energyCapacity, 350)),
                    new Center(new DownArrow()),
                    new Center(new ItemSlot(this, "machine", 8*8))
                )
            )
        )
    );
}

NuclearReactorController::NuclearReactorController(ControllerChannel &channel) :
    InventoryController(channel),
    updateReactorState(this, RunsOn::Client,
        [this](
            bool isSwitchedOn,
            int energy,
            std::vector<int> slotTemperatures,
            std::vector<DataCableComponent::InputRule> inputRules
        ) -> uint32_t
    {
        this->isSwitchedOn = isSwitchedOn;
        this->energy = energy;
        this->slotTemperatures = slotTemperatures;
        this->inputRules = inputRules;

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

        return 0;
    }),
    remoteDataComponent(this, [this]() -> DataCableComponent& { return reactor->getComponent<DataCableComponent>(); })
{}

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

int NuclearReactorController::getCellTemperature(glm::ivec2 pos)
{
    if (pos.x < 0 || pos.x >= 8 || pos.y < 0 || pos.y >= 8)
    {
        return 300;
    }

    return slotTemperatures[pos.y * 8 + pos.x];
}

Widget* NuclearReactorController::getRightPanel()
{
    return new Column(
        new Row(
            new Center(new IconText(
                blockRegistry.getBlockByName("main:nuclearReactor").iconMaterial,
                "Nuclear 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(
            new Panel(glm::ivec2(2, 0)),

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

                new TabList::Page(
                    Material::dataInputs,
                    "Data inputs",
                    new DataInputTable(
                        inputRules,
                        remoteDataComponent,
                        std::vector<DataInputTable::VerbOption>{
                            {std::string(MNuclearReactor::Verbs::TurnOn), "Turn on"},
                            {std::string(MNuclearReactor::Verbs::TurnOff), "Turn off"},
                        }
                    )
                )
            )
        )
    );
}

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

    if (id == "machine" && slot == 8*8)
    {
        return Color::rechargeHighlight;
    }

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

        glm::ivec2 cellPos(slot % 8, slot / 8);

        return component->getNuclearReactorSlotHighlight(stack, *this, cellPos);
    }

    IEnergyContainingItem *energyContainer = stack.getItemAs<IEnergyContainingItem>();
    if (energyContainer != nullptr)
    {
        return Color::rechargeHighlight;
    }

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

    return Color::reactorHighlight;
}

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

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

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

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

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

    if (world.isLocal() && reactor != nullptr)
    {
        isSwitchedOn = reactor->isSwitchedOn();
        energy = reactor->getEnergy();
        slotTemperatures = reactor->getSlotTemperatures();
        reactor->getComponent<DataCableComponent>().syncInputRules(inputRules);
        
        updateReactorState(isSwitchedOn, energy, slotTemperatures, inputRules);
    }
}