#pragma once

#include <mapreduce/lib/all.h>
#include <util/thread/factory.h>

#define MEMORY_LIMIT_1GB    1024ul * 1024ul * 1024ul
#define MEMORY_LIMIT_2GB    MEMORY_LIMIT_1GB * 2ul
#define MEMORY_LIMIT_4GB    MEMORY_LIMIT_1GB * 4ul
#define MEMORY_LIMIT_8GB    MEMORY_LIMIT_1GB * 8ul
#define MEMORY_LIMIT_10GB   MEMORY_LIMIT_1GB * 10ul

#define ASYNC_CTX0  0
#define ASYNC_CTX1  1
#define ASYNC_CTX2  2
#define ASYNC_CTX3  3
#define ASYNC_CTX4  4
#define ASYNC_CTX5  5
#define ASYNC_CTX6  6
#define ASYNC_CTX7  7

namespace NWebmaster {

typedef TAtomicSharedPtr<IThreadFactory::IThreadAble> TThreadAblePtr;

struct TSortTask : public IThreadFactory::IThreadAble {
    TSortTask(NMR::TServer &server, const TString &src, const TString &dst = "");
    ~TSortTask() override;

public:
    void DoExecute() override;
    void RunAsync();

public:
    TAutoPtr<IThreadFactory::IThread> ThreadRef;
    NMR::TServer &Server;
    TString Src;
    TString Dst;
};

struct TMapTask : public IThreadFactory::IThreadAble {
    TMapTask(NMR::TServer &server, NMR::IMap *mapOperator, TSimpleSharedPtr<NMR::TMRParams> params, const TString &comment = "");
    ~TMapTask() override;

public:
    void DoExecute() override;
    void RunAsync();

public:
    TAutoPtr<IThreadFactory::IThread> ThreadRef;
    NMR::TServer &Server;
    NMR::IMap *MapOperator;
    TSimpleSharedPtr<NMR::TMRParams> Params;
    TString Comment;
};

struct TMergeTask : public IThreadFactory::IThreadAble {
    TMergeTask(NMR::TServer &server, TSimpleSharedPtr<NMR::TMRParams> params, const TString &comment = "");
    ~TMergeTask() override;

public:
    void DoExecute() override;
    void RunAsync();

public:
    TAutoPtr<IThreadFactory::IThread> ThreadRef;
    NMR::TServer &Server;
    TSimpleSharedPtr<NMR::TMRParams> Params;
    TString Comment;
};

struct TReduceTask : public IThreadFactory::IThreadAble {
    TReduceTask(NMR::TServer &server, NMR::IReduce *reduceOperator, TSimpleSharedPtr<NMR::TMRParams> params, const TString &comment = "");
    ~TReduceTask() override;

public:
    void DoExecute() override;
    void RunAsync();

public:
    TAutoPtr<IThreadFactory::IThread> ThreadRef;
    NMR::TServer &Server;
    NMR::IReduce *ReduceOperator;
    TSimpleSharedPtr<NMR::TMRParams> Params;
    TString Comment;
};

struct TMapReduceTask : public IThreadFactory::IThreadAble {
    TMapReduceTask(NMR::TServer &server, NMR::IMap *mapOperator, NMR::IReduce *reduceOperator, TSimpleSharedPtr<NMR::TMRParams> params, const TString &comment = "");
    ~TMapReduceTask() override;

public:
    void DoExecute() override;
    void RunAsync();

public:
    TAutoPtr<IThreadFactory::IThread> ThreadRef;
    NMR::TServer &Server;
    NMR::IMap *MapOperator;
    NMR::IReduce *ReduceOperator;
    TSimpleSharedPtr<NMR::TMRParams> Params;
    TString Comment;
};

struct TUniqTask : public IThreadFactory::IThreadAble {
    TUniqTask(NMR::TServer &server, TSimpleSharedPtr<NMR::TMRParams> params, const TString &comment = "");
    ~TUniqTask() override;

public:
    void DoExecute() override;
    void RunAsync();

public:
    TAutoPtr<IThreadFactory::IThread> ThreadRef;
    NMR::TServer &Server;
    TSimpleSharedPtr<NMR::TMRParams> Params;
    TString Comment;
};

struct TMapLambdaExecutor0 : public NMR::IMap {
    OBJECT_METHODS(TMapLambdaExecutor0)
    SAVELOAD_OVERRIDE_WITHOUT_BASE(MethodPtr)

public:
    typedef void (*TMethodPtr)(NMR::TValue &key, NMR::TValue &value, NMR::TUpdate &output);

