#include "box.hpp"

#include <MyGUI_EditText.h>

namespace Gui
{
    void AutoSizedWidget::notifySizeChange(MyGUI::Widget* w)
    {
        MyGUI::Widget* parent = w->getParent();
        if (parent != nullptr)
        {
            if (mExpandDirection.isLeft())
            {
                int hdiff = getRequestedSize().width - w->getSize().width;
                w->setPosition(w->getPosition() - MyGUI::IntPoint(hdiff, 0));
            }
            w->setSize(getRequestedSize());

            while (parent != nullptr)
            {
                Box* b = dynamic_cast<Box*>(parent);
                if (b)
                    b->notifyChildrenSizeChanged();
                else
                    break;
                parent = parent->getParent();
            }
        }
    }

    MyGUI::IntSize AutoSizedTextBox::getRequestedSize()
    {
        return getCaption().empty() ? MyGUI::IntSize{ 0, 0 } : getTextSize();
    }

    void AutoSizedTextBox::setCaption(const MyGUI::UString& _value)
    {
        TextBox::setCaption(_value);

        notifySizeChange(this);
    }

    void AutoSizedTextBox::setPropertyOverride(std::string_view _key, std::string_view _value)
    {
        if (_key == "ExpandDirection")
        {
            mExpandDirection = MyGUI::Align::parse(_value);
        }
        else
        {
            Gui::TextBox::setPropertyOverride(_key, _value);
        }
    }

    int AutoSizedEditBox::getWidth()
    {
        // If the widget has the one short text line, we can shrink widget to avoid a lot of empty space.
        int textWidth = mMaxWidth;

        if (mShrink)
        {
            // MyGUI needs to know the widget size for wordwrapping, but we will know the widget size only after
            // wordwrapping. To solve this issue, use the maximum tooltip width first for wordwrapping, then resize
            // widget to its actual used space.
            if (mWasResized)
            {
                int maxLineWidth = 0;
                const MyGUI::VectorLineInfo& lines = getSubWidgetText()->castType<MyGUI::EditText>()->getLineInfo();
                for (unsigned int i = 0; i < lines.size(); ++i)
                    maxLineWidth = std::max(maxLineWidth, lines[i].width);

                textWidth = std::min(maxLineWidth, textWidth);
            }
            else
            {
                mWasResized = true;
            }
        }

        return textWidth;
    }

    MyGUI::IntSize AutoSizedEditBox::getRequestedSize()
    {
        if (getAlign().isHStretch())
            throw std::runtime_error("AutoSizedEditBox can't have HStretch align (" + getName() + ")");
        return MyGUI::IntSize(getWidth(), getTextSize().height);
    }

    void AutoSizedEditBox::setCaption(const MyGUI::UString& _value)
    {
        EditBox::setCaption(_value);
        mWasResized = false;

        notifySizeChange(this);
    }

    void AutoSizedEditBox::initialiseOverride()
    {
        mMaxWidth = getSize().width;
        Base::initialiseOverride();
        setNeedKeyFocus(false);
        setEditStatic(true);
    }

    void AutoSizedEditBox::setPropertyOverride(std::string_view _key, std::string_view _value)
    {
        if (_key == "ExpandDirection")
        {
            mExpandDirection = MyGUI::Align::parse(_value);
        }
        else if (_key == "Shrink")
        {
            mShrink = MyGUI::utility::parseValue<bool>(_value);
        }
        else
        {
            Gui::EditBox::setPropertyOverride(_key, _value);
        }
    }

    MyGUI::IntSize AutoSizedButton::getRequestedSize()
    {
        MyGUI::IntSize padding(24, 8);
        if (isUserString("TextPadding"))
            padding = MyGUI::IntSize::parse(getUserString("TextPadding"));

        MyGUI::IntSize size = getTextSize() + MyGUI::IntSize(padding.width, padding.height);
        return size;
    }

    void AutoSizedButton::setCaption(const MyGUI::UString& _value)
    {
        Button::setCaption(_value);

        notifySizeChange(this);
    }

    void AutoSizedButton::setPropertyOverride(std::string_view _key, std::string_view _value)
    {
        if (_key == "ExpandDirection")
        {
            mExpandDirection = MyGUI::Align::parse(_value);
        }
        else
        {
            Gui::Button::setPropertyOverride(_key, _value);
        }
    }

    Box::Box()
        : mSpacing(4)
        , mPadding(0)
        , mAutoResize(false)
    {
    }

    void Box::notifyChildrenSizeChanged()
    {
        align();
    }

    bool Box::_setPropertyImpl(std::string_view _key, std::string_view _value)
    {
        if (_key == "Spacing")
            mSpacing = MyGUI::utility::parseValue<int>(_value);
        else if (_key == "Padding")
            mPadding = MyGUI::utility::parseValue<int>(_value);
        else if (_key == "AutoResize")
            mAutoResize = MyGUI::utility::parseValue<bool>(_value);
        else
            return false;

        return true;
    }

