#include "embed.h"

#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/io/coded_stream.h>
#include <contrib/libs/yaml-cpp/include/yaml-cpp/yaml.h>

#include <library/cpp/json/json_writer.h>

#include <util/system/unaligned_mem.h>

template <typename T, typename... Ts, typename F>
static inline auto MapScalar(const YAML::Node& node, F&& f) {
    T value;
    if constexpr (sizeof...(Ts) != 0) {
        // yaml-cpp marks unquoted strings with a default tag of "?". Quoted ones get "!".
        return node.Tag() == "?" && YAML::convert<decltype(value)>::decode(node, value)
            ? f(std::move(value)) : MapScalar<Ts...>(node, f);
    }
    return f(node.as<T>());
}

TString NSv::NAppHost::YamlToJson(const YAML::Node& node) {
    auto convert = [](auto& convert, const YAML::Node& v) -> NJson::TJsonValue {
        if (v.IsScalar()) {
            return MapScalar<bool, ui64, i64, double, std::string>(v, [](auto c) {
                return NJson::TJsonValue{c};
            });
        }
        NJson::TJsonValue result;
        for (const auto& item : v) {
            if (v.IsSequence()) {
                result.AppendValue(convert(convert, item));
            } else if (item.first.IsScalar()) {
                result[item.first.Scalar()] = convert(convert, item.second);
            } else {
                throw YAML::Exception(item.first.Mark(), "JSON keys can only be strings");
            }
        }
        return result;
    };
    return NJson::WriteJson(convert(convert, node), false);
}

TString NSv::NAppHost::YamlToProto(const YAML::Node& node) {
    TString result;
    google::protobuf::io::StringOutputStream so(&result);
    google::protobuf::io::CodedOutputStream co(&so);
    for (const auto& kv : node) {
        auto writeV = [&, tag = kv.first.as<size_t>()](const auto& v) {
            if constexpr (std::is_same<std::decay_t<decltype(v)>, std::string>::value
                       || std::is_same<std::decay_t<decltype(v)>, TString>::value) {
                co.WriteTag(tag << 3 | 2);
                co.WriteVarint64(v.size());
                co.WriteRaw(v.data(), v.size());
            } else if constexpr (std::is_same<std::decay_t<decltype(v)>, double>::value) {
                co.WriteTag(tag << 3 | 1);
                co.WriteLittleEndian64(ReadUnaligned<ui64>(&v));
            } else {
                co.WriteTag(tag << 3 | 0);
                co.WriteVarint64(v);
            }
        };
        auto writeN = [&](const YAML::Node& v) {
            return v.IsMap() ? writeV(YamlToProto(v)) : MapScalar<bool, ui64, i64, double, std::string>(v, writeV);
        };
        if (!kv.second.IsSequence()) {
            writeN(kv.second);
        } else for (const auto& value : kv.second) {
            if (value.IsSequence()) {
                throw YAML::Exception(value.Mark(), "must be a scalar or a map");
            }
            writeN(value);
        }
    }
    return result;
}
