//
// Created by Ht on 2022/10/20.
//

#ifndef IOT_EDGE_VDALNEW_VDALDEFAULTSERVICE_H
#define IOT_EDGE_VDALNEW_VDALDEFAULTSERVICE_H

#include <src/VdalDeviceContext.h>
#include <src/vdal/QLeveledMergeQueue.h>
#include "vdal/QClassifiedQueue.h"
#include <src/dbg/QVdalJedpDebugger.h>

namespace jedge {

    /**
     *  此处的指令，意味着后来的指令，可以覆盖前面的指令。这是一种复杂的幂等模型。
     *      对系统而言，后来的指令，可以覆盖前序指令的状态。
     */
    static const char* vkey_default_port = "blemesh";

    ///VdalDefaultService
    class VdalDefaultService : public QJAMgService {
    private:
        string fn;

    protected:
        VdalDeviceContext& vdd_context_;
        std::string         active_site_;
    public:
        explicit VdalDefaultService(jedge::QJAMgServer& opr, const std::string& name,
                                    const qlibc::QData& config);

        ~VdalDefaultService() override {
        }

        void onServiceStart(const qlibc::QData &config) override {}

        void onServiceStop() override {

        }

        void preparePattern() override {
            QJAMgService::preparePattern();
            prepareSelfBind;
            bindHandler(controlDevice);
            bindHandler(debug);
            bindHandler(request);
        }

        bool jaMessageCall(QData &msg, qlibc::QData *rsp) override {
            if(vdd_context_.mgbusHandle(msg, rsp))
                return true;
            if(!QJAMgService::jaMessageCall(msg, rsp)) {
                FailPRspMsg(rsp, "Not handled.");
                return false;
            }
            return true;
        }

        void saveActiveSetup() {
            operator_.updateRunCacheProperty(name()+"."+"active", active_site_);
        }

        bool OnModuleConnected(MgTriggerWatcher &w, const string &k, const QData &m) override {
            vdd_context_.runOnAllPorts([this](const std::string& k, QGAPortPool& o) {
                operator_.threadPool().enqueue([this,k,&o](){
                    SystemUtil::tryWait(2000);
                    auto* param = operator_.getBlankMessage();
                    param->setString("module", operator_.name());
                    param->setString("host", operator_.getRunCachePropertyString("mg_host"));
                    param->setInt("port", operator_.getRunCachePropertyInt("mg_port",8877));
                    o.invokeDPEvent("mgbus","onConnect", *param);
                    operator_.releaseMessage(param);
                });
            });
            return QJAMgService::OnModuleDisconnected(w, k, m);
        }

        bool OnModuleDisconnected(MgTriggerWatcher &w, const string &k, const QData &m) override {
            return QJAMgService::OnModuleDisconnected(w, k, m);
        }

        bool queryDevice(qlibc::QData &message, qlibc::QData *response) {
            //设置初始化参数
            //默认的消息接受处理函数
            QInfo("Vdal Mgbus received:%s", message.toJSONString().c_str());
            if( vdd_context_.handleConfigMessage(operator_,message,response) ){
                OkPRsp(response);
            }else{
                FailPRspMsg(response, "Fail to query device service.");
            }
            NoHandleRsp(response);
            return true;
        }

    protected:

        bool debug(qlibc::QData &message, qlibc::QData *response) {
            //设置初始化参数
            //默认的消息接受处理函数
            message.setBool("~comfirmed", true);
            string model_name = operator_.name();
            model_name = StringUtils::toUpperCase(model_name);
            QInfo("%s-DEBUG Mgbus received:%s", operator_.name().c_str(),message.toJSONString().c_str());
            if(vdd_context_.handleMgBusMessage(message, response)) {
                OkPRsp(response);
                return true;
            }
            NoHandleRsp(response);
            return true;
        }

        ///[uri : device/request] upbs
        bool request(qlibc::QData &message, qlibc::QData *response) {
            message.setBool("~comfirmed", true);
            string model_name = operator_.name();
            model_name = StringUtils::toUpperCase(model_name);
            QInfo("%s-UPBS received:%s", operator_.name().c_str(),message.toJSONString().c_str());
            if(vdd_context_.handleUPBSMessage(operator_, message, response)) {
                OkPRsp(response);
                return true;
            }
            NoHandleRsp(response);
            return true;
        }