    TMapLambdaExecutor0()
        : MethodPtr(nullptr)
    {
    }

    TMapLambdaExecutor0(TMethodPtr ptr)
        : MethodPtr(ptr)
    {
    }

public:
    void Do(NMR::TValue key, NMR::TValue data, NMR::TUpdate &output) override {
        if (MethodPtr) {
            MethodPtr(key, data, output);
        }
    }

public:
    TMethodPtr MethodPtr;
};

struct TMapLambdaExecutor1 : public NMR::IMap {
    OBJECT_METHODS(TMapLambdaExecutor1)
    SAVELOAD_OVERRIDE_WITHOUT_BASE(MethodPtr)

public:
    typedef void (*TMethodPtr)(NMR::TValue &key, NMR::TValue &subkey, NMR::TValue &value, NMR::TUpdate &output);

    TMapLambdaExecutor1()
        : MethodPtr(nullptr)
    {
    }

    TMapLambdaExecutor1(TMethodPtr ptr)
        : MethodPtr(ptr)
    {
    }

public:
    void DoSub(NMR::TValue key, NMR::TValue subKey, NMR::TValue data, NMR::TUpdate &output) override {
        if (MethodPtr) {
            MethodPtr(key, subKey, data, output);
        }
    }

public:
    TMethodPtr MethodPtr;
};

struct TMapLambdaExecutor2 : public NMR::IMap {
    OBJECT_METHODS(TMapLambdaExecutor2)
    SAVELOAD_OVERRIDE_WITHOUT_BASE(MethodPtr)

public:
    typedef void (*TMethodPtr)(NMR::IMapContext *ctx, NMR::TValue &key, NMR::TValue &subKey, NMR::TValue &value, NMR::TUpdate &output);

    TMapLambdaExecutor2()
        : MethodPtr(nullptr)
    {
    }

    TMapLambdaExecutor2(TMethodPtr ptr)
        : MethodPtr(ptr)
    {
    }

public:
    void DoSub(NMR::TValue key, NMR::TValue subKey, NMR::TValue data, NMR::TUpdate &output) override {
        if (MethodPtr) {
            MethodPtr(GetContext().Get(), key, subKey, data, output);
        }
    }

public:
    TMethodPtr MethodPtr;
};

struct TReduceLambdaExecutor : public NMR::IReduce {
    OBJECT_METHODS(TReduceLambdaExecutor)
    SAVELOAD_OVERRIDE_WITHOUT_BASE(MethodPtr)

public:
    typedef void (*TMethodPtr)(NMR::IReduceContext *ctx, NMR::TValue &key, NMR::TTableIterator &input, NMR::TUpdate &output);

    TReduceLambdaExecutor()
        : MethodPtr(nullptr)
    {
    }

