#ifndef DOC_VIEW_HPP_
#define DOC_VIEW_HPP_

#include <TextFormatter/TextFormatter.hpp>
#include <Widget/Layout/Center/Center.hpp>
#include <Widget/Layout/Column/Column.hpp>
#include <Widget/Layout/CompoundWidget/CompoundWidget.hpp>
#include <Widget/Layout/CompoundWidget/Deck/Deck.hpp>
#include <Widget/Layout/Row/Row.hpp>
#include <Widget/Panel/Panel.hpp>
#include <string>
#include <unordered_map>

/**
 * @brief A widget which displays documents in the "DocLang".
 */
class DocView final : public CompoundWidget
{
private:
    typedef std::function<Widget*(const std::vector<std::string>&)> TagHandler;

    std::unordered_map<std::string, TagHandler> tagHandlers;
    int maxLineWidth;

    template<typename TParagraph = Column>
    Widget* buildLine(const std::string &line)
    {
        TextFormatter<TParagraph, Deck<Row, Center>> formatter(maxLineWidth);

        if (line.empty())
        {
            return new Panel(glm::ivec2(0, 6));
        }

        if (line[0] == '@')
        {
            return new Center(buildTag(line.substr(1)));
        }

        if (line[0] == '*')
        {
            return new Row(
                new Label("●"),
                buildLine(line.substr(1))
            );
        }

        if (line[0] == '#')
        {
            return buildLine<Deck<Column, Center>>(line.substr(1));
        }

        auto it = line.begin();
        while (it != line.end())
        {
            auto tagIt = std::find(it, line.end(), '{');
            if (tagIt != it)
            {
                formatter.addElements(std::string(it, tagIt));
            }

            if (tagIt == line.end())
            {
                break;
            }

            auto endTagIt = std::find(tagIt, line.end(), '}');
            if (endTagIt == line.end())
            {
                break;
            }

            std::string tagBody(tagIt + 1, endTagIt);
            formatter.addElements(buildTag(tagBody));

            it = endTagIt + 1;
        }

        return formatter.build();
    }

    Widget* buildTag(const std::string &tagSpec);
    void parse(const std::string &code);

    template<typename TWidget, typename... TStaticArgs>
    void registerTag(const std::string &tagName, TStaticArgs... staticArgs)
    {
        tagHandlers[tagName] = [staticArgs...](const std::vector<std::string> &args) -> Widget*
        {
            return new TWidget(args, staticArgs...);
        };
    }

public:
    DocView(int maxLineWidth, const std::string &code);
};

#endif