#pragma once

#include <util/generic/map.h>
#include <util/generic/deque.h>
#include <util/string/builder.h>

#include <mapreduce/yt/interface/client.h>

#include <wmconsole/version3/wmcutil/path.h>

namespace NWebmaster {
namespace NYTUtils {

struct TYaMRRow {
    TYaMRRow() = default;
    TYaMRRow(const TString &key, const TString &subkey, const TString &value)
        : Key(key)
        , SubKey(subkey)
        , Value(value)
    {
    }

    TYaMRRow(const NYT::TYaMRRow &row)
        : Key(row.Key)
        , SubKey(row.SubKey)
        , Value(row.Value)
    {
    }

    operator NYT::TYaMRRow() const {
        NYT::TYaMRRow row;
        row.Key = Key;
        row.SubKey = SubKey;
        row.Value = Value;
        return row;
    }

public:
    TString Key;
    TString SubKey;
    TString Value;
};

struct TTableInfo {
    TTableInfo(const TString &tableName = "")
        : Name(tableName)
    {
    }

    struct TTimeGreater {
        bool operator() (const TTableInfo &x, const TTableInfo &y) const {
            return x.Time > y.Time;
        }
    };

    struct TNameLess {
        bool operator() (const TTableInfo &x, const TTableInfo &y) const {
            return x.Name < y.Name;
        }
    };

    struct TNameGreater {
        bool operator() (const TTableInfo &x, const TTableInfo &y) const {
            return x.Name > y.Name;
        }
    };

public:
    TString Name;
    size_t ChunkCount = 0;
    size_t RecordCount = 0;
    time_t Time = 0;
    size_t DiskSize = 0;
    size_t CompressedSize = 0;
    size_t UncompressedSize = 0;
    size_t ReplicationFactor = 0;
};

struct TResolvedNode {
    TResolvedNode() = default;

    TResolvedNode(const TString &path, const NYT::TNode &value)
        : Path(path)
        , Value(value)
    {
    }

    TResolvedNode(const TString &path, const NYT::TNode &value, const TDeque<TResolvedNode> &attrs)
        : Path(path)
        , Value(value)
    {
        for (const TResolvedNode &attr : attrs) {
            Attributes[attr.Path] = attr.Value;
        }
    }

    NYT::TNode GetAttr(const TString &name) const {
        const auto it = Attributes.find(name);
        if (it != Attributes.end()) {
            return it->second;
        } else {
            ythrow yexception() << "there is no attribute " << name << " in " << Path;
        }
    }

