/*
    SPDX-FileCopyrightText: 2022-2025 Igor Mironchik <igor.mironchik@gmail.com>
    SPDX-License-Identifier: MIT
*/

#ifndef MD4QT_MD_ALGO_H_INCLUDED
#define MD4QT_MD_ALGO_H_INCLUDED

// md4qt include.
#include "traits.h"
#include "visitor.h"

// C++ include.
#include <functional>

namespace MD
{

/*!
 * \inmodule md4qt
 * \typealias MD::ItemFunctor
 * \inheaderfile md4qt/algo.h
 *
 * \brief Function type for algorithms.
 */
template<class Trait>
using ItemFunctor = std::function<void(Item<Trait> *)>;

/*!
 * \namespace MD::details
 * \inmodule md4qt
 * \inheaderfile md4qt/algo.h
 *
 * \brief Namespace for some implemetation details, but useful for reuse.
 *
 * Namespace for reusable auxiliary data structures.
 */
namespace details
{

//
// AlgoVisitor
//

/*!
 * \class MD::details::AlgoVisitor
 * \inmodule md4qt
 * \inheaderfile md4qt/algo.h
 *
 * \brief Visitor for algorithms.
 *
 * The main difference from MD::Visitor is that this class during walking through the
 * document takes into account nesting level of the item. If nesting level reached defined value
 * it won't process anything further. And if type of the item is in set types walking through
 * the document will invoke a given function object for that item.
 *
 * \sa MD::Visitor
 */
template<class Trait>
class AlgoVisitor : public Visitor<Trait>
{
public:
    /*!
     * Constructor
     *
     * \a mnl Maximum nesting level
     *
     * \a t List of items' types.
     *
     * \a f Functor.
     */
    AlgoVisitor(unsigned int mnl, const typename Trait::template Vector<ItemType> &t, ItemFunctor<Trait> f)
        : m_maxNestingLevel(mnl)
        , m_types(t)
        , m_func(f)
    {
    }

    ~AlgoVisitor() override = default;

    /*!
     * Walk through the document.
     *
     * \a doc Document.
     */
    virtual void walk(std::shared_ptr<Document<Trait>> doc)
    {
        this->process(doc);

        onFootnotes(doc);
    }

protected:
    void onAddLineEnding() override
    {
    }

    /*!
     * Process user defined item type.
     *
     * \a i Item.
     */
    void onUserDefined(Item<Trait> *i) override
    {
        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(i->type())) {
            m_func(i);
        }
    }

