#pragma once

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

#include <library/cpp/digest/lower_case/hash_ops.h>
#include <library/cpp/regex/pire/regexp.h>

#include <util/generic/algorithm.h>
#include <util/generic/array_ref.h>
#include <util/generic/hash.h>

namespace NSrvKernel {

    constexpr TStringBuf CRLF = "\r\n";
    constexpr TStringBuf HDRD = ": ";
    constexpr TStringBuf SP = " ";

    class THeaders {
    private:
        using TInnerMapType = THashMap<
            TStringStorage,
            TVector<TStringStorage>,
            TStringStorage::TCIOps,
            TStringStorage::TCIOps>;

    public:
        using iterator = TInnerMapType::iterator;
        using const_iterator = TInnerMapType::const_iterator;

    public:
        THeaders() noexcept = default;

        THeaders(const THeaders& rhs) = default;
        THeaders& operator=(const THeaders& rhs) = default;

        THeaders(THeaders&&) noexcept = default;
        THeaders& operator=(THeaders&&) noexcept = default;

        THeaders UseBuffer() const {
            THeaders ret;
            ret.Buffer_ = Buffer_;
            return std::move(ret);
        }

        //capacity
        size_t Size() const noexcept {
            return std::accumulate(begin(), end(), 0u, [](size_t sum, const auto& header) {
                return sum + header.second.size();
            });
        }

        //modifiers
        void Clear() noexcept {
            Buffer_.Reset();
            Map_.clear();
        }

        void Clear(TString&& newBuffer) noexcept {
            Buffer_ = MakeAtomicShared<TString>(std::move(newBuffer));
            Map_.clear();
        }

        void reserve(size_t size) {
            Map_.reserve(size);
        }

        void erase(const_iterator header) {
            Map_.erase(header->first);
        }

        void Swap(THeaders& rhs) noexcept {
            Buffer_.Swap(rhs.Buffer_);
            Map_.swap(rhs.Map_);
        }

        void Delete(const TStringBuf name) {
            if (auto iter = Map_.find(name); iter != end()) {
                erase(iter);
            }
        }

        void Delete(const TFsm& fsm) {
            EraseNodesIf(*this, [&fsm](auto& header) {
                if (Match(fsm, header.first.AsStringBuf())) {
                    return true;
                } else {
                    return false;
                }
            });
        }

        template <class K, class V>
        void Add(const K& name, const V& value) {
            Y_ASSERT(name);
            if (!name) {
                return;
            }

            Map_[TStringStorage(name)].emplace_back(TStringStorage(value));
        }

        template <class K, class V>
        void Replace(const K& name, const V& value) {
            Y_ASSERT(name);
            if (!name) {
                return;
            }

            Map_[TStringStorage(name)] = {TStringStorage(value)};
        }

        template <class K>
        void Add(const K& name, TVector<TStringStorage> value) {
            Y_ASSERT(name);
            if (!name) {
                return;
            }

            if (!value) {
                return;
            }

            auto& h = Map_[TStringStorage(name)];
            h.reserve(h.size() + value.size());
            for (auto& v : value) {
                h.emplace_back(std::move(v));
            }
        }

        template <class K>
        void Replace(const K& name, TVector<TStringStorage> value) {
            Y_ASSERT(name);
            if (!name) {
                return;
            }

            if (!value) {
                Delete(name);
                return;
            }

            Map_[TStringStorage(name)] = std::move(value);
        }

        void AddFromParser(const TStringBuf name, const TStringBuf value) {
            Y_VERIFY_DEBUG(IsDataOwnedByBuffer(name), "non owning name is not in buffer");
            Y_VERIFY_DEBUG(IsDataOwnedByBuffer(value), "non owning value is not in buffer");
            Map_[name].emplace_back(value);
        }

        iterator FindValues(const TStringBuf name) {
            return Map_.find(name);
        }

        const_iterator FindValues(const TStringBuf name) const {
            return Map_.find(name);
        }