    bool HasAttr(const TString &name) const {
        auto it = Attributes.find(name);
        return it != Attributes.end();
    }

public:
    TString Path;
    NYT::TNode Value;
    THashMap<TString, NYT::TNode> Attributes;
};

void ArchiveTable(NYT::IClientBasePtr client, const TString& table, size_t desiredChunkCount = 500, const TString& eCodec = "lrc_12_2_2", const TString& cCodec = "brotli8");
void DefragTable(NYT::IClientBasePtr client, const TString& table, size_t desiredChunkCount = 500);

template<class C>
void ArgsToContainer(C&) {
}

template<class C, class V, class... TArgs>
void ArgsToContainer(C &cont, const V &value, const TArgs&... args) {
    cont.insert(cont.end(), value);
    ArgsToContainer(cont, args...);
}

void CreateDummyTable(NYT::IClientBasePtr client, const TString &dummyTable, bool overwrite);
void CreatePath(NYT::IClientBasePtr client, const TString &path);
void CreateTable(NYT::IClientBasePtr client, const TString &path, bool overwrite = false);

void DisableLogger();

void DownloadFile(NYT::IClientBasePtr client, const TString& srcPath, const TString& dstPath);
NYT::TTableSchema DropSortOrder(const NYT::TTableSchema &schema);
NYT::TNode GetAttr(NYT::IClientBasePtr client, const TString& table, const TString& attr);
TString GetDirectoryName(const TString &path);
time_t GetIso8601Time(const TString &t);
bool GetLatestTable(NYT::IClientBasePtr client, const TString &tablePrefix, TString &tableName);
time_t GetModificationTime(NYT::IClientBasePtr client, const TString &path);
bool GetTableInfo(NYT::IClientBasePtr client, const TString &tableName, TTableInfo &tableInfo);
size_t GetTableList(NYT::IClientBasePtr client, const TString &tablePrefix, TDeque<TTableInfo> &tableList, size_t maxCount = 500);
size_t GetTableListByPrefix(NYT::IClientBasePtr client, const TString &prefix, TDeque<NYTUtils::TTableInfo> &tablesDst, size_t maxCount = 500);
TString GetObjectName(const TString &path);
TString GetTableName(const TString &path);
NYT::TTableSchema GetTableSchema(NYT::IClientBasePtr client, const NYT::TRichYPath &path);
TString GetTempTable(const TString &prefix, const TString &name);
NYT::TTableSchema GetYaMRSchema();
bool HasAttr(NYT::IClientBasePtr client, const TString &table, const TString &attr);
bool IsTableArchived(NYT::IClientBasePtr client, const TString &table);
bool IsTableSorted(NYT::IClientBasePtr client, const TString& path);

template<typename... Args>
inline TString JoinPath(const TString &lhs, const TString &rhs, const Args&... args) {
    return "//" + NUtils::JoinPath('/', lhs, rhs, args...);
}

void PatchSchema(NYT::TTableSchema &tableSchema, const NYT::TColumnSchema &newColumn);
void PatchSchema(NYT::TTableSchema &tableSchema, const TString columnName, NYT::EValueType valueType);

void RemoveAttr(NYT::IClientBasePtr client, const TString& table, const TString& attr);
bool ResolveTree(NYT::IClientBasePtr client, const TString &path, TDeque<TResolvedNode> &dest, const TDeque<TString> &reqAttrs = TDeque<TString>());

template<class... TArgs>
bool ResolveTree(NYT::IClientBasePtr client, const TString &path, TDeque<TResolvedNode> &dest, const TArgs&... args) {
    TDeque<TString> attrs;
    ArgsToContainer(attrs, args...);
    return ResolveTree(client, path, dest, attrs);
}

void SetArchiveAttr(NYT::IClientBasePtr client, const TString& table, const TString &eCodec, const TString &cCodec);

template<class T>
void SetAttr(NYT::IClientBasePtr client, const TString& table, const TString& attr, const T& value) {
    const TString path = TStringBuilder() << table << "/@" << attr;
    client->Set(path, value);
}

void UploadFile(NYT::IClientBasePtr client, const TString& srcPath, const TString& dstPath);

namespace NInternal {
template<typename T> using NodeCastType = typename std::conditional<std::is_pod<T>::value, T, const T &>::type;
}

inline bool IsNodeNull(const NYT::TNode &node) {
    return node.GetType() == NYT::TNode::Null || node.GetType() == NYT::TNode::Undefined;
}

template<typename T> inline NInternal::NodeCastType<T> FromNode(const NYT::TNode &node);

template<> inline const TString &FromNode<TString>(const NYT::TNode &node) {
    return node.AsString();
}

template<> inline i64 FromNode<i64>(const NYT::TNode &node) {
    return node.AsInt64();
}

template<> inline ui64 FromNode<ui64>(const NYT::TNode &node) {
    return node.AsUint64();
}

template<> inline double FromNode<double>(const NYT::TNode &node) {
    return node.AsDouble();
}

template<> inline bool FromNode<bool>(const NYT::TNode &node) {
    return node.AsBool();
}

template<> inline const NYT::TNode::TListType &FromNode<NYT::TNode::TListType>(const NYT::TNode &node) {
    return node.AsList();
}

template<> inline const NYT::TNode::TMapType &FromNode<NYT::TNode::TMapType>(const NYT::TNode &node) {
    return node.AsMap();
}

template<> inline const NYT::TNode &FromNode<NYT::TNode>(const NYT::TNode &node) {
    return node;
}

template<typename T> NInternal::NodeCastType<T> FromNodeOrDefault(const NYT::TNode &node, NInternal::NodeCastType<T> defaultValue) {
    if (IsNodeNull(node)) {
        return defaultValue;
    } else {
        return FromNode<T>(node);
    }
}

template<typename T> inline NInternal::NodeCastType<T> GetNodeField(const NYT::TNode &node, const TString &fieldName) {
    return FromNode<T>(node[fieldName]);
}

template<typename T> inline NInternal::NodeCastType<T> GetNodeFieldOrDefault(const NYT::TNode &node, const TString &fieldName, NInternal::NodeCastType<T> defaultValue) {
    return FromNodeOrDefault<T>(node[fieldName], defaultValue);
}

template<typename T> inline NInternal::NodeCastType<T> GetRowField(const NYT::TTableReader<NYT::TNode> *reader, const TString &fieldName) {
    return GetNodeField<T>(reader->GetRow(), fieldName);
}

template<typename T> inline NInternal::NodeCastType<T> GetRowFieldOrDefault(const NYT::TTableReader<NYT::TNode> *reader, const TString &fieldName, NInternal::NodeCastType<T> defaultValue) {
    return GetNodeFieldOrDefault<T>(reader->GetRow(), fieldName, defaultValue);
}

template<class T>
T GetAttrOrDefault(NYT::IClientBasePtr client, const TString &table, const TString &attr, const T &defaultValue) {
    const TString path = TStringBuilder() << table << "/@" << attr;
    if (!client->Exists(path)) {
        return defaultValue;
    }

    NYT::TNode node = client->Get(path);
    return NYTUtils::FromNodeOrDefault<T>(node, defaultValue);
}

} //namespace NYTUtils
} //namespace NWebmaster
