#include <Controller/InventoryController/ItemExtractorController/ItemExtractorController.hpp>
#include <Log/Log.hpp>
#include <Machine/IBeltMachine.hpp>
#include <Machine/MItemExtractor/MItemExtractor.hpp>
#include <Widget/EnumSelector/EnumSelector.hpp>
#include <Widget/Layout/Center/Center.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/Switch/Switch.hpp>
#include <Widget/Layout/Row/Row.hpp>
#include <Widget/Layout/Column/HorizontalSeparator/HorizontalSeparator.hpp>
#include <Widget/TabList/TabList.hpp>
#include <World/World.hpp>
#include <Widget/Layout/CompoundWidget/DataInputTable/DataInputTable.hpp>
#include <Widget/ItemListSelector/ItemListSelector.hpp>

void ItemExtractorController::parseModeSpecs(const std::vector<IBeltMachine::ExtractionModeSpec> &modeSpecs)
{
    if (!modeSpecOptions.empty())
    {
        return;
    }

    for (const auto &modeSpec : modeSpecs)
    {
        modeSpecOptions.push_back({modeSpec.modeCode, modeSpec.label});
    }
}

Widget* ItemExtractorController::makeFilterTab()
{
    return new Column(
        new Panel(glm::ivec2(150, 0)),

        new EnumSelector<FilterType>(
            filterType,
            {
                {FilterType::All, "Extract all items"},
                {FilterType::Some, "Extract only these items:"},
                {FilterType::AllExcept, "Extract all items except:"},
            },
            [this]
            {
                setFilterType(filterType);
            }
        ),

        Layout::WithProportion<1>(),
        new ItemListSelector(
            filterItemStacks,
            [this](ItemStack stack)
            {
                addItemFilter(stack);
            },
            [this](int index)
            {
                removeItemFilter(index);
            }
        ),

        new Row(
            new Center(new Label("Mode:")),
            Layout::WithProportion<1>(),
            new Builder<bool>(
                [this]() -> bool
                {
                    return modeSpecs.has_value();
                },
                [this](bool loaded) -> Widget*
                {
                    if (!loaded)
                    {
                        return new Label("...");
                    }

                    return new EnumSelector<uint32_t>(
                        modeCode,
                        modeSpecOptions,
                        [this]()
                        {
                            setModeCode(modeCode);
                        }
                    );
                }
            )
        )
    );
}

ItemExtractorController::ItemExtractorController(ControllerChannel &channel) :
    InventoryController(channel),
    setSwitch(this, RunsOn::Server,
        [this](bool on) -> uint32_t
    {
        if (extractor != nullptr)
        {
            extractor->setSwitch(on);
        }

        return 0;
    }),
    setFilterType(this, RunsOn::Server,
        [this](FilterType type) -> uint32_t
    {
        if (extractor != nullptr)
        {
            extractor->setFilterType(type);
        }

        return 0;
    }),
    addItemFilter(this, RunsOn::Server,
        [this](ItemStack stack) -> uint32_t
    {
        if (extractor != nullptr)
        {
            extractor->addItemFilter(stack);
        }

        return 0;
    }),
    removeItemFilter(this, RunsOn::Server,
        [this](uint32_t index) -> uint32_t
    {
        if (extractor != nullptr)
        {
            extractor->removeItemFilter(index);
        }

        return 0;
    }),
    updateExtractorState(this, RunsOn::Client,
        [this](
            bool on,
            FilterType filterType,
            std::vector<DataCableComponent::InputRule> inputRules,
            int filterUpdateCounter,
            std::vector<ItemStack> filterItemNames,
            uint32_t modeCode
    ) -> uint32_t
    {
        this->switchedOn = on;
        this->filterType = filterType;
        this->inputRules = inputRules;
        this->filterUpdateCounter = filterUpdateCounter;
        this->filterItemStacks = filterItemNames;
        this->modeCode = modeCode;

        return 0;
    }),
    onModeSpecsLoaded(this, RunsOn::Client,
        [this](std::vector<IBeltMachine::ExtractionModeSpec> modeSpecs) -> uint32_t
    {
        this->modeSpecs = modeSpecs;
        parseModeSpecs(*this->modeSpecs);

        return 0;
    }),
    setModeCode(this, RunsOn::Server,
        [this](uint32_t modeCode) -> uint32_t
    {
        if (extractor == nullptr)
        {
            return 1;
        }

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

void ItemExtractorController::onConnect(std::shared_ptr<Machine> itemExtractorMachine)
{
    bindMachine(extractor = std::dynamic_pointer_cast<MItemExtractor>(itemExtractorMachine));
}

Widget* ItemExtractorController::getRightPanel()
{
    return new Column(
        new Row(
            new Label("Item Extractor"),

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

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

        Layout::WithProportion<1>(),
        new TabList(
            "/item-extractor/tab-list",
            new TabList::Page(
                Material::filterIcon,
                "Filters",
                makeFilterTab()
            ),

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

            MachineConfigPanel::page(
                player,
                "main:itemExtractor",
                "/item-extractor/config",
                Material::filterIcon,
                *this
            )
        )
    );
}

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

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

    if (world.isLocal() && extractor != nullptr)
    {
        bool inputRulesUpdated = extractor->getComponent<DataCableComponent>().syncInputRules(inputRules);

        if (
            inputRulesUpdated ||
            switchedOn != extractor->isSwitchedOn() ||
            filterType != extractor->getFilterType() ||
            filterUpdateCounter != extractor->getFilterUpdateCounter() ||
            modeCode != extractor->getModeCode()
        )
        {
            switchedOn = extractor->isSwitchedOn();
            filterType = extractor->getFilterType();
            filterUpdateCounter = extractor->getFilterUpdateCounter();
            filterItemStacks = extractor->getFilterItemStacks();
            modeCode = extractor->getModeCode();
            
            updateExtractorState(switchedOn, filterType, inputRules, filterUpdateCounter, filterItemStacks, modeCode);
        }

        if (!modeSpecs.has_value() && extractor->areModeSpecsLoaded())
        {
            modeSpecs = extractor->getModeSpecs();
            parseModeSpecs(*modeSpecs);
            onModeSpecsLoaded(*modeSpecs);
        }
    }
}