#pragma once

#include "iface.h"

#include <balancer/kernel/regexp/regexp_pire.h>

#include <library/cpp/containers/stack_array/stack_array.h>

#include <unordered_set>
#include <utility>

#include <util/generic/algorithm.h>
#include <util/generic/maybe.h>
#include <util/stream/str.h>

namespace NSrvKernel {
    class TEventHandler {
        class THandle {
        public:
            template <typename TFunc>
            THandle(TString regexp, const TEventHandler* origin, TFunc func)
                : Regexp_(std::move(regexp))
                , Func_(func)
                , Origin_(origin)
            {
            }

            void Call(TEventData& data) const noexcept {
                Func_(data);
            }

            const TString& Regexp() const noexcept {
                return Regexp_;
            }

            const TEventHandler* Origin() const noexcept {
                return Origin_;
            }

        private:
            TString Regexp_;
            std::function<void(TEventData& data)> Func_;
            const TEventHandler* Origin_;
        };

        template <class F>
        void Trigger(TEventData& event, F& f) const noexcept {
            if (!Fsm_) {
                return;
            }

            TMatcher matcher(*Fsm_);
            matcher.Match(event.Event());

            if (matcher.Final()) {
                const TMatcher::TMatchedRegexps matched = matcher.MatchedRegexps();
                const size_t matchedCount = (matched.second - matched.first);

                if (matchedCount == 0) {
                    event.RawOut() << "no handler for '" << event.Event() << "'" << Endl;
                    return;
                }

                TVector<size_t> sorted(matchedCount);
                Copy(matched.first, matched.second, sorted.begin());
                Sort(sorted.begin(), sorted.end());

                for (size_t i : sorted) {
                    const auto& toCall = *HandlersBySection_.FindPtr(Sections_[i]);
                    for (const auto& handler : toCall) {
                        f(handler);
                    }
                }
            }
        }

    public:
        template <class TFunc, class TArg>
        void Register(TString regexp, TString, TFunc&& func, TArg&& arg) {
            auto& handlers = HandlersBySection_[regexp];
            if (handlers.empty()) {
                AddSection(regexp);
            }
            handlers.emplace_back(std::move(regexp), this,
                                  [func, arg](TEventData& event) { return std::invoke(func, arg, event); });
        }

        void Unite(const TEventHandler& right) {
            for (const auto& section : right.Sections_) {
                auto& handlers = HandlersBySection_[section];
                if (handlers.empty()) {
                    AddSection(section);
                }
                const auto& toAdd = *right.HandlersBySection_.FindPtr(section);
                handlers.insert(handlers.end(), toAdd.begin(), toAdd.end());
            }
        }

        void Remove(const THashSet<const TEventHandler*>& sources) {
            THashSet<TString> erasedSections;
            for (auto& h : HandlersBySection_) {
                auto end = std::remove_if(h.second.begin(), h.second.end(), [&sources](const THandle& handle) {
                    return sources.contains(handle.Origin());
                });
                if (end != h.second.end()) {
                    h.second.erase(end, h.second.end());
                    if (h.second.empty()) {
                        erasedSections.emplace(h.first);
                    }
                }
            }

            if (!erasedSections.empty()) {
                Sections_.erase(
                    std::remove_if(Sections_.begin(), Sections_.end(), [&erasedSections](const TString &section) {
                        return erasedSections.contains(section);
                    }),
                    Sections_.end()
                );
                Fsm_.Clear();
                for (const auto& section : Sections_) {
                    const TFsm fsm(section);
                    Fsm_ = Fsm_ ? TFsm::Glue(*Fsm_, fsm) : fsm;
                }
            }
        }

        void Call(TEventData& event) const noexcept {
            auto f = [&event](const THandle& handler) { return handler.Call(event); };
            Trigger(event, f);
        }

        void CallJson(TEventData& event) const noexcept {
            event.Out().OpenArray();
            auto f = [&event](const THandle& handler) { return handler.Call(event); };
            Trigger(event, f);
            event.Out().CloseArray();
            event.Out().Flush();
        }

        TVector<TString> List() const noexcept {
            TVector<TString> result;
            for (const auto& section: Sections_) {
                result.push_back(TString::Join("/", section));
            }
            return result;
        }

        bool Empty() const noexcept {
            return Fsm_.Empty();
        }

    private:
        void AddSection(const TString& regexp) {
            const TFsm fsm(regexp);
            Fsm_ = Fsm_ ? TFsm::Glue(*Fsm_, fsm) : fsm;
            Sections_.emplace_back(regexp);
        }
        TMaybe<TFsm> Fsm_;
        TVector<TString> Sections_;
        THashMap<TString, TVector<THandle>> HandlersBySection_;
    };
}

#define PARSE_EVENTS                                  \
    if (key == "events") {                            \
        this->ParseEventConfig(value->AsSubConfig()); \
        return;                                       \
    }