        bool controlDevice(qlibc::QData &req, qlibc::QData *rsp) {
            QHLog("Mgbus MESSAGE INCOME:%s",req.toJSONString().c_str());
            if(vdd_context_.handleMgbusControlMessage(req,rsp))
                return true;
            NoHandleRsp(rsp);
            return true;
        }

        bool reload(qlibc::JCArgNode& param) {
            auto port = param.getParam(1);
            auto p = getActivePort(param, port);
            if (p == nullptr) {
                QWarn("Site %p not loaded.", port.c_str());
            } else {
                auto portName = p->getPoolName();
                p=nullptr;
                vdd_context_.reloadPort(portName);
            }
            return true;
        }

        /**
         *  调试过程:
         *      1-加载指定的jedp文件
         *      2-解析一个指定的串口字符
         *      3-编码一个指定的json字符串
         *      4-向一个指定的jedp对象中插入一段
        */
        //调试jedp文件

    public:
        bool jedpCmd(qlibc::JCArgNode& param) {
            auto isEncoder = !param.getBool("d");
            auto grepKey = param.getParam(1);
            //处理 调试端口
            auto port = param.getString("p");
            auto pt = getActivePort(param, port);
            if(pt == nullptr) {
                QWarn("Port %s not loaded.", port.c_str());
                return true;
            }
            Json::Value thLogs;
            pt->listJedpItems(grepKey, &thLogs, isEncoder);
            //显示logs
            for(auto& l : thLogs) {
                if(l.isString()) {
                    printf("%s\n",l.asCString());
                }
            }
            return true;
        }

        //调试port中的文件， 包括加载或清理扩展消息、加载或清理jedp、显示jedp列表等
        /**
         *  支持的命令列表:
         */
        bool dbgPort(qlibc::JCArgNode& param) {
            //处理 调试端口
            auto port = param.getString("p");
            auto pt = getActivePort(param, port);
            if(pt == nullptr) {
                QWarn("Port %s not loaded.", port.c_str());
                return true;
            }
            auto cmd = param.getParam(1);
            pt->handleLocalDebugCommand("", cmd, param);
            return true;
        }

        bool serialPush(qlibc::JCArgNode& param) {
            auto port = param.getString("port");
            auto s = getActivePort(param, port);
            if (s == nullptr) {
                QWarn("Site %s not loaded.", port.c_str());
            } else {
                auto hexStr = param.getParam(1);
                if(hexStr.empty()) {
                    QDLog("Lack of hex data, e.g.: ss --port lightjia EF88000012");
                    return true;
                }
                //StringUtils::replaceAll(hexStr," ",""); //去掉空格
                StringUtils::trimString(hexStr);
                //hexStr.insert(0,"0X");
                QByte *buff;
                auto len = NumberUtils::hexStrToBytes(hexStr, &buff);
                s->serialPush(buff, len);
                delete[] buff;
            }
            return true;
        }

        //显示缓存中的变量及其值状态
        bool setActivePort(qlibc::JCArgNode& param) {
            auto listSite = param.getBool("l");
            if(listSite) {
                vdd_context_.listLoadedSites();
                return true;
            }
            auto port = param.getParam(1);
            if(!port.empty()) {
                auto s = vdd_context_.findPort(port);
                if (s == nullptr) {
                    QWarn("Site %s not loaded.", port.c_str());
                } else {
                    active_site_ = port;
                    saveActiveSetup();
                }
            } else {
                if(active_site_.empty()) {
                    auto sp = vdd_context_.findPort("");
                    if(sp!=nullptr)
                        active_site_ = sp->getName();
                }
            }
            HVULogR("Active port set to : %s", active_site_);
            return true;
        }

        QVdalDevicePortR getActivePort(qlibc::JCArgNode &param, std::string& port) {
            if(port.empty())
                port = active_site_;
            auto sp =  vdd_context_.findPort(port);
            if(sp== nullptr && port!=active_site_)
                sp = vdd_context_.findPort(active_site_);
            if(sp==nullptr) {
                sp = vdd_context_.findPort("");
                if(sp!=nullptr)
                    active_site_ = sp->getName();
                saveActiveSetup();
            }
            return sp;
        }

