/**
 * @file smart.cc
 *
 * @brief this is the main file impelents smart detection
 *
 * @ingroup evcamera
 *
 * @author Bruce Lu
 * Contact: lzbgt@icloud.com
 *
 */

#include "smart.h"

#include <spdlog/spdlog.h>
extern "C"
{
#include <maque_type.h>
#include <maque_smart.h>
#include <maque_osd.h>
#include <maque_watchdog.h>
}

#include "mqtt_helper.hpp"
#include <jsoncons/json.hpp>
#include <vector>
#include <chrono>


using namespace std;
using namespace jsoncons;

#define DIV_UP(x, a)    ( ((x) + ((a) - 1) ) / a )
#define SMART_CAP_MIN_SECS  1

extern shared_ptr<spdlog::logger> rlogger;
#include <motiondetect.hpp>

long long smartTsLast = 0;
XM_S32 MaQue_JpegEnc_getFrame_callback (XM_VOID *pUserArg, MaQueSmartJpegFrame_s *pstJpegFrame)
{
    ev_module_config_t * mod =  (ev_module_config_t *)pUserArg;
    auto now = chrono::duration_cast<chrono::seconds>(chrono::system_clock::now().time_since_epoch()).count();
    // no two capture within SMART_CAP_MIN_SECS seconds
    if(now - smartTsLast < SMART_CAP_MIN_SECS) {
        return 0;
    }

    smartTsLast = now;
    json js;
    string str = fmt::format("new ai capture: {}, idx = {}, toltal = {}", pstJpegFrame->aClassName, pstJpegFrame->nIndex, pstJpegFrame->nToltalJpeg);
    rlogger->info(str);

    return XM_SUCCESS;
}

