#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include "log.h"
#include "libuv_dbus.h"

typedef struct
{
    MODULE_NAME         modName;
    uint32_t            hTm[MODULE_MAX];
    int                 isDaemonWork[MODULE_MAX];
    int                 isConnected[MODULE_MAX];
    OnDaemonMsg     pOnHeartLostCb;
} HEART_DAEMON, *PHEART_DAEMON;

uint32_t                g_hblTout       = HEART_LOST_DELAY;      ///< nano second: heart lost timeout, default 1s
static uv_loop_t        *g_DeamonLoop;
static uv_idle_t        g_uvDeamonIdle;
static HEART_DAEMON     g_heartDaemon;

static unsigned int g_Cnt = 0;
static int timerExpire(uint32_t tm, uint32_t tExp)
{
    uint32_t now = LIBUV_CURRENT_TIME_MS();
    int64_t diff = now - tm;
    
    if(tm == 0 || tExp == 0)
    {
        return 0;
    }

    if(diff > tExp * 1000)
    {
        return 0;
    }
    else if(diff >= tExp)
    {   
        return 1;
    }

    return (0);
}

static void RunPingSvr(void)
{
    int                     ret         = 0;
    unsigned int            tm = LIBUV_CURRENT_TIME_MS();
    PING_MSG                pMsg;
    struct timeval          tv;

    gettimeofday(&tv, NULL);
    
    pMsg.PING   = (double)tm / 1000;
    pMsg.tmSec  = tv.tv_sec;
    pMsg.tmMSec = tv.tv_usec;
    
    ret = DBusJsonBoardcastCommand(NULL, 0xFFFFFFFF, CMD_MISC_PING, JSON_ENGINE_PING, &pMsg, FALSE);
    
    if(ret != 0)
    {
        LOG_EX(LOG_Error, "DBus boardcast message error: %d\n", ret);
    }
}

void HeartDaemonHblCheck(void)
{
    if(g_heartDaemon.modName != MODULE_CONTROLLER)
    {
        if(g_heartDaemon.isDaemonWork[MODULE_CONTROLLER] && timerExpire(g_heartDaemon.hTm[MODULE_CONTROLLER], g_hblTout))
        {
            g_heartDaemon.pOnHeartLostCb(MODULE_CONTROLLER, TRUE);
            g_heartDaemon.hTm[MODULE_CONTROLLER] = 0;
            g_heartDaemon.isConnected[MODULE_CONTROLLER] = FALSE;
        }
    }
    else
    {
        int i;
                
        for(i = 0; i < MODULE_MAX; i++)
        {
            if(g_heartDaemon.isDaemonWork[i]
                && i != MODULE_CONTROLLER
                && timerExpire(g_heartDaemon.hTm[i], g_hblTout))
            {
                g_heartDaemon.pOnHeartLostCb(i, TRUE);
                g_heartDaemon.hTm[i] = 0;
                g_heartDaemon.isConnected[i] = FALSE;
            }
        }
    }
}

void HeartDaemonUpgrade(int iWatcher)
{
    if(iWatcher >= MODULE_MAX)
    {
        return;
    }

    if(g_heartDaemon.hTm[iWatcher] == 0)
    {
        if(g_heartDaemon.modName == MODULE_CONTROLLER)
        {
            g_heartDaemon.pOnHeartLostCb(iWatcher, FALSE);
        }
        else if(iWatcher == MODULE_CONTROLLER)
        {
            g_heartDaemon.pOnHeartLostCb(iWatcher, FALSE);
        }

        g_heartDaemon.isConnected[iWatcher] = TRUE;
        RunPingSvr();
    }

    g_heartDaemon.hTm[iWatcher] = LIBUV_CURRENT_TIME_MS();
}

static int __isSendPingOnTime(void)
{
    static unsigned int     tmPre       = 0;
    unsigned int            tm = LIBUV_CURRENT_TIME_MS();
    unsigned int            tmOut       = HEART_SEND_DELAY;

    if(g_heartDaemon.modName != MODULE_CONTROLLER
       && g_heartDaemon.isConnected[MODULE_CONTROLLER] == FALSE)
    {
        tmOut = 5000;
    }
    
    if(tmPre != 0 && tm - tmPre < tmOut)
    {
        return (FALSE);
    }

    tmPre = tm;

    return (TRUE);
}

static void __uvIdleCb(uv_idle_t* phuvIdle)
{       
    if(DBusLibuvGetRuntime()->onHblCb
       && __isSendPingOnTime())
    {
        RunPingSvr();
    }

    HeartDaemonHblCheck();
    sleep(1);
}

static void __uvThreadDaemon(void *pParams)
{
    g_DeamonLoop    = uv_loop_new();

    uv_idle_init(g_DeamonLoop, &g_uvDeamonIdle);    
    uv_idle_start(&g_uvDeamonIdle, __uvIdleCb);

    uv_run(g_DeamonLoop, UV_RUN_DEFAULT);

    pthread_detach(pthread_self());
}

void HeartDaemonInit(MODULE_NAME mod, int msHblTout, OnDaemonMsg cb)
{
    uv_thread_t uvDaemonThread;
    int i;

    memset(&g_heartDaemon, 0, sizeof(HEART_DAEMON));
    
    if(msHblTout > 0)
    {
        g_hblTout = msHblTout;
    }

    g_heartDaemon.modName = mod;

    for(i = 0; i < MODULE_MAX; i++)
    {
        if(mod == MODULE_CONTROLLER)
        {
            g_heartDaemon.isDaemonWork[i] = (g_pModInfoTable[i].modName != MODULE_CONTROLLER) ? TRUE : FALSE;
        }
        else
        {
            g_heartDaemon.isDaemonWork[i] = (g_pModInfoTable[i].modName == MODULE_CONTROLLER) ? TRUE : FALSE;
        }

        g_heartDaemon.isConnected[i] = FALSE;
    }
  
    g_heartDaemon.pOnHeartLostCb = cb;

    uv_thread_create(&uvDaemonThread, __uvThreadDaemon, NULL);
}
