#include <mapreduce/lib/all.h>
#include <mapreduce/library/operations/uniq/uniq.h>
#include <util/string/join.h>
#include <util/string/printf.h>

#include "mr_tasks.h"

namespace NWebmaster {

TSortTask::TSortTask(NMR::TServer &server, const TString &src, const TString &dst)
    : Server(server)
    , Src(src)
    , Dst(dst.empty() ? src : dst)
{
}

TSortTask::~TSortTask() {
    if (ThreadRef) {
        ThreadRef->Join();
    }
}

void TSortTask::DoExecute() {
    TString comment = Sprintf("sorting [input: %s; output: %s]", Src.data(), Dst.data());
    TTaskRunner::LogInfo(comment);
    Server.Sort(Src, Dst);
    TTaskRunner::LogInfo(comment + " - done");
}

void TSortTask::RunAsync() {
    ThreadRef = SystemThreadFactory()->Run(this);
}

TMapTask::TMapTask(NMR::TServer &server, NMR::IMap *mapOperator, TSimpleSharedPtr<NMR::TMRParams> params, const TString &comment)
    : Server(server)
    , MapOperator(mapOperator)
    , Params(params)
    , Comment(comment)
{
}

TMapTask::~TMapTask() {
    if (ThreadRef) {
        ThreadRef->Join();
    }
}

void TMapTask::DoExecute() {
    TTaskRunner::LogInfo(Comment);
    Server.Map(*Params, MapOperator);
    TTaskRunner::LogInfo(Comment + " - done");
}

void TMapTask::RunAsync() {
    ThreadRef = SystemThreadFactory()->Run(this);
}

TMergeTask::TMergeTask(NMR::TServer &server, TSimpleSharedPtr<NMR::TMRParams> params, const TString &comment)
    : Server(server)
    , Params(params)
    , Comment(comment)
{
}

TMergeTask::~TMergeTask() {
    if (ThreadRef) {
        ThreadRef->Join();
    }
}

void TMergeTask::DoExecute() {
    TTaskRunner::LogInfo(Comment);
    TVector<TString> inputTables;

    for (const auto &table : Params->SrcTables) {
        inputTables.push_back(table.Name);
    }

    if (Params->DstTables.size() > 1) {
        ythrow yexception() << "there are more than one output table for merge operation";
    }

    if (!Params->SrcTables.empty() && !Params->DstTables.empty()) {
        Server.Merge(inputTables, Params->DstTables[0]);
    }

    TTaskRunner::LogInfo(Comment + " - done");
}

void TMergeTask::RunAsync() {
    ThreadRef = SystemThreadFactory()->Run(this);
}

TReduceTask::TReduceTask(NMR::TServer &server, NMR::IReduce *reduceOperator, TSimpleSharedPtr<NMR::TMRParams> params, const TString &comment)
    : Server(server)
    , ReduceOperator(reduceOperator)
    , Params(params)
    , Comment(comment)
{
}

TReduceTask::~TReduceTask() {
    if (ThreadRef) {
        ThreadRef->Join();
    }
}

void TReduceTask::DoExecute() {
    TTaskRunner::LogInfo(Comment);
    Server.Reduce(*Params, ReduceOperator);
    TTaskRunner::LogInfo(Comment + " - done");
}

void TReduceTask::RunAsync() {
    ThreadRef = SystemThreadFactory()->Run(this);
}

TMapReduceTask::TMapReduceTask(NMR::TServer &server, NMR::IMap *mapOperator, NMR::IReduce *reduceOperator, TSimpleSharedPtr<NMR::TMRParams> params, const TString &comment)
    : Server(server)
    , MapOperator(mapOperator)
    , ReduceOperator(reduceOperator)
    , Params(params)
    , Comment(comment)
{
}

TMapReduceTask::~TMapReduceTask() {
    if (ThreadRef) {
        ThreadRef->Join();
    }
}

void TMapReduceTask::DoExecute() {
    TTaskRunner::LogInfo(Comment);
    Server.MapReduce(*Params, MapOperator, ReduceOperator);
    TTaskRunner::LogInfo(Comment + " - done");
}

void TMapReduceTask::RunAsync() {
    ThreadRef = SystemThreadFactory()->Run(this);
}

TUniqTask::TUniqTask(NMR::TServer &server, TSimpleSharedPtr<NMR::TMRParams> params, const TString &comment)
    : Server(server)
    , Params(params)
    , Comment(comment)
{
}

TUniqTask::~TUniqTask() {
    if (ThreadRef) {
        ThreadRef->Join();
    }
}

void TUniqTask::DoExecute() {
    TTaskRunner::LogInfo(Comment);
    TVector<TString> inputTables;

    for (const auto &table : Params->SrcTables) {
        inputTables.push_back(table.Name);
    }

    if (Params->DstTables.size() > 1) {
        ythrow yexception() << "there are more than one output table for uniq operation";
    }

    if (!Params->SrcTables.empty() && !Params->DstTables.empty()) {
        GetUniqTableRowsHashed(Server, inputTables, Params->DstTables[0]);
    }

    TTaskRunner::LogInfo(Comment + " - done");
}

void TUniqTask::RunAsync() {
    ThreadRef = SystemThreadFactory()->Run(this);
}

TTaskRunner::TTaskRunner(NMR::TServer &server)
    : Server(server)
{
    TTaskRunner::LogInfo("cluster " + server.GetServerName());
}

TTaskRunner& TTaskRunner::Input(const TString &input) {
    PrepareParams();

    CommentLogInfo("input " + input);
    Params->AddInputTable(input);

    return *this;
}

TTaskRunner& TTaskRunner::InputByPrefix(const TString &prefix, size_t maxCount) {
    PrepareParams();

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

    TVector<NMR::TTableInfo> tables;
    Server.GetTables(&tables, NMR::GT_PREFIX_MATCH, prefix.data());

    std::sort(tables.begin(), tables.end(), TTableTimeGreater());

    if (tables.size() > maxCount) {
        tables.resize(maxCount);
    }

    for (const NMR::TTableInfo &table : tables) {
        CommentLogInfo("input " + table.Name);
        Params->AddInputTable(table.Name);
    }

    return *this;
}

TTaskRunner& TTaskRunner::Output(const TString &output, NMR::EUpdateMode mode) {
    PrepareParams();
    CommentLogInfo("output " + output);
    Params->AddOutputTable(output, mode);
    return *this;
}

TTaskRunner& TTaskRunner::Comment(const TString &comment) {
    CommentStr = comment;
    return *this;
}

TTaskRunner& TTaskRunner::Copy() {
    TString opComment = GetCommentedMsg(TString("copy operation ") + GetParametersComment(*Params));
    TTaskRunner::LogInfo(opComment);
    TVector<TString> inputTables;

    for (const auto &table : Params->SrcTables) {
        inputTables.push_back(table.Name);
    }

    if (Params->DstTables.size() > 1) {
        ythrow yexception() << "there are more than one output table for copy operation";
    }

    if (!Params->SrcTables.empty() && !Params->DstTables.empty()) {
        Server.Copy(inputTables, Params->DstTables[0]);
    }

    TTaskRunner::LogInfo(opComment + " - done");
    return *this;
}

TTaskRunner& TTaskRunner::Drop(const TString &table) {
    Server.Drop(table);
    CommentLogInfo("dropped table " + table);
    return *this;
}

TTaskRunner& TTaskRunner::DropByPrefix(const TString &prefix) {
    TVector<NMR::TTableInfo> tables;
    Server.GetTables(&tables, NMR::GT_PREFIX_MATCH, prefix.data());

    for (const NMR::TTableInfo &table : tables) {
        Drop(table.Name);
    }

    return *this;
}

TTaskRunner& TTaskRunner::EnableReduceWithoutSort() {
    PrepareParams();
    Params->EnableReduceWithoutSort();
    return *this;
}

TTaskRunner& TTaskRunner::EnableOrderedMap() {
    PrepareParams();
    Params->EnableOrderedMap();
    return *this;
}

TTaskRunner& TTaskRunner::JobCountMultiplier(int m) {
    PrepareParams();
    Params->SetJobCountMultiplier(m);
    return *this;
}

TTaskRunner& TTaskRunner::JobCount(int m) {
    PrepareParams();
    Params->SetJobCount(m);
    return *this;
}

TTaskRunner& TTaskRunner::MemoryLimit(size_t n) {
    PrepareParams();
    Params->SetJobMemoryLimit(n);
    return *this;
}

TTaskRunner& TTaskRunner::ThreadCount(int t) {
    PrepareParams();
    Params->SetThreadCount(t);
    return *this;
}

TTaskRunner& TTaskRunner::YtSpec(const TString &ytSpec) {
    PrepareParams();
    Params->AddAttr("yt.spec", ytSpec);
    return *this;
}

TTaskRunner& TTaskRunner::Map(NMR::IMap *mapOperator, int ctx) {
    TString opComment = GetCommentedMsg(TString("map operation ") + GetParametersComment(*Params));
    return RunTask(new TMapTask(Server, mapOperator, Params, opComment), ctx);
}

TTaskRunner& TTaskRunner::Merge(int ctx) {
    TString opComment = GetCommentedMsg(TString("merge operation ") + GetParametersComment(*Params));
    return RunTask(new TMergeTask(Server, Params, opComment), ctx);
}

TTaskRunner& TTaskRunner::Reduce(NMR::IReduce *reduceOperator, int ctx) {
    TString opComment = GetCommentedMsg(TString("reduce operation ") + GetParametersComment(*Params));
    return RunTask(new TReduceTask(Server, reduceOperator, Params, opComment), ctx);
}

TTaskRunner& TTaskRunner::MapReduce(NMR::IMap *mapOperator, NMR::IReduce *reduceOperator, int ctx) {
    TString opComment = GetCommentedMsg(TString("mapreduce operation ") + GetParametersComment(*Params));
    return RunTask(new TMapReduceTask(Server, mapOperator, reduceOperator, Params, opComment), ctx);
}

TTaskRunner& TTaskRunner::Move() {
    TString opComment = GetCommentedMsg(TString("move operation ") + GetParametersComment(*Params));
    TTaskRunner::LogInfo(opComment);

    if (Params->SrcTables.size() > 1) {
        ythrow yexception() << "there are more than one input table for move operation";
    }

    if (Params->DstTables.size() > 1) {
        ythrow yexception() << "there are more than one output table for move operation";
    }

    if (!Params->SrcTables.empty() && !Params->DstTables.empty()) {
        Server.Move(Params->SrcTables[0], Params->DstTables[0]);
    }

    TTaskRunner::LogInfo(opComment + " - done");
    return *this;
}

TTaskRunner& TTaskRunner::Sort(const TString &src, int ctx) {
    return RunTask(new TSortTask(Server, src, src), ctx);
}

TTaskRunner& TTaskRunner::Sort(const TString &src, const TString &dst, int ctx) {
    return RunTask(new TSortTask(Server, src, dst), ctx);
}

TTaskRunner& TTaskRunner::Uniq(int ctx) {
    TString opComment = GetCommentedMsg(TString("uniq operation ") + GetParametersComment(*Params));
    return RunTask(new TUniqTask(Server, Params, opComment), ctx);
}

TTaskRunner& TTaskRunner::Wait(size_t ctx) {
    CtxQueue[ctx].clear();
    return *this;
}

TString TTaskRunner::GetParametersComment(NMR::TMRParams &params) {
    TVector<TString> input, output;

    for (const auto &table : params.SrcTables) {
        input.push_back(table.Name);
    }

    for (const auto &table : params.DstTables) {
        output.push_back(table.Name);
    }

    if (input.size() > 5) {
        size_t total = input.size();
        input.resize(5);
        input.push_back(Sprintf("...%lu tables", total));
    }

    if (output.size() > 5) {
        size_t total = output.size();
        output.resize(5);
        output.push_back(Sprintf("...%lu tables", total));
    }

    return "[input: " + JoinSeq(", ", input) + "; output: " + JoinSeq(", ", output) + "]";
}

TString TTaskRunner::GetCommentedMsg(const TString &msg) const {
    if (!CommentStr.empty()) {
        return CommentStr + " " + msg;
    }

    return msg;
}

void TTaskRunner::CommentLogInfo(TString msg) const {
    LogInfo(GetCommentedMsg(msg));
}

void TTaskRunner::DefaultLogInfo(const TString &msg) {
    Cout << msg << Endl;
}

void (*TTaskRunner::LogInfo)(const TString &msg) = &TTaskRunner::DefaultLogInfo;

} //namespace NWebmaster

using namespace NWebmaster;
REGISTER_SAVELOAD_CLASS(0xFFFF0010, TMapLambdaExecutor0)
REGISTER_SAVELOAD_CLASS(0xFFFF0011, TMapLambdaExecutor1)
REGISTER_SAVELOAD_CLASS(0xFFFF0012, TMapLambdaExecutor2)
REGISTER_SAVELOAD_CLASS(0xFFFF0020, TReduceLambdaExecutor)