        iterator FindValues(const TFsm& fsm) {
            for (auto it = Map_.begin(); it != Map_.end(); ++it) {
                if (Match(fsm, it->first.AsStringBuf())) {
                    return it;
                }
            }
            return Map_.end();
        }

        const_iterator FindValues(const TFsm& fsm) const {
            for (auto it = Map_.cbegin(); it != Map_.cend(); ++it) {
                if (Match(fsm, it->first.AsStringBuf())) {
                    return it;
                }
            }

            return Map_.cend();
        }

        TVector<TStringStorage> GetValues(const TStringBuf name) const {
            if (auto header = FindValues(name); header != end()) {
                TVector<TStringStorage> ret = header->second;
                for (auto &v : ret) {
                    v.MakeOwned();
                }
                return ret;
            }
            return {};
        }

        TVector<TStringStorage> GetValuesMove(const TStringBuf name) {
            if (auto header = FindValues(name); header != end()) {
                TVector<TStringStorage> ret = std::move(header->second);
                for (auto &v : ret) {
                    v.MakeOwned();
                }
                return std::move(ret);
            }
            return {};
        }

        TVector<TStringStorage> GetValuesMoveNonOwned(const TStringBuf name) {
            if (auto header = FindValues(name); header != end()) {
                return std::move(header->second);
            }
            return {};
        }

        TArrayRef<const TStringStorage> GetValuesRef(const TStringBuf name) const {
            if (auto header = FindValues(name); header != end()) {
                return {header->second.begin(), header->second.end()};
            }
            return {};
        }

        TArrayRef<TStringStorage> GetValuesRef(const TStringBuf name) {
            if (auto header = FindValues(name); header != end()) {
                return {header->second.begin(), header->second.end()};
            }
            return {};
        }

        TStringBuf GetFirstValue(const TStringBuf name) const {
            if (auto it = FindValues(name); it != End() && !it->second.empty()) {
                return it->second[0].AsStringBuf();
            }
            return {};
        }

        TStringBuf GetFirstValue(const TFsm& fsm) const {
            for (auto it = Map_.cbegin(); it != Map_.cend(); ++it) {
                if (Match(fsm, it->first.AsStringBuf())) {
                    return it->second[0].AsStringBuf();
                }
            }
            return {};
        }

        void BuildTo(IOutputStream& out) const noexcept {
            TVector<const_iterator> headers;
            headers.reserve(Map_.size());
            for (auto it = begin(); it != end(); ++it) {
                headers.push_back(it);
            }
            std::sort(headers.begin(), headers.end(), [](const auto& lhs, const auto& rhs) {
                return lhs->first < rhs->first;
            });
            for (const auto& headerIt : headers) {
                const auto& header = *headerIt;
                for (auto& headerValue : header.second) {
                    out << header.first << HDRD << headerValue << CRLF;
                }
            }
        }

        static TVector<TStringStorage> MakeOwned(TVector<TStringStorage> values) {
            for (auto &v : values) {
                v.MakeOwned();
            }
            return values;
        }

        //iterators
        iterator begin() noexcept { return Map_.begin(); }
        iterator end() noexcept { return Map_.end(); }
        const_iterator begin() const noexcept { return Map_.begin(); }
        const_iterator end() const noexcept { return Map_.end(); }
        const_iterator cbegin() const noexcept { return Map_.cbegin(); }
        const_iterator cend() const noexcept { return Map_.cend(); }

        iterator Begin() noexcept { return begin(); }
        iterator End() noexcept { return end(); }
        const_iterator Begin() const noexcept { return cbegin(); }
        const_iterator End() const noexcept { return cend(); }
        const_iterator CBegin() const noexcept { return cbegin(); }
        const_iterator CEnd() const noexcept { return cend(); }

    private:
        bool IsDataOwnedByBuffer(const TStringBuf data) const noexcept {
            const TString* buffer = Buffer_.Get();
            return buffer == nullptr ||
                (buffer->Data() <= data.Data() && data.Data() + data.Size() <= buffer->Data() + buffer->Size());
        }

    private:
        TAtomicSharedPtr<TString> Buffer_;
        TInnerMapType Map_;
    };

} // namespace NSrvKernel
