//
// Created by Ht on 2019/12/25.
//

#ifndef LIBGOING_JCCMDRUNNER_H
#define LIBGOING_JCCMDRUNNER_H

#include <jebase/config.h>

#include <qlibc/qlibc.h>
#include "JCArgNode.h"
#include "JCStreamInput.h"
#include <mutex>
#include "qlibc/qlibc_config.h"

namespace qlibc {

    //命令行历史记录命令个数，一般不要超过 20
#define  DEF_MAX_HISTORY_SIZE 20

    using QCmdRunnerHandler = std::function<bool(unsigned int *retCode, void **d, void* ctx)>;
    class QCmdRunnerCallback {
    public:
        void*              ctx_;
        QCmdRunnerHandler* callback_;
        explicit QCmdRunnerCallback (const QCmdRunnerHandler& cb, void* ctx) {
            callback_ = new QCmdRunnerHandler(cb);
            ctx_ = ctx;
        }
        ~QCmdRunnerCallback() {
            delete callback_;
        }
    };


#define _cmd_handle_func_name_(Method) \
    _cmd_method_##Method

#define bindCallbackByKeyWithContextToThis(k, f)\
    this->appendCallback((k), _cmd_handle_func_name_(f))

#define bindCallbackOnObjectMethod(obj, funcKey, class_method) \
    (obj)->appendCallback(funcKey, [obj](unsigned int* retCode, void** data, void* ctx) -> bool { \
        return (obj)->class_method(retCode, data, ctx); \
    })

    //将类方法与指定的命令进行捆绑、但不带帮助信息
    //key，命令，可以,分开多个命令
    //pcb, 方法名，方法参数表必须是 (unsigned int* retCode, unsigned int MsgId, void** data)，与 QThreadFunction 定义相同
#define RegisterClassMethodCommand(key, pcb) \
    bindCallbackOnObjectMethod(this, key, pcb);

    //将类方法与指定的命令进行捆绑，并带帮助信息
    //helpInfo ，帮助信息，key,pcb参数与RegisterClassMethodCommand相同
#define RegisterClassMethodCommandWithHelp(key, pcb, helpInfo) \
    bindCallbackOnObjectMethod(this, key, pcb);\
    regCommandHelpInfo(key, helpInfo)

    //将指定对象的类方法与指定的命令进行捆绑，对象不一定是this
#define RegisterObjClassMethodCommandWithHelp(obj, key, pcb, helpInfo) \
    bindCallbackOnObjectMethod(obj, key, pcb);\
    regCommandHelpInfo(key, helpInfo)


    //将指定函数与指定的命令进行捆绑，
    //key，命令，可以,分开多个命令
    //pcb,函数名，可与implCmdCallbackFunction / implCmdCallbackFunctionWithContext 实现的函数名对应
#define RegisterFunctionCommand(key, pcb) \
    bindCallbackByKeyWithContextToThis(key, pcb);

    //将指定函数与指定的命令进行捆绑，带帮助信息，
    //helpInfo，帮助信息，key及pcb信息与前同
#define RegisterFunctionCommandWithHelp(key, pcb, helpInfo) \
    bindCallbackByKeyWithContextToThis(key, pcb); \
    regCommandHelpInfo(key, helpInfo)

    //实现一个命令行响应函数，该函数可使用RegisterFunctionCommandWithHelp绑定到指定命令
    //Method: 函数名
#define implCmdCallbackFunction(Method) \
    _cmd_handle_func_name_(Method)(unsigned int* r, void** d)

    //实现一个带context参数的命令行响应函数，该函数可使用RegisterFunctionCommandWithHelp绑定到指定命令
    //Method: 函数名 c, context对象指针,如this
#define implCmdCallbackFunctionWithContext(Method, d, ctx) \
    _cmd_handle_func_name_(Method)(void* c, void** d, void* ctx)

