#pragma once

#include <saas/library/string_utils/tskv_escape.h>
#include <library/cpp/logger/global/global.h>
#include <util/stream/str.h>
#include <library/cpp/string_utils/scan/scan.h>
#include <util/string/strip.h>
#include <util/generic/map.h>
#include <util/generic/hash.h>

namespace NUtil {

template<typename Escaping>
class TKeyValueLogRecordImpl {
public:
    TKeyValueLogRecordImpl(const TString& delimiter, const TString& kvDelimeter)
        : Delimiter(delimiter)
        , KeyValueDelimiter(kvDelimeter)
    {
    }

    template <class T>
    Y_FORCE_INLINE TKeyValueLogRecordImpl<Escaping>& Add(const TString& key, const T& value) {
        if (!!value) {
            DoAdd(key, value);
        }
        return *this;
    }

    template <class T>
    Y_FORCE_INLINE TKeyValueLogRecordImpl<Escaping>& ForceAdd(const TString& key, const T& value) {
        DoAdd(key, value);
        return *this;
    }

    inline TKeyValueLogRecordImpl<Escaping>& AddStandaloneKey(const TString& key) {
        Stream << key;
        return *this;
    }

    inline TString ToString() const {
        return Stream.Str();
    }

protected:
    template <class T>
    inline void DoAdd(const TString& key, const T& value) {
        Stream << Delimiter;
        AddKeyValue(key, value);
    }

private:
    template <class T>
    inline void AddKeyValue(const TString& key, const T& value) {
        Stream << key << KeyValueDelimiter << Escaping::Trait(value);
    }

private:
    TStringStream Stream;
    TString Delimiter;
    TString KeyValueDelimiter;
};

namespace NDetail {
    struct TLogEscapingNone {
        template<typename T>
        static constexpr auto Trait(const T &vl) noexcept {
            return vl;
        }
    };

    struct TLogEscapingStatBox {
        template<typename T>
        static constexpr auto Trait(const T &vl) noexcept {
            return TskvEscaped(vl);
        }
    };
}

using TKeyValueLogRecord = TKeyValueLogRecordImpl<NDetail::TLogEscapingNone>;

class TTSKVRecord: public TKeyValueLogRecordImpl<NDetail::TLogEscapingStatBox> {
public:
    using TBase = TKeyValueLogRecordImpl<NDetail::TLogEscapingStatBox>;

    TTSKVRecord(const TString& format = Default<TString>())
        : TBase("\t", "=")
    {
        AddStandaloneKey("tskv");
        Add("tskv_format", format);
        Add("iname", StdIName);
    }

    TTSKVRecord& AddIsoEventTime();

private:
    static TString StdIName;
public:
    static void SetStdIName(const TString& iname);
    inline static const TString& GetStdIName() {
        return StdIName;
    }
};

class TTSKVRecordParser {
public:
    template <char sep = ';', char sepKV = '='>
    static void Parse(const TString& record, TMap<TString, TString>& result) {
        result.clear();
        const auto saver = [&result](const TStringBuf key, const TStringBuf val) {
            result[(TString)key] = val;
        };
        ScanKeyValue<true, sep, sepKV>(record, saver);
    }
};

class TCSVRemapper {
private:
    THashMap<TString, ui32> NameToId;
    TVector<TStringBuf> Context;
    const char Delim;
public:

    TCSVRemapper(const TVector<TString>& fieldNames, const char delim)
        : Delim(delim)
    {
        for (ui32 i = 0; i < fieldNames.size(); ++i) {
            NameToId[fieldNames[i]] = i;
        }
    }

    const TVector<TStringBuf>& GetContext() const {
        return Context;
    }

    void SetContext(const TString& str) {
        Context.clear();
        TStringBuf buf(str);
        TStringBuf l;
        TStringBuf r;
        while (buf.TrySplit(Delim, l, r)) {
            Context.push_back(l);
            buf = r;
        }
        Context.push_back(buf);
        CHECK_WITH_LOG(Context.size() == NameToId.size()) << Context.size() << "/" << NameToId.size();
    }

    template <class T>
    Y_FORCE_INLINE T GetValue(const TString& fieldName) const {
        try {
            return FromString<T>(StripString(GetValueAsIs(fieldName)));
        } catch (...) {
            ythrow yexception() << "Cannot get " << fieldName << ": " << CurrentExceptionMessage();
        }
    }

    template <class T>
    Y_FORCE_INLINE bool TryGetValue(const TString& fieldName, T& value) const {
        auto it = NameToId.find(fieldName);
        VERIFY_WITH_LOG(it != NameToId.end(), "%s", fieldName.data());
        VERIFY_WITH_LOG(Context.size() > it->second, "%lu / %u", Context.size(), it->second);
        return TryFromString<T>(StripString(Context[it->second]), value);
    }

    Y_FORCE_INLINE TStringBuf GetValueAsIs(const TString& fieldName) const {
        auto it = NameToId.find(fieldName);
        VERIFY_WITH_LOG(it != NameToId.end(), "%s", fieldName.data());
        VERIFY_WITH_LOG(Context.size() > it->second, "%lu / %u", Context.size(), it->second);
        return Context[it->second];
    }
};

}