        //显示缓存中的变量及其值状态
        bool showCacheVar(qlibc::JCArgNode& param) {
            auto cacheKey = param.getParam(1);         //
            auto port = param.getString("port");    // --port hongmei
            auto full = param.getBool("f");         //  -f
            if(port.empty())
                port = active_site_;
            auto update = param.getBool("u");       // -u
            auto s = vdd_context_.findPort(port);
            if(s!=nullptr) {
                s->showCachedVars(cacheKey, full, update);
            }
            return true;
        }

        //显示缓存中的变量及其值状态
        bool showConst(qlibc::JCArgNode& param) {
            auto constKey = param.getParam(1);         //
            auto port = param.getString("port");    // --port hongmei
            auto full = param.getBool("f");         //  -f
            if(port.empty())
                port = active_site_;
            auto s = vdd_context_.findPort(port);
            if(s!=nullptr) {
                s->showConsts(constKey, full);
            }
            return true;
        }

        Json::Value logs;

        bool runGaoWithKey(qlibc::JCArgNode& param, const std::string &keyVal, bool showResult = false) {
            std::string port;
            auto s = getActivePort(param, port);
            if (s == nullptr) {
                QWarn("Site %s not loaded.", port.c_str());
            } else {
                auto realKey = StringUtils::getSubStrAfter(keyVal,"]",-1);
                auto* gao = s->findGAO(realKey);
                if(gao!= nullptr) {
                    operator_.threadPoolRef()->enqueue([gao, param, showResult]() {
                        qlibc::QData resp, newParam(param);
                        gao->performAction(&newParam, &resp);
                        if(showResult)
                            QEMark("Result of gao is : \n%s", resp.toJSONString(true).c_str());
                    });
                } else {
                    QWarn("gao of %s not exits...", keyVal.c_str());
                }
            }
            return true;
        }

        bool runGao(qlibc::JCArgNode& param) {
            auto showResult = param.getBool("i");
            auto list = param.getBool("l");
            if(list) {
                auto grepKey = param.getParam(1);
                std::string port;
                auto s = getActivePort(param, port);
                if (s == nullptr) {
                    QWarn("Site %s not loaded.", port.c_str());
                } else {
                    logs.clear();
                    s->listGaoItems(grepKey, &logs);
                    int i=0;
                    for(auto& l : logs) {
                        auto ll = l.asString();
                        if(StringUtils::startsWith(ll,"==> ")) {
                            l = ll.insert(4,StringUtils::formatString("[%s%3d%s]", qlog_Comment,i, qlog_LOG));
                            i++;
                        }
                        FLogR(ll.c_str());
                    }
                }
                return true;
            }
            auto keyVal = param.getParam(1);
            if(keyVal.empty()) {
                QWarn("user index-type must be set, e.g. gao <mqtt|mgbus> statusValue ...");
                return true;
            }

            if(StringUtils::isNumbericStr(keyVal)) {
                int i = NumberUtils::stringToInt(keyVal);
                if(logs.empty()) {
                    std::string port;
                    auto s = getActivePort(param, port);
                    s->listGaoItems("", &logs);
                    int i=0;
                    for(auto& l : logs) {
                        auto ll = l.asString();
                        if(StringUtils::startsWith(ll,"==> ")) {
                            l = ll.insert(4,StringUtils::formatString("[%s%3d%s]", qlog_Comment,i, qlog_LOG));
                            i++;
                        }
                        FLogR(ll.c_str());
                    }
                }
                if(i>=0 && i< logs.size()-1) {
                    auto l = logs[i+1];
                    auto key = l.asString();
                    if(!key.empty()) {
                        return runGaoWithKey(param, key, showResult);
                    }
                }
            }

            if(keyVal.find('.')!=std::string::npos) { // gao mqtt.xxx.xxx
                return runGaoWithKey(param, keyVal, showResult);
            }

            auto channel = param.getParam(2);
            std::string type ="request";
            if(channel.empty()) {
                channel = "mgbus";
            } else {
                channel = keyVal;
                keyVal = param.getParam(2);
            }

            auto runDeliver = param.getBool("d");
            if(runDeliver) { //gao mqtt -d response
                auto keyType = param.getString("userIndex", "uri");
                std::string port;
                auto s = getActivePort(param, port);
                if (s == nullptr) {
                    QWarn("Site %s not loaded.", port.c_str());
                } else {
                    auto* gao = s->findDeliverGAO(channel, keyType, keyVal);
                    if(gao!= nullptr) {
                        qlibc::QData resp;
                        gao->performAction(&param, &resp);
                        if(showResult)
                            QEMark("Result of gao is : \n%s", resp.toJSONString(true).c_str());
                    } else {
                        QWarn("gao of %s.%s.%s.%s not exits...", channel.c_str(), "deliver", keyType.c_str(), keyVal.c_str());
                    }
                }
                return true;
            }

            auto runCycler = param.getBool("c");
            if(runCycler) {
                type = "cycler";
                QCmt("Not implemented yet ... ");
                return true;
            }

            auto runUser = param.getBool("u");
            if(runUser) {
                auto userArea = param.getString("userArea");
                auto userIndex = param.getString("userIndex");
                if(userArea.empty() || userIndex.empty()) {
                    QWarn("user index-type must be set, e.g. gao mgbus --userArea response --userIndex command ...");
                    return true;
                }
                std::string port;
                auto s = getActivePort(param, port);
                if (s == nullptr) {
                    QWarn("Site %s not loaded.", port.c_str());
                } else {
                    qlibc::QData resp;
                    param.putString(userIndex, keyVal);
                    if(!s->handleRedirectHandler(userArea, userIndex, &param, &resp)) {
                        QWarn("User gao (%s.%s: %s) handle failed ", userArea.c_str(), userIndex.c_str(), keyVal.c_str());
                    }
                    if(showResult)
                        QEMark("Result of gao is : \n%s", resp.toJSONString(true).c_str());
                }
            }

            auto runReq = param.getBool("r", true);
            if(runReq) {
                std::string port;
                auto s = getActivePort(param, port);
                if (s == nullptr) {
                    QWarn("Site %s not loaded.", port.c_str());
                } else {
                    auto* gao = s->findGAO(channel, type, keyVal);
                    if(gao!= nullptr) {
                        qlibc::QData resp;
                        gao->performAction(&param, &resp);
                        if(showResult)
                            QEMark("Result of gao is : \n%s", resp.toJSONString(true).c_str());
                    } else {
                        QWarn("gao of %s.%s.%s not exits...", channel.c_str(), type.c_str(), keyVal.c_str());
                    }
                }
                return true;
            }
            return true;
        }

    };