    void HBox::align()
    {
        unsigned int count = getChildCount();
        size_t h_stretched_count = 0;
        int total_width = 0;
        int total_height = 0;
        std::vector<std::pair<MyGUI::IntSize, bool>> sizes;
        sizes.resize(count);

        for (unsigned int i = 0; i < count; ++i)
        {
            MyGUI::Widget* w = getChildAt(i);
            bool hstretch = w->getUserString("HStretch") == "true";
            bool hidden = w->getUserString("Hidden") == "true";
            if (hidden)
                continue;
            h_stretched_count += hstretch;
            AutoSizedWidget* aw = dynamic_cast<AutoSizedWidget*>(w);
            if (aw)
            {
                sizes[i] = std::make_pair(aw->getRequestedSize(), hstretch);
                total_width += aw->getRequestedSize().width;
                total_height = std::max(total_height, aw->getRequestedSize().height);
            }
            else
            {
                sizes[i] = std::make_pair(w->getSize(), hstretch);
                total_width += w->getSize().width;
                if (!(w->getUserString("VStretch") == "true"))
                    total_height = std::max(total_height, w->getSize().height);
            }

            if (i != count - 1)
                total_width += mSpacing;
        }

        if (mAutoResize
            && (total_width + mPadding * 2 != getClientCoord().width
                || total_height + mPadding * 2 != getClientCoord().height))
        {
            int xmargin = getSize().width - getClientCoord().width;
            int ymargin = getSize().height - getClientCoord().height;
            setSize(MyGUI::IntSize(total_width + mPadding * 2 + xmargin, total_height + mPadding * 2 + ymargin));
            return;
        }

        int curX = 0;
        for (unsigned int i = 0; i < count; ++i)
        {
            if (i == 0)
                curX += mPadding;

            MyGUI::Widget* w = getChildAt(i);

            bool hidden = w->getUserString("Hidden") == "true";
            if (hidden)
                continue;

            bool vstretch = w->getUserString("VStretch") == "true";
            int max_height = getClientCoord().height - mPadding * 2;
            int height = vstretch ? max_height : sizes[i].first.height;

            MyGUI::IntCoord widgetCoord;
            widgetCoord.left = curX;
            widgetCoord.top = mPadding + (getClientCoord().height - mPadding * 2 - height) / 2;

            int width = 0;
            if (sizes[i].second)
            {
                if (h_stretched_count == 0)
                    throw std::logic_error("unexpected");
                width
                    = sizes[i].first.width + (getClientCoord().width - mPadding * 2 - total_width) / h_stretched_count;
            }
            else
                width = sizes[i].first.width;

            widgetCoord.width = width;
            widgetCoord.height = height;
            w->setCoord(widgetCoord);
            curX += width;

            if (i != count - 1)
                curX += mSpacing;
        }
    }

    void HBox::setPropertyOverride(std::string_view _key, std::string_view _value)
    {
        if (!Box::_setPropertyImpl(_key, _value))
            MyGUI::Widget::setPropertyOverride(_key, _value);
    }

    void HBox::setSize(const MyGUI::IntSize& _value)
    {
        MyGUI::Widget::setSize(_value);
        align();
    }

    void HBox::setCoord(const MyGUI::IntCoord& _value)
    {
        MyGUI::Widget::setCoord(_value);
        align();
    }

    void HBox::initialiseOverride()
    {
        Base::initialiseOverride();
        MyGUI::Widget* client = nullptr;
        assignWidget(client, "Client");
        setWidgetClient(client);
    }

    void HBox::onWidgetCreated(MyGUI::Widget* _widget)
    {
        align();
    }

    MyGUI::IntSize HBox::getRequestedSize()
    {
        MyGUI::IntSize size(0, 0);
        for (unsigned int i = 0; i < getChildCount(); ++i)
        {
            bool hidden = getChildAt(i)->getUserString("Hidden") == "true";
            if (hidden)
                continue;

            AutoSizedWidget* w = dynamic_cast<AutoSizedWidget*>(getChildAt(i));
            if (w)
            {
                MyGUI::IntSize requested = w->getRequestedSize();
                size.height = std::max(size.height, requested.height);
                size.width = size.width + requested.width;
                if (i != getChildCount() - 1)
                    size.width += mSpacing;
            }
            else
            {
                MyGUI::IntSize requested = getChildAt(i)->getSize();
                size.height = std::max(size.height, requested.height);

                if (getChildAt(i)->getUserString("HStretch") != "true")
                    size.width = size.width + requested.width;

                if (i != getChildCount() - 1)
                    size.width += mSpacing;
            }
            size.height += mPadding * 2;
            size.width += mPadding * 2;
        }
        return size;
    }

