#pragma once

#include <balancer/kernel/memory/chunks.h>

#include <util/generic/map.h>
#include <util/generic/strbuf.h>
#include <util/generic/string.h>
#include <util/generic/yexception.h>
#include <util/string/split.h>
#include <util/string/cast.h>
#include <util/string/strip.h>

#include <utility>


namespace NSrvKernel {
    template<typename TKeyValueReaction>
    class TKvFileConsumer: public TKeyValueReaction {
    public:
        using TKeyValueReaction::TKeyValueReaction;

        bool operator()(TStringBuf value) {
            TStringBuf key = value.NextTok(',');
            if (key.empty()) {
                ythrow yexception() << "Empty value";
            }
            return this->OnKeyValue(key, value);
        }
    };

    template<typename TKeyValueReaction>
    void ProcessKvData(TKvFileConsumer<TKeyValueReaction>& consumer, const TChunkPtr& contents) {
        if (contents && contents->Data()) {
            StringSplitter(contents->AsStringBuf()).Split('\n').SkipEmpty().Consume(consumer);
        }
    }

    template<typename TKeyValueReaction>
    void ProcessKvData(TKvFileConsumer<TKeyValueReaction>& consumer, const TStringBuf buf) {
        if (buf) {
            StringSplitter(buf).Split('\n').SkipEmpty().Consume(consumer);
        }
    }

    template<typename TKey, typename TValue, bool stripValue=false>
    class TBaseFileReaction {
    public:
        using TStorage = TMap<TKey, TValue>;
    private:
        TStorage Storage_;
        size_t Idx_ = 1; // Lua arrays typically start with idx=1
    public:
        bool OnKeyValue(const TStringBuf& key, const TStringBuf& origValue) {
            TStringBuf value = origValue;
            if (stripValue) {
                value = StripString(value);
            }
            if (value.empty()) {
                if constexpr(std::is_same_v<TKey, TString>) {
                    Storage_[::ToString(Idx_++)] = FromString<TValue>(key);
                } else if constexpr(std::is_arithmetic_v<TKey>) {
                    Storage_[Idx_++] = FromString<TValue>(key);
                } else {
                    return false;
                }
            } else {
                if constexpr(std::is_same_v<TKey, TString>) {
                    Storage_[::ToString(key)] = FromString<TValue>(value);
                } else if constexpr(std::is_arithmetic_v<TKey>) {
                    Storage_[FromString<TKey>(key)] = FromString<TValue>(value);
                } else {
                    return false;
                }
            }
            return true;
        }

        const TStorage& Storage() const noexcept {
            return Storage_;
        }

        TStorage&& TakeStorage() noexcept {
            return std::move(Storage_);
        }
    };

    // Weights
    using TExternalWeightsFileReaction = TBaseFileReaction<TString, double>;
    using TExternalWeightsFileConsumer = TKvFileConsumer<TExternalWeightsFileReaction>;
}