void maq_smart_task_entry(ev_module_config_t *pArg)
{
    if(!pArg->module.ai.enabled) {
        rlogger->warn("ai detection is disabled. daemon will start and no video uploads");
    }
    rlogger->info("starting ai detection task");
    XM_S32 res;
    MaQueStreamChannel_e eStreamChn;
    MaQueSmartParams_s stMaQueSmartParams;
    MaQueSmartTarget_s stMaQueSmartTarget;
    MaQueSmartLowBitRate_s stMaQueSmartLowBitRate;
    XM_S32 bAdvanceIspEnable = 1;
    XM_FLOAT pdThreshold = 0.75;
    XM_FLOAT fdThreshold = 0.63;
    XM_U32 mbSize = 4;
    XM_S32 mbRangeX, mbRangeY;
    XM_S32 i, j, idx;
    MaQueSmartJpegCallbackParam_s stCallback;
    XM_S32 thick = 4;
    XM_S32 x, y;
    XM_S32 width, height;
    XM_U32 cifWidth = 352, tarWidth = 1920;
    XM_U32 cifHeight = 288, tarHeight = 1080;
    XM_U32 osdFlags[MAQUE_STREAM_CHN_NR] = {0, 0};

    // 目前业务都是并行的，此处的delay 仅是确保图像业务已经开启完成。
    sleep(5);

    res = LibXmMaQue_SmartCreate(0);
    if (XM_SUCCESS != res) {
        rlogger->error("LibXmMaQue_SmartCreate() Failed!");
        return;
    }

    stMaQueSmartParams.bEnabled = 1;
    stMaQueSmartParams.pdThreshold = pdThreshold;
    stMaQueSmartParams.fdThreshold = fdThreshold;

    stMaQueSmartParams.bRoiEnabled = 0; // crash when enable this
    stMaQueSmartParams.roiThreshold = 256;

    stMaQueSmartParams.roiMbMode = MAQUE_SMART_ROI_MB_MODE_16X16;
    stMaQueSmartParams.imgWidth = 640;
    stMaQueSmartParams.imgHeight = 360;
    stMaQueSmartParams.pMbMap = NULL;

    switch (stMaQueSmartParams.roiMbMode) {
    case MAQUE_SMART_ROI_MB_MODE_4X4:
        mbSize = 4;
        break;
    case MAQUE_SMART_ROI_MB_MODE_8X8:
        mbSize = 8;
        break;
    case MAQUE_SMART_ROI_MB_MODE_16X16:
        mbSize = 16;
        break;
    default:
        return;
    }

    // mbRangeX = DIV_UP(stMaQueSmartParams.imgWidth, mbSize);
    // mbRangeY = DIV_UP(stMaQueSmartParams.imgHeight, mbSize);
    // stMaQueSmartParams.pMbMap = (XM_U8*)malloc(mbRangeX * mbRangeY);
    // if (stMaQueSmartParams.pMbMap == nullptr) {
    //     return;
    // }
    // memset(stMaQueSmartParams.pMbMap, 0, mbRangeX * mbRangeY);

    // int minx = mbRangeX * pArg->module.ai.region.minx;
    // int maxx = mbRangeX * pArg->module.ai.region.maxx;
    // int miny = mbRangeY * pArg->module.ai.region.miny;
    // int maxy = mbRangeY * pArg->module.ai.region.maxy;

    // for(i = minx; i <= maxx; i++){
    //     for(j = miny; j <= maxy; j++){
    //         idx = i * mbRangeX + j;
    //         stMaQueSmartParams.pMbMap[idx] = 1;
    //     }
    // }
    rlogger->info("start LibXmMaQue_SmartSetParameter");
    res = LibXmMaQue_SmartSetParameter(0, &stMaQueSmartParams);
    if (XM_SUCCESS != res) {
        rlogger->error("LibXmMaQue_SmartSetParameter() Failed!");
        free(stMaQueSmartParams.pMbMap);
        stMaQueSmartParams.pMbMap = NULL;
        return;
    }

    if (stMaQueSmartParams.pMbMap) {
        free(stMaQueSmartParams.pMbMap);
        stMaQueSmartParams.pMbMap = NULL;
    }
    rlogger->info("finished LibXmMaQue_SmartSetParameter");

    eStreamChn = MAQUE_STREAM_CHN_MAIN;
    stMaQueSmartLowBitRate.bLowBitrate = 1;
    stMaQueSmartLowBitRate.bIsoAdaptive = 1;
    res = LibXmMaQue_SmartLowBitRate(0, eStreamChn, &stMaQueSmartLowBitRate);
    if (XM_SUCCESS != res) {
        rlogger->error("LibXmMaQue_SmartLowBitRate() Failed!");
        return;
    }

    res = LibXmMaQue_SmartAdvanceIsp(0, bAdvanceIspEnable);
    if (XM_SUCCESS != res) {
        rlogger->error("LibXmMaQue_SmartAdvanceIsp() Failed!");
        return;
    }

    res = LibXmMaQue_SmartPDThreshold(0, pArg->module.ai.human_thresh);
    if (XM_SUCCESS != res) {
        rlogger->error("LibXmMaQue_SmartPDThreshold() Failed!");
        return;
    }

    res = LibXmMaQue_SmartFDThreshold(0, pArg->module.ai.face_thresh);
    if (XM_SUCCESS != res) {
        rlogger->error("LibXmMaQue_SmartFDThreshold() Failed!");
        return;
    }

    rlogger->info("finished smart init");

    stCallback.eType = MAQUE_SMART_JPEG_ENCODE_LARGE;//MAQUE_SMART_JPEG_ENCODE_CUTOUT;
    stCallback.eClass = MAQUE_SMART_JPEG_CLASS_PD;//MAQUE_SMART_JPEG_CLASS_FD;
    stCallback.stCallback.pCallbackArg = nullptr; //pArg;
    stCallback.stCallback.pCallbackFuncPtr = nullptr; //MaQue_JpegEnc_getFrame_callback;
    LibXmMaQue_SmartRegisterCallback(0, &stCallback);

    extern std::atomic<uint64_t> gMotionCounter;
    extern atomic<uint64_t> frameCntTotal;
    uint64_t frameCntLast = frameCntTotal.load(memory_order_relaxed);
    uint64_t motionCntLast = gMotionCounter.load(memory_order_relaxed);
    const float kMotionPerSecondThresh = 2.5;
    const int kMotionDetectWindowSeconds = 4;
    const float kMsPerMotion = 1000.0/kMotionPerSecondThresh;
    int state = 0;
    int windowSecs = kMotionDetectWindowSeconds;
    ev_region_t last_region = {0};
    sml::sm<md::fsm> fsm{md::fsm{}};
    bool hasHuman = false;
    bool hasMotion = false;

    while(1) {
        // assuming it's 1s
        this_thread::sleep_for(1s);
        // check AI stat
        if(windowSecs %2 == 0){
            res = LibXmMaQue_SmartGetTarget(0, &stMaQueSmartTarget);
            if (XM_SUCCESS != res) {
                rlogger->error("failed LibXmMaQue_SmartGetTarget");
            }
            if (stMaQueSmartTarget.targetPDNum > 0) {
                hasHuman = true;
                last_region = {stMaQueSmartTarget.aPDRect[0].s16X1, stMaQueSmartTarget.aPDRect[0].s16Y1, stMaQueSmartTarget.aPDRect[0].s16X2, stMaQueSmartTarget.aPDRect[0].s16Y2};
            }else if (stMaQueSmartTarget.targetFDNum > 0) {
                hasHuman = true;
                //last_region = {stMaQueSmartTarget.aFDRect[0].s16X1, stMaQueSmartTarget.aFDRect[0].s16Y1, stMaQueSmartTarget.aFDRect[0].s16X2, stMaQueSmartTarget.aFDRect[0].s16Y2};
            }
        }
        
        if(windowSecs > 0) {
            windowSecs--;
        }
        else {
            auto frameCntTotal_ = frameCntTotal.load(memory_order_relaxed);
            auto motionCnt_ = gMotionCounter.load(memory_order_relaxed);
            float deltaTimeMs = (frameCntTotal_ - frameCntLast) * 1000/pArg->module.sys.fps;
            frameCntLast = frameCntTotal_;
            int motionCntThresh = deltaTimeMs / kMsPerMotion;
            int deltaMotionCnt = motionCnt_ - motionCntLast;
            motionCntLast = motionCnt_;

            if(deltaTimeMs == 0) {
                /// abnormal conditions, should restart and investigation
                rlogger->warn("no video frames in last ~5 seconds");
                frameCntTotal.store(0, std::memory_order_relaxed);
                frameCntLast = 0;
            }
            else {
                if(deltaMotionCnt >= motionCntThresh) {
                    hasMotion = true;
                }
                if(hasMotion && hasHuman) {
                    rlogger->info("ai_task: deltaTimeMs {}, mpboth", deltaTimeMs);
                    fsm.process_event(md::mpboth{});
                }
                else if(hasMotion) {
                    rlogger->info("ai_task: deltaTimeMs {}, motion", deltaTimeMs);
                    fsm.process_event(md::motion{});
                }
                else if(hasHuman) {
                    rlogger->info("ai_task: deltaTimeMs {}, people", deltaTimeMs);
                    fsm.process_event(md::people{});
                }
                else {
                    rlogger->info("ai_task: deltaTimeMs {}, none", deltaTimeMs);
                    fsm.process_event(md::none{});
                }
            }

            windowSecs = kMotionDetectWindowSeconds;
            hasHuman = false;
            hasMotion = false;
            last_region = {0,0,0,0};
        }
    }

    res = LibXmMaQue_SmartDestory(0);
    if (XM_SUCCESS != res) {
        rlogger->error("LibXmMaQue_SmartDestory() Failed!\n");
        return;
    }
}
