#ifndef TEXT_FORMATTER_HPP_
#define TEXT_FORMATTER_HPP_

#include <GraphicsContext/GraphicsContext.hpp>
#include <Widget/Label/Label.hpp>
#include <Widget/Layout/Column/Column.hpp>
#include <Widget/Layout/CompoundWidget/CompoundWidget.hpp>
#include <Widget/Layout/Row/Row.hpp>
#include <Widget/Panel/Panel.hpp>
#include <algorithm>
#include <cctype>
#include <sstream>
#include <string>

/**
 * @brief Builder for a widget that displays wrapping text with inline widgets.
 */
template<typename TParagraph = Column, typename TLine = Row>
class TextFormatter final
{
private:
    std::vector<Widget*> paragraphLines;
    std::vector<Widget*> currentLine;
    std::stringstream ssNextLabel;
    int maxLineWidth;
    bool space = false;

    int getCurrentLineWidth()
    {
        Label nextLabel(ssNextLabel.str());
        int totalWidth = nextLabel.getMinSize().x;

        for (Widget *element : currentLine)
        {
            totalWidth += element->getMinSize().x;
        }

        return totalWidth;
    }

    void flushNextLabel()
    {
        if (ssNextLabel.tellp() == 0)
        {
            return;
        }

        currentLine.push_back(new Label(ssNextLabel.str()));
        ssNextLabel.str("");
        ssNextLabel.clear();
    }

    void flushLine()
    {
        flushNextLabel();

        if (currentLine.empty())
        {
            return;
        }

        paragraphLines.push_back(new TLine(currentLine));
        currentLine.clear();
        space = false;
    }

    void addWord(const std::string &word)
    {
        int spaceWidth = space ? GraphicsContext::measureText(Font::generic, " ").x : 0;
        int wordWidth = GraphicsContext::measureText(Font::generic, word).x;
        int currentWidth = getCurrentLineWidth();

        if (currentWidth == 0 || (currentWidth + spaceWidth + wordWidth) <= maxLineWidth)
        {
            if (space)
            {
                ssNextLabel << " ";
                space = false;
            }

            ssNextLabel << word;
            return;
        }

        space = false;
        flushLine();
        addWord(word);
    }

    void addElement(const std::string &text)
    {
        auto it = text.begin();

        while (it != text.end())
        {
            if (*it == ' ')
            {
                space = true;
                it++;
                continue;
            }

            auto endOfWord = std::find(it, text.end(), ' ');
            std::string word(it, endOfWord);
            addWord(word);
            it = endOfWord;
        }
    }

    void addElement(Widget *widget)
    {
        int spaceWidth = space ? GraphicsContext::measureText(Font::generic, " ").x : 0;
        int widgetWidth = widget->getMinSize().x;
        int currentWidth = getCurrentLineWidth();

        if (currentWidth == 0 || (currentWidth + spaceWidth + widgetWidth) <= maxLineWidth)
        {
            addWord("");
            flushNextLabel();
            currentLine.push_back(widget);
            return;
        }

        space = false;
        flushLine();
        addElement(widget);
    }

public:
    void addElements() {}

    template<typename TElement, typename... TRest>
    void addElements(TElement element, TRest... rest)
    {
        addElement(element);
        addElements(rest...);
    }

    TextFormatter(int maxLineWidth) : maxLineWidth(maxLineWidth) {}

    Widget* build()
    {
        flushLine();
        return new TParagraph(
            new Panel(glm::ivec2(maxLineWidth, 0)),
            paragraphLines
        );
    }
};

#endif