    VdalDefaultService::VdalDefaultService(QJAMgServer &opr, const string &name, const QData &config) :
        QJAMgService(opr, name, config),
        vdd_context_(VdalDeviceContext::getInstance(*this)) {//初始化Vdal服务
        auto_remove_call_info = false;
        active_site_ = config.getString("active");
        if(active_site_.empty()) {
            active_site_ = operator_.getRunCachePropertyString(opr.name()+"."+"active", vkey_default_port);
        } else {
            saveActiveSetup();
        }

        setVersionInfo("0.1.0.1", RELEASE_DATE);

        BindServiceCommand("cache", "显示cache中的变量 : cache <varKey> [-u,-f] : e.g.: cache deviceInfo / cache deviceInfo -u : 立即更新数据, -f:显示已缓存数据 ", showCacheVar);
        BindServiceCommand("const", "显示const中的常量 : const <varKey>|<idx> : e.g.: const deviceInfo / const 1 : ", showConst);
        BindServiceCommand("port", "列举或设置活跃site : port hongmei / port -l ", setActivePort);
        BindServiceCommand("gao", "触发一个Gao对象 : gao -r/-c/-d/-u mqtt <keyVal> [--userArea kname] [--userIndex kname]... ", runGao);
        BindServiceCommand("rr,reload", "触发gao对象重新加载 : reload ", reload);
        BindServiceCommand("ss,serial", "模拟一个串口数据输入 : serial \"EF88000012038\" ", serialPush);
        BindServiceCommand("jedp", "调试jedp代码 : jedp <name> show, 显示jedp文件的结构", jedpCmd);
        BindServiceCommand("dbg", "调试port命令 : dbg -p [port] <cmd> --params values, 调试port文件", dbgPort);
    }
}

#endif //IOT_EDGE_VDALNEW_VDALDEFAULTSERVICE_H
