#pragma once

#include "set_cookie_errors.h"

#include <balancer/kernel/cookie/set_cookie_errors.h_serialized.h>

#include <util/generic/bitmap.h>
#include <util/generic/maybe.h>
#include <util/generic/vector.h>
#include <util/generic/xrange.h>
#include <util/memory/blob.h>
#include <util/string/cast.h>

#include <util/generic/serialized_enum.h>

#include <array>
#include <string>

namespace NSrvKernel {

    template <class TErrorEnum>
    TStringBuf ErrorAsStringBuf(TErrorEnum err) noexcept {
        static const auto errNames = ([](){
            std::array<std::string, GetEnumItemsCount<TErrorEnum>()> res;
            for (auto i : xrange<ui32>(res.size())) {
                res[i] = TStringBuf(ToString(TErrorEnum(i)));
            }
            return res;
        })();
        return (ui32)err < errNames.size() ? errNames[(ui32)err] : TStringBuf("INVALID");
    }


    template <class TErrorEnum>
    class [[nodiscard]] TCookieErrors : public TBitMap<GetEnumItemsCount<TErrorEnum>()> {
        using TParent = TBitMap<GetEnumItemsCount<TErrorEnum>()>;
        using TParent::Get;
        using TParent::Set;

    public:
        TCookieErrors() = default;

        TCookieErrors(std::initializer_list<TErrorEnum> errors) {
            for (auto err : errors) {
                Set(err);
            }
        }

        explicit operator bool() const noexcept {
            return !TParent::Empty();
        }

        void Set(TErrorEnum err) noexcept {
            TParent::Set(ui32(err));
        }

        bool Get(TErrorEnum err) const noexcept {
            return TParent::Get(ui32(err));
        }

        template <class TF>
        void ForEach(TF&& f) const noexcept {
            for (auto i : xrange<ui32>(0, GetEnumItemsCount<TErrorEnum>())) {
                f((TErrorEnum)i, Get((TErrorEnum)i));
            }
        }

        template <class TF>
        void ForEachTrue(TF&& f) const noexcept {
            ForEach([&](TErrorEnum e, bool y) {
                if (y) {
                    f(e);
                }
            });
        }

        auto AsVector() const noexcept {
            TVector<TErrorEnum> res(Reserve(TParent::Count()));
            ForEachTrue([&](TErrorEnum e) {
                res.emplace_back(e);
            });
            return res;
        }

        void Print(IOutputStream& out) const noexcept {
            ForEachTrue([&](TErrorEnum e) {
                out.Write(ErrorAsStringBuf(e));
                out.Write(',');
            });
        }

        size_t PrintSz() const noexcept {
            size_t sz = 0;
            ForEachTrue([&](TErrorEnum e) {
                sz += ErrorAsStringBuf(e).size() + 1;
            });
            return sz;
        }

        void Print(TString& out) const noexcept {
            ForEachTrue([&](TErrorEnum e) {
                out.append(ErrorAsStringBuf(e)).append(',');
            });
        }
    };


    using TSetCookieSyntaxErrors = TCookieErrors<ESetCookieSyntaxError>;
    using TSetCookieSemanticErrors = TCookieErrors<ESetCookieSemanticError>;
}