    //获取输入的command参数
#define GET_CMD_PARAM( var, data) \
    qlibc::JCArgNode *var = nullptr,_cmd_p_emtpy_; \
    if(data!= nullptr && data[0]!= nullptr){ \
        var = static_cast<qlibc::JCArgNode *>(data[0]); \
    } \
    if(var==nullptr) var = &_cmd_p_emtpy_;

#define CheckParamValid(ret, var, cnt, ...) \
    if((var)==nullptr || !var->checkParamValid(cnt, "", ##__VA_ARGS__, nullptr)) { \
        MSG_RET(ret,1); \
    }

/**
 *  以下宏的使用方法：
 *      1、定义一个JCCmdRunner的派生类，如GafficTester
 *      2、在类的成员声明中，用 declearRunObject(tagtype, objname)定义命令运行服务对象及其获取函数
 *      3、派生定义命令响应函数的派生宏：
 *          用implementCallbackFunction(Method, target, param, appcls, tagtype, objname)宏
 *          定义派生命令的宏，再用此派生宏定义其它命令响应函数。其中
 *              appcls = GafficTester
 *              tagtype 与 declearRunObject的tagtype对应
 *              objname 与 declearRunObject的objname对应
 *          如：
 *              implementGafficCallbackFunction(Method, s, p)
 *      4、用implementGafficCallbackFunction定义其它派生函数，如：
 *          implementGafficCallbackFunction(Start,s,p) {
 *              //Start为函数名
 *              //此处可直接使用s变量和p变量
 *              //s为gaffic的对象引用，p为命令行输入参数
 *          }
 *      5、在GafficTester类中派生buildCmdTables方法，在派生方法中，用
 *          RegisterFunctionCommandWithHelp(key, pcb, helpInfo)
 *          宏或
 *          RegisterClassMethodCommandWithHelp(key, pcb, helpInfo)
 *          宏定义自定义命令implementGafficCallbackFunction所声明的命令方法。
 */
//快速定义一个运行对象
#define declearRunObjectByType(tagtype, objname) \
    private:\
        std::shared_ptr<tagtype> run_target_##objname;\
    public: \
        std::shared_ptr<tagtype> getRunTarget##objname() { return run_target_##objname;} \
        void setRunnerToTargetObject() { run_target_##objname->setRunnerInstance(this);}


//用于快速定义一个自定义命令函数，其使用必须与declearRunObject配对
#define implementCallbackFunctionByType(Method, target, param, appcls, tagtype, objname) \
bool Method(tagtype &target, qlibc::JCArgNode &param);\
bool implCmdCallbackFunctionWithContext(Method, d, ctx){ \
    GET_CMD_PARAM(p, d); \
    auto target = (static_cast<appcls*>(ctx))->getRunTarget##objname(); \
    Method(*target.get(), *p); \
    return true;\
}\
bool Method(tagtype &target, qlibc::JCArgNode &param)

//简化宏
#define declearRunObject(tagtype) \
    declearRunObjectByType(tagtype, _CmdApp)
#define RunObjectVarName \
    run_target_##_CmdApp

/**
 *  定义一个Cmd的回调函数实现
 *      Method为方法名， target为服务运行对象， param 为命令参数， appcls 为runner的类名， tagType为被捆绑的服务的类名
 *      通常可在实现时使用另外一个宏定义、对函数声明进行进一步简化：
 *      例如：
 *          #define vdtCmdCallback(Method, s, p) DefCmdCallback(Method, s, p, VDalRunner, jedge::VDalServer)
 *      然后可直接用如下方式定义函数：
 *      vdtCmdCallback(Start, server, p) {
 *          return true;
 *      }
 *
 */
#define DefCmdCallback(Method, target, param, appcls, tagtype) \
    implementCallbackFunctionByType(Method, target, param, appcls, tagtype, _CmdApp)

#define Define_Log(WarnType, logType) \
        bool cmd_##WarnType(unsigned int* retCode, void** d, void* dd) { \
            GET_CMD_PARAM(p, d); \
          int pc = p->getParamCount(); std::string str; for(int i=1;i<pc;i++) str += " " + p->getParam(i); \
            std::cout << logType << str << qlog_LOG << std::endl; \
            return true;\
        };

#define DefineLogCommand(name)\
        RegisterClassMethodCommandWithHelp(#name, cmd_##name, StringUtils::formatString("Log %s a message", #name).c_str())

    class JE_API JCCmdRunner {
    protected:
        std::string        name_;
        qlibc::QSharedObjectHolder<QCmdRunnerCallback>  mCmdHandlers;
		std::unordered_map<std::string, std::string> mHelpMap;
        QStringList        mCmdHistory;
        int                mCmdHistorySize;
        std::string        mHistoryFn;
        QStringList             remote_cmds_;
        std::mutex              hist_mutex_;
        std::mutex              read_wait_mutex_;
        std::condition_variable read_wait_;
        std::string        mDefaultCommand;

        bool    mUserMode;      //用户模式，只关注用户自定义的帮助信息

    public:
        explicit JCCmdRunner(const qlibc::JCArgNode& initConfig);
        virtual ~JCCmdRunner();

        virtual void buildCmdTables();

        bool handleCommand(JCArgNode& argNode, std::string& cmdLine, bool logHistory = true);

        void runApp(JCCmdRunner &runner, const JCArgNode &node, const JCStreamInput* input = nullptr, bool runInBack = false) const;

        void appendCallback(const std::string& funcKey, const QCmdRunnerHandler& handler, void* context = nullptr);

    protected:
        bool cmd_Quit(unsigned int* retCode, void** data, void* d);

        bool cmd_Help(unsigned int* retCode, void** data, void* d);

        bool cmd_History(unsigned int* retCode, void** data, void* d);

        bool cmd_Pause(unsigned int* retCode, void** data, void* d);

        //Define_Log(echo, qlog_LOG);
        static bool cmd_echo(unsigned int* retCode, void** data, void* d);;

        Define_Log(warn, qlog_WARN);
        Define_Log(info, qlog_INFO);
        Define_Log(err, qlog_ERR);
        Define_Log(high, qlog_HighLight);
        Define_Log(mark, qlog_Mark);
        Define_Log(comment, qlog_Comment);
        Define_Log(emark, qlog_EMark);

        bool cmd_Repeat(unsigned int* retCode, void** data, void* d);

        bool cmd_Doom(unsigned int* retCode, void** data, void* d){return true;}

    public:
        void loadCommandHistory(const std::string& fn);

        void regCommandHelpInfo(const std::string &key, const std::string &helpInfor);

        std::string getHelpInfo(const std::string &cmd);

        void updateCmdHistory(const std::string& cmdLine);

        void getRemoteCommand(std::string &buffer);

        void pushRemoteCommand(const std::string& cmd);

        virtual void markCommandInputed();

        virtual void markLineKeyInputed();

        void getFromRemoteCommandsQueue(std::string &buffer);

        void setUserMode(bool userMode);
    };

}
#endif //LIBGOING_JCCMDRUNNER_H