    TReduceLambdaExecutor(TMethodPtr ptr)
        : MethodPtr(ptr)
    {
    }

public:
    void Do(NMR::TValue key, NMR::TTableIterator &input, NMR::TUpdate &output) override {
        if (MethodPtr) {
            MethodPtr(GetContext().Get(), key, input, output);
        }
    }

public:
    TMethodPtr MethodPtr;
};

struct TTaskRunner {
    TTaskRunner(NMR::TServer &server);

private:
    TString GetCommentedMsg(const TString &msg) const;
    void CommentLogInfo(TString msg) const;

public:
    TTaskRunner& Input(const TString &input);
    TTaskRunner& InputByPrefix(const TString &prefix, size_t maxCount = 500);
    TTaskRunner& Output(const TString &output, NMR::EUpdateMode mode = NMR::UM_REPLACE);
    TTaskRunner& Comment(const TString &comment);
    TTaskRunner& Copy();
    TTaskRunner& Drop(const TString &table);
    TTaskRunner& DropByPrefix(const TString &prefix);
    TTaskRunner& EnableReduceWithoutSort();
    TTaskRunner& EnableOrderedMap();
    TTaskRunner& Map(NMR::IMap *mapOperator, int ctx = -1);
    TTaskRunner& MapReduce(NMR::IMap *mapOperator, NMR::IReduce *reduceOperator, int ctx = -1);
    TTaskRunner& Merge(int ctx = -1);
    TTaskRunner& Move();
    TTaskRunner& Reduce(NMR::IReduce *reduceOperator, int ctx = -1);
    TTaskRunner& Sort(const TString &src, int ctx = -1);
    TTaskRunner& Sort(const TString &src, const TString &dst, int ctx = -1);
    TTaskRunner& Uniq(int ctx = -1);
    TTaskRunner& JobCount(int m);
    TTaskRunner& JobCountMultiplier(int m = 1);
    TTaskRunner& ThreadCount(int t = 1);
    TTaskRunner& MemoryLimit(size_t n);
    TTaskRunner& Wait(size_t ctx);
    TTaskRunner& YtSpec(const TString &ytSpec);

public:
    TMapLambdaExecutor0 *LambdaExecutor(TMapLambdaExecutor0::TMethodPtr ptr) {
        return new TMapLambdaExecutor0(ptr);
    }

    TMapLambdaExecutor1 *LambdaExecutor(TMapLambdaExecutor1::TMethodPtr ptr) {
        return new TMapLambdaExecutor1(ptr);
    }

    TMapLambdaExecutor2 *LambdaExecutor(TMapLambdaExecutor2::TMethodPtr ptr) {
        return new TMapLambdaExecutor2(ptr);
    }

    template<typename TMethodPtr>
    TTaskRunner& MapFunc(TMethodPtr ptr, int ctx = -1) {
        return Map(LambdaExecutor(ptr), ctx);
    }

    template<typename TMethodPtr>
    TTaskRunner& ReduceFunc(TMethodPtr ptr, int ctx = -1) {
        return Reduce(new TReduceLambdaExecutor(ptr), ctx);
    }

    template<typename TMapMethodPtr, typename TReduceMethodPtr>
    TTaskRunner& MapReduceFunc(TMapMethodPtr mapPtr, TReduceMethodPtr reducePtr, int ctx = -1) {
        return MapReduce(LambdaExecutor(mapPtr), new TReduceLambdaExecutor(reducePtr), ctx);
    }

public:
    void PrepareParams() {
        if (!Params) {
            Params.Reset(new NMR::TMRParams);
        }
    }

    template<typename T>
    TTaskRunner& RunTask(T *task, int ctx) {
        try {
            TThreadAblePtr ptr(task);

            if (ctx >= 0) {
                task->RunAsync();
                CtxQueue[ctx].push_back(ptr);
            } else {
                task->DoExecute();
            }
        } catch(...) {
            Params.Reset();
            throw;
        }
        Params.Reset();
        return *this;
    }

    static TString GetParametersComment(NMR::TMRParams &params);
    static void DefaultLogInfo(const TString &msg);

public:
    TMap<size_t, TList<TThreadAblePtr>> CtxQueue;
    TString CommentStr;
    NMR::TServer &Server;
    TList<TSimpleSharedPtr<NMR::TClient>> TxClientList;
    TSimpleSharedPtr<NMR::TMRParams> Params;

public:
    static void (*LogInfo)(const TString &msg);
};

} //namespace NWebmaster
