#ifndef COMNTOOLS_H
#define COMNTOOLS_H

#include "opstream.h"
#include <QStandardItemModel>
#include <functional>

namespace Tools {

    class Run {
      public:
        Run(bool manual_flag, std::function<bool()> judge,
            std::function<void(bool)> execution);

        void exec();

      private:
        bool manual;
        std::function<bool()> judgement;
        std::function<void(bool)> execution;
    };

    class StatusSyncCore : public QObject {
      public:
        explicit StatusSyncCore(QObject *p = nullptr);
        virtual ~StatusSyncCore() = default;

        void widgetEnableSync(QWidget *tar, std::function<bool()> proc);
        void actionEnableSync(QAction *tar, std::function<bool()> proc);
        void actionCheckSync(QAction *tar, std::function<bool()> proc);
        void registerTrigger(std::function<bool()> judge,
                             std::function<void(bool)> exec);
        Run *registerManualRun(std::function<bool()> judge,
                               std::function<void(bool)> exec);
        void sync();

      private:
        QHash<QWidget *, std::function<bool()>> widget_trigger_map;
        QHash<QAction *, std::function<bool()>> action_trigger_map;
        QList<Run *> alltriggers;
    };

    template <class BaseType> class TreeSyncs {
      public:
        TreeSyncs(
            QStandardItemModel *model_base,
            std::function<bool(const BaseType &n, QStandardItem *p)> _equal,
            std::function<void(const BaseType &n, QStandardItem *p)> sync_proc)
            : model_present(model_base), sync_process(sync_proc),
              compare_process(_equal) {}

        void presentSync(
            std::function<QList<BaseType>(const BaseType &pnode)> items_peak) {
            auto default_pdata = BaseType();
            auto datas = items_peak(default_pdata);

            Operate::OpStream<QStandardItem *>(
                [this](int &cnt, int idx) -> QStandardItem * {
                    cnt = model_present->rowCount();
                    if (cnt <= 0)
                        return nullptr;

                    return model_present->item(idx);
                })
                .filter([&datas, this](QStandardItem *const &it) -> bool {
                    for (auto &d : datas)
                        if (compare_process(d, it))
                            return false;
                    return true;
                })
                .forEach([this](QStandardItem *const &it) {
                    model_present->removeRow(it->row());
                });

            for (auto idx = 0; idx < datas.size(); ++idx) {
                if (idx >= model_present->rowCount()) {
                    auto ins = new QStandardItem();
                    ins->setEditable(false);
                    model_present->appendRow(ins);
                }

                auto &data = datas[idx];
                auto item = model_present->item(idx);
                sync_process(data, item);

                layer_items_sync(data, item, items_peak);
            }
        }

      private:
        QStandardItemModel *model_present;
        std::function<void(const BaseType &n, QStandardItem *p)> sync_process;
        std::function<bool(const BaseType &n, QStandardItem *p)>
            compare_process;

        void layer_items_sync(
            const BaseType &data, QStandardItem *pnode,
            std::function<QList<BaseType>(const BaseType &pnode)> items_peak) {
            auto datas = items_peak(data);

            Operate::OpStream<QStandardItem *>(
                [pnode](int &cnt, int idx) -> QStandardItem * {
                    cnt = pnode->rowCount();
                    return pnode->child(idx);
                })
                .filter([&datas, this](QStandardItem *const &it) -> bool {
                    for (auto &d : datas)
                        if (compare_process(d, it))
                            return false;
                    return true;
                })
                .forEach([pnode](QStandardItem *const &it) {
                    pnode->removeRow(it->row());
                });

            for (auto idx = 0; idx < datas.size(); ++idx) {
                if (idx >= pnode->rowCount()) {
                    auto ins = new QStandardItem();
                    ins->setEditable(false);
                    pnode->appendRow(ins);
                }

                auto &data = datas[idx];
                auto item = pnode->child(idx);
                sync_process(data, item);

                layer_items_sync(data, item, items_peak);
            }
        }
    };
} // namespace Tools

#endif // COMNTOOLS_H