    void VBox::align()
    {
        unsigned int count = getChildCount();
        size_t v_stretched_count = 0;
        int total_height = 0;
        int total_width = 0;
        std::vector<std::pair<MyGUI::IntSize, bool>> sizes;
        sizes.resize(count);
        for (unsigned int i = 0; i < count; ++i)
        {
            MyGUI::Widget* w = getChildAt(i);

            bool hidden = w->getUserString("Hidden") == "true";
            if (hidden)
                continue;

            bool vstretch = w->getUserString("VStretch") == "true";
            v_stretched_count += vstretch;
            AutoSizedWidget* aw = dynamic_cast<AutoSizedWidget*>(w);
            if (aw)
            {
                sizes[i] = std::make_pair(aw->getRequestedSize(), vstretch);
                total_height += aw->getRequestedSize().height;
                total_width = std::max(total_width, aw->getRequestedSize().width);
            }
            else
            {
                sizes[i] = std::make_pair(w->getSize(), vstretch);
                total_height += w->getSize().height;

                if (!(w->getUserString("HStretch") == "true"))
                    total_width = std::max(total_width, w->getSize().width);
            }

            if (i != count - 1)
                total_height += mSpacing;
        }

        if (mAutoResize
            && (total_width + mPadding * 2 != getClientCoord().width
                || total_height + mPadding * 2 != getClientCoord().height))
        {
            int xmargin = getSize().width - getClientCoord().width;
            int ymargin = getSize().height - getClientCoord().height;
            setSize(MyGUI::IntSize(total_width + mPadding * 2 + xmargin, total_height + mPadding * 2 + ymargin));
            return;
        }

        int curY = 0;
        for (unsigned int i = 0; i < count; ++i)
        {
            if (i == 0)
                curY += mPadding;

            MyGUI::Widget* w = getChildAt(i);

            bool hidden = w->getUserString("Hidden") == "true";
            if (hidden)
                continue;

            bool hstretch = w->getUserString("HStretch") == "true";
            int maxWidth = getClientCoord().width - mPadding * 2;
            int width = hstretch ? maxWidth : sizes[i].first.width;

            MyGUI::IntCoord widgetCoord;
            widgetCoord.top = curY;
            widgetCoord.left = mPadding + (getClientCoord().width - mPadding * 2 - width) / 2;

            int height = 0;
            if (sizes[i].second)
            {
                if (v_stretched_count == 0)
                    throw std::logic_error("unexpected");
                height = sizes[i].first.height
                    + (getClientCoord().height - mPadding * 2 - total_height) / v_stretched_count;
            }
            else
                height = sizes[i].first.height;

            widgetCoord.height = height;
            widgetCoord.width = width;
            w->setCoord(widgetCoord);
            curY += height;

            if (i != count - 1)
                curY += mSpacing;
        }
    }

    void VBox::setPropertyOverride(std::string_view _key, std::string_view _value)
    {
        if (!Box::_setPropertyImpl(_key, _value))
            MyGUI::Widget::setPropertyOverride(_key, _value);
    }

    void VBox::setSize(const MyGUI::IntSize& _value)
    {
        MyGUI::Widget::setSize(_value);
        align();
    }

    void VBox::setCoord(const MyGUI::IntCoord& _value)
    {
        MyGUI::Widget::setCoord(_value);
        align();
    }

    void VBox::initialiseOverride()
    {
        Base::initialiseOverride();
        MyGUI::Widget* client = nullptr;
        assignWidget(client, "Client");
        setWidgetClient(client);
    }

    MyGUI::IntSize VBox::getRequestedSize()
    {
        MyGUI::IntSize size(0, 0);
        for (unsigned int i = 0; i < getChildCount(); ++i)
        {
            bool hidden = getChildAt(i)->getUserString("Hidden") == "true";
            if (hidden)
                continue;

            AutoSizedWidget* w = dynamic_cast<AutoSizedWidget*>(getChildAt(i));
            if (w)
            {
                MyGUI::IntSize requested = w->getRequestedSize();
                size.width = std::max(size.width, requested.width);
                size.height = size.height + requested.height;
                if (i != getChildCount() - 1)
                    size.height += mSpacing;
            }
            else
            {
                MyGUI::IntSize requested = getChildAt(i)->getSize();
                size.width = std::max(size.width, requested.width);

                if (getChildAt(i)->getUserString("VStretch") != "true")
                    size.height = size.height + requested.height;

                if (i != getChildCount() - 1)
                    size.height += mSpacing;
            }
            size.height += mPadding * 2;
            size.width += mPadding * 2;
        }
        return size;
    }

    void VBox::onWidgetCreated(MyGUI::Widget* _widget)
    {
        align();
    }

    Spacer::Spacer()
    {
        setUserString("HStretch", "true");
        setUserString("VStretch", "true");
    }

}
