#pragma once

#include <util/generic/yexception.h>
#include <util/generic/string.h>
#include <util/generic/strbuf.h>
#include <util/string/cast.h>
#include <util/stream/output.h>
#include <util/stream/str.h>
#include <util/system/shellcommand.h>

#include <contrib/libs/rapidjson/include/rapidjson/reader.h>
#include <contrib/libs/rapidjson/include/rapidjson/document.h>
#include <contrib/libs/rapidjson/include/rapidjson/error/error.h>
#include <contrib/libs/rapidjson/include/rapidjson/error/en.h>

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

struct TYtOptions {
    TString ytCluster;
    TString ytInputTable;
    TString ytOutputTable;
    TString ytHeaderFile;
    TString ytSecretsFile;
    TString tsharkPath;
    TString libsPath;
    TString luaPath;
    TString httpHeadersPath;
    TString http2HeadersPath;
    uint64_t memoryLimit;
};

struct TUploadOptions {
    TString tsharkLocalPath;
    TString libsLocalPath;
    TString luaLocalPath;
    TString httpHeadersLocalPath;
    TString http2HeadersLocalPath;
    TString secretsLocalPath;
};

bool CheckFileExists(const TString& path);
void RunMain(int argc, const char **argv);
void RunMainUpload(TStringBuf argv0, const TYtOptions& options, const TUploadOptions& uploadOptions);
void RunMainMap(TStringBuf argv0, const TYtOptions& options);


class TFileOperations {
private:
    NYT::IClientPtr YtClient_ = nullptr;
    NYT::ITransactionPtr YtTrx_ = nullptr;
public:
    explicit TFileOperations(const TString& cluster) {
        YtClient_ = NYT::CreateClient(cluster);
        YtTrx_ = YtClient_->StartTransaction();
    }

    ~TFileOperations() {
        if (YtTrx_ != nullptr)
            YtTrx_->Commit();
    }

    void UploadFile(const TString& tableName, const TString& fileName, bool exec) {
        TFileInput input(fileName);

        NYT::TRichYPath ytTableName(tableName);
        YtTrx_->Create(tableName, NYT::NT_FILE, NYT::TCreateOptions()
                                                    .Recursive(true)
                                                    .IgnoreExisting(true)
                                                    .Attributes(NYT::TNode::CreateMap()("executable", exec)));

        auto writer = YtTrx_->CreateFileWriter(ytTableName);
        TransferData(&input, writer.Get());
        writer->Finish();
    }
};

class TMapOperations {
private:
    NYT::IClientPtr YtClient_ = nullptr;
    NYT::ITransactionPtr YtTrx_ = nullptr;
public:
    explicit TMapOperations(const TString& cluster) {
        YtClient_ = NYT::CreateClient(cluster);
        YtTrx_ = YtClient_->StartTransaction();
    }

    ~TMapOperations() {
        if (YtTrx_ != nullptr)
            YtTrx_->Commit();
    }

    void RunMap(const TYtOptions& options);
};

// RapidJson streamer for TString
class TRapidStream {
public:
    typedef char Ch;
    TRapidStream(const TString& str)
        : Str_(str)
    {}

    Ch Peek() const {
        return Str_[pos];
    }

    Ch Take() {
        return Str_[pos++];
    }

    size_t Tell() {
        return pos;
    }

    /* Hack for compatibility */

    Ch* PutBegin() {
        return 0;
    }

    size_t PutEnd(Ch* c) {
        Y_UNUSED(c);
        return 0;
    }

    void Put(Ch c) {
        Y_UNUSED(c);
    }

    void Flush() {

    }

private:
    const TString& Str_;
    size_t pos = 0;
};