    /*!
     * Process text item.
     *
     * \a t Text.
     */
    void onText(Text<Trait> *t) override
    {
        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::Text)) {
            m_func(t);
        }
    }

    /*!
     * Process LaTeX math item.
     *
     * \a m Math.
     */
    void onMath(Math<Trait> *m) override
    {
        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::Math)) {
            m_func(m);
        }
    }

    /*!
     * Process line break.
     *
     * \a l Line break.
     */
    void onLineBreak(LineBreak<Trait> *l) override
    {
        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::LineBreak)) {
            m_func(l);
        }
    }

    /*!
     * Process paragraph.
     *
     * \a p Paragraph.
     *
     * \a skipOpeningWrap Indicates that opening wrap should be added or no.
     */
    void onParagraph(Paragraph<Trait> *p, bool, bool skipOpeningWrap = false) override
    {
        MD_UNUSED(skipOpeningWrap)

        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::Paragraph)) {
            m_func(p);
        }

        if (inc.nextAllowed()) {
            Visitor<Trait>::onParagraph(p, true, skipOpeningWrap);
        }
    }

    /*!
     * Process heading.
     *
     * \a h Heading.
     */
    void onHeading(Heading<Trait> *h) override
    {
        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::Heading)) {
            m_func(h);
        }

        if (h->text() && !h->text()->isEmpty() && inc.nextAllowed()) {
            onParagraph(h->text().get(), true);
        }
    }

    /*!
     * Process code block.
     *
     * \a c Code.
     */
    void onCode(Code<Trait> *c) override
    {
        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::Code)) {
            m_func(c);
        }
    }

    /*!
     * Process inline code.
     *
     * \a c Code.
     */
    void onInlineCode(Code<Trait> *c) override
    {
        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::Code)) {
            m_func(c);
        }
    }

    /*!
     * Process blockquote.
     *
     * \a b Blockquote.
     */
    void onBlockquote(Blockquote<Trait> *b) override
    {
        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::Blockquote)) {
            m_func(b);
        }

        if (inc.nextAllowed()) {
            Visitor<Trait>::onBlockquote(b);
        }
    }

    /*!
     * Process list.
     *
     * \a l List.
     */
    void onList(List<Trait> *l) override
    {
        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::List)) {
            m_func(l);
        }

        for (auto it = l->items().cbegin(), last = l->items().cend(); it != last; ++it) {
            if ((*it)->type() == ItemType::ListItem && inc.nextAllowed()) {
                onListItem(static_cast<ListItem<Trait> *>(it->get()), true);
            }
        }
    }

    /*!
     * Process table.
     *
     * \a t Table.
     */
    void onTable(Table<Trait> *t) override
    {
        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::Table)) {
            m_func(t);
        }

        if (!t->isEmpty() && inc.nextAllowed()) {
            int columns = 0;

            for (auto th = (*t->rows().cbegin())->cells().cbegin(), last = (*t->rows().cbegin())->cells().cend(); th != last; ++th) {
                this->onTableCell(th->get());

                ++columns;
            }

            for (auto r = std::next(t->rows().cbegin()), rlast = t->rows().cend(); r != rlast; ++r) {
                int i = 0;

                for (auto c = (*r)->cells().cbegin(), clast = (*r)->cells().cend(); c != clast; ++c) {
                    this->onTableCell(c->get());

                    ++i;

                    if (i == columns) {
                        break;
                    }
                }
            }
        }
    }

    /*!
     * Process anchor.
     *
     * \a a Anchor.
     */
    void onAnchor(Anchor<Trait> *a) override
    {
        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::Anchor)) {
            m_func(a);
        }
    }

    /*!
     * Process raw HTML block.
     *
     * \a h Raw HTML.
     */
    void onRawHtml(RawHtml<Trait> *h) override
    {
        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::RawHtml)) {
            m_func(h);
        }
    }

    /*!
     * Process horizontal line.
     *
     * \a l Horizontal line.
     */
    void onHorizontalLine(HorizontalLine<Trait> *l) override
    {
        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::HorizontalLine)) {
            m_func(l);
        }
    }

    /*!
     * Process link.
     *
     * \a l Link.
     */
    void onLink(Link<Trait> *l) override
    {
        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::Link)) {
            m_func(l);
        }

        if (inc.nextAllowed()) {
            if (!l->img()->isEmpty()) {
                onImage(l->img().get());
            } else if (l->p() && !l->p()->isEmpty()) {
                onParagraph(l->p().get(), false);
            }
        }
    }

    /*!
     * Process Image.
     *
     * \a i Image.
     */
    void onImage(Image<Trait> *i) override
    {
        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::Image)) {
            m_func(i);
        }
    }

    /*!
     * Process footnote reference.
     *
     * \a ref Footnote reference.
     */
    void onFootnoteRef(FootnoteRef<Trait> *ref) override
    {
        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::FootnoteRef)) {
            m_func(ref);
        }
    }

    /*!
     * Process list item.
     *
     * \a i List item.
     *
     * \a skipOpeningWrap Indicates that opening wrap should be added or no.
     */
    void onListItem(ListItem<Trait> *i, bool, bool skipOpeningWrap = false) override
    {
        MD_UNUSED(skipOpeningWrap)

        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::ListItem)) {
            m_func(i);
        }

        if (inc.nextAllowed()) {
            Visitor<Trait>::onListItem(i, true);
        }
    }

    /*!
     * Process table cell.
     *
     * \a c Table cell.
     */
    void onTableCell(TableCell<Trait> *c) override
    {
        Visitor<Trait>::onTableCell(c);
    }

    /*!
     * Process footnote.
     *
     * \a f Footnote.
     */
    void onFootnote(Footnote<Trait> *f) override
    {
        IncrementNestingLevel inc(m_currentNestingLevel, m_maxNestingLevel, m_types);

        if (inc.allowed(ItemType::Footnote)) {
            m_func(f);
        }

        if (inc.nextAllowed()) {
            Visitor<Trait>::onFootnote(f);
        }
    }

    /*!
     * Process all footnotes.
     *
     * \a doc Document.
     */
    virtual void onFootnotes(std::shared_ptr<Document<Trait>> doc)
    {
        for (const auto &f : doc->footnotesMap()) {
            this->onFootnote(f.second.get());
        }
    }

protected:
    /*!
     * Current nesting level, increments/decrements during walking through te document.
     */
    unsigned int m_currentNestingLevel = 0;
    /*!
     * Maximum allowed nesting level.
     */
    unsigned int m_maxNestingLevel = 0;
    /*!
     * List of types that should be processed.
     */
    const typename Trait::template Vector<ItemType> &m_types;
    /*!
     * Functor that will be invoked if all circumstances will be observed.
     */
    ItemFunctor<Trait> m_func = {};

    /*!
     * \inmodule md4qt
     *
     * \brief Auxiliary structure for MD::details::AlgoVisitor.
     *
     * Auxiliary structure to increment/decrement nesting level during walking through
     * the document, and checking circumstances for the algorithm.
     */
    struct IncrementNestingLevel {
        /*!
         * Constructor.
         *
         * \a l Level.
         *
         * \a m Max nesting level.
         *
         * \a t List of types.
         */
        IncrementNestingLevel(unsigned int &l, unsigned int m, const typename Trait::template Vector<ItemType> &t)
            : m_level(l)
            , m_maxNestingLevel(m)
            , m_types(t)
        {
            ++m_level;
        }

        ~IncrementNestingLevel()
        {
            --m_level;
        }

        /*!
         * Returns whether this item type allowed at this nesting level.
         *
         * \a t Item's type to check.
         */
        bool allowed(ItemType t) const
        {
            return ((m_maxNestingLevel == 0 || m_level <= m_maxNestingLevel) && std::find(m_types.cbegin(), m_types.cend(), t) != m_types.cend());
        }

        /*!
         * Returns whether next nesting level allowed.
         */
        bool nextAllowed() const
        {
            return (m_maxNestingLevel == 0 || m_level + 1 <= m_maxNestingLevel);
        }

        /*!
         * Reference to nesting level.
         */
        unsigned int &m_level;
        /*!
         * Maximum allowed nesting level.
         */
        unsigned int m_maxNestingLevel;
        /*!
         * Reference to list of allowed types.
         */
        const typename Trait::template Vector<ItemType> &m_types;
    }; // struct IncrementNestingLevel
}; // class HtmlVisitor

} /* namespace details */

/*!
 * \inheaderfile md4qt/algo.h
 *
 * \brief Calls function for each item in the document with the given type.
 *
 * \a types Vector of item's types to be processed.
 *
 * \a doc Document.
 *
 * \a func Functor object.
 *
 * \a maxNestingLevel Maximun nesting level. 0 means infinity, 1 - only top level items...
 */
template<class Trait>
inline void
forEach(const typename Trait::template Vector<ItemType> &types, std::shared_ptr<Document<Trait>> doc, ItemFunctor<Trait> func, unsigned int maxNestingLevel = 0)
{
    details::AlgoVisitor<Trait> v(maxNestingLevel, types, func);

    v.walk(doc);
}

} /* namespace MD */

#endif // MD4QT_MD_ALGO_H_INCLUDED
