#include "mw_soft_watchdog.h"
#include "list.h"
#include "htime.h"
#include "hthread.h"
#include "hbase.h"
#include "hlog.h"
#include "hmutex.h"
#include "hplatform.h"


#define reboot_delay(miniutes)  miniutes

typedef struct {
    hsem_t     sem;
    wd_item_s *p_items;
    uint16_t   item_cnt;
    soft_watchdog_item_fail fail_cb;
}sf_wtd_s;

static sf_wtd_s *s_wtd;
 

void soft_watchdog_stop(uint32_t item)
{
    if(s_wtd->p_items[item].start_ms != IGNORE) {
        hlogi("%s item %d IGNORE", __func__, item);
		s_wtd->p_items[item].start_ms = IGNORE;
    }
}

void soft_watchdog_start(uint32_t item)
{
    if( s_wtd->p_items[item].start_ms > 0 ) {
        /* already starting */
    } else {
        s_wtd->p_items[item].start_ms = gettimeofday_ms();
        hsem_post(&s_wtd->sem);
        hlogi("%s %u start_ms %lld", __func__, item, s_wtd->p_items[item].start_ms);
    }
}

void soft_watchdog_timeout(uint32_t item, uint32_t timeout_ms){
	s_wtd->p_items[item].start_ms   = gettimeofday_ms();
    s_wtd->p_items[item].timeout_ms = timeout_ms;
    hsem_post(&s_wtd->sem);
}

//something happen, delay the watchdog some time
void soft_watchdog_timeout_all(uint32_t timeout_ms){
    for(uint32_t i = 0; i < s_wtd->item_cnt; i++){
        wd_item_s * item = &s_wtd->p_items[i];
        if (item->timeout_ms >= 0) {
            item->timeout_ms += timeout_ms;
        }
    }
    hlogi("wd_delay_all timeout_ms = %d", timeout_ms);
}

HTHREAD_ROUTINE(soft_wtd_thread_entry)
{
    hlogd("%s", __func__);
    hsem_wait(&s_wtd->sem);
    for(;;) {
        uint64_t  now_ms      = gettimeofday_ms();
        uint64_t  expire_ms   = (uint64_t)(-1);
        int32_t   expire_item = -1;

        for(uint32_t i = 0; i < s_wtd->item_cnt; i++){
            wd_item_s * item = &s_wtd->p_items[i];
            uint64_t item_expire_ms;
            //hlogd("%d start_ms %d timeout_ms %d now_ms %d",i , item->start_ms, item->timeout_ms, now_ms);
            if (item->start_ms == IGNORE) {
                continue;
            }
            item_expire_ms = item->start_ms + item->timeout_ms;
            if( now_ms > item_expire_ms ){
                hlogd("%d now_ms %llu, timeout_ms %llu timeout-now_ms:%llu", i, now_ms, item_expire_ms, item_expire_ms-now_ms);  
                s_wtd->fail_cb(i);
            }

            /* 最近的时间 */
            if ( item_expire_ms < expire_ms ) {
                expire_ms   = item_expire_ms;
                expire_item = i;
            }
            
        }
        now_ms = gettimeofday_ms();
        hlogd("%d now_ms %llu, timeout_ms %llu timeout-now_ms:%llu", expire_item, now_ms, expire_ms, expire_ms-now_ms);        
        hsem_wait_for(&s_wtd->sem, expire_ms - now_ms + 1);/* '1' 避免， hsem_wait_for(sem, 0) 又快速循环 */
    }
    return NULL;
}


void soft_watchdog_init(wd_item_s *items, uint32_t item_cnt, soft_watchdog_item_fail fail_cb)
{
    if (!s_wtd){
        s_wtd = (sf_wtd_s*)hv_malloc(sizeof(sf_wtd_s));
        hsem_init(&s_wtd->sem, 0);
        s_wtd->p_items  = items;
        s_wtd->item_cnt = item_cnt;
        s_wtd->fail_cb  = fail_cb;
        hthread_create(soft_wtd_thread_entry, NULL);
    }
}


/*******************soft_watchdog_test**********************/


typedef enum{
    WD_None = -1,
    WD_Sensor_Init = 0, /* Sensor 初始化 */
    WD_Wifi_Init,       /* WIFI 初始化 */
    WD_Wifi_Associated, /* WIFI 连接 */
    WD_Wifi_DHCP_IP,    /* WIFI 获取IP */
    WD_Connect_Server,  /* 连接服务器 */
    WD_AP_Wait_Client,  /* AP模式 连接 */
    WD_Suspend,         /* 休眠过程 */
    WD_Max
}swd_e;

typedef struct {
     swd_e    last_fail_idx;
     uint8_t  last_fail_cnt;
}swd_last_s;

static wd_item_s wditem_policy[] =
{
    //WD_Sensor_Init, should call wd_set_ok in 3 seconds after system bootup
   [WD_Sensor_Init]     = { IGNORE, 10*1000,       { REBOOT, REBOOT, REBOOT, reboot_delay(2), reboot_delay(10), SHUTDOWN} },

    //WD_Wifi_Init, should init done in 3 seconds after system bootup
   [WD_Wifi_Init]       = { IGNORE, 10*1000,       { REBOOT, REBOOT, reboot_delay(1), reboot_delay(2), reboot_delay(3),  SHUTDOWN} },
    //WD_Wifi_Associated, should Associate done in 13 seconds after system bootup
   [WD_Wifi_Associated] = { IGNORE, 60*1000,	   { REBOOT, reboot_delay(3), reboot_delay(10), reboot_delay(10), reboot_delay(30), reboot_delay(60)} },
    //WD_Wifi_DHCP_IP, after enabled should be IGNORE to connect 30 seconds, try 2 times
   [WD_Wifi_DHCP_IP]    = { IGNORE, 30*1000,       { REBOOT, reboot_delay(2), reboot_delay(5), reboot_delay(10), reboot_delay(30), reboot_delay(60)} },
    //WD_Connect_Server, after enabled, 60 seconds should be IGNORE, try 2 times.
   [WD_Connect_Server]  = { IGNORE, 60*1000,       { REBOOT, reboot_delay(3), reboot_delay(10), reboot_delay(10), reboot_delay(30), reboot_delay(30)} },
    //WD_AP_Wait_Client, AP mode, wait client connect, 3minutes timeout_ms, just SUSPEND
   [WD_AP_Wait_Client]  = { IGNORE, 180*1000,      { SUSPEND, SUSPEND, SUSPEND, SUSPEND, SUSPEND, SUSPEND} },

    //WD_Suspend
   [WD_Suspend]         = { IGNORE, 60*1000,       { REBOOT, REBOOT, REBOOT, REBOOT, REBOOT, REBOOT} }
    
};

static const char* get_item_name(swd_e item)
{
    switch (item) {
        case WD_Sensor_Init :       return NAME(WD_Sensor_Init);
        case WD_Wifi_Init :         return NAME(WD_Wifi_Init);
        case WD_Wifi_Associated :   return NAME(WD_Wifi_Associated);
        case WD_Wifi_DHCP_IP :      return NAME(WD_Wifi_DHCP_IP);
        case WD_Connect_Server :    return NAME(WD_Connect_Server);
        case WD_AP_Wait_Client :    return NAME(WD_AP_Wait_Client);
        case WD_Suspend :           return NAME(WD_Suspend);
        case WD_None:               return NAME(WD_None);
        case WD_Max:                return NAME(WD_Max);
    }
    return "None";
}

static swd_last_s swd_last;
static void soft_watchdog_fail_cb(uint32_t fail_idx){

    /* 这段主要是要执行对应的action */
     if(swd_last.last_fail_idx == fail_idx) {
        swd_last.last_fail_cnt++;
    }else {
        swd_last.last_fail_cnt = 1;
    }
    if (swd_last.last_fail_cnt >= WD_POLICY_NUMBER) {
        swd_last.last_fail_cnt = WD_POLICY_NUMBER;
    }
    
    uint32_t action = wditem_policy[swd_last.last_fail_idx].action[swd_last.last_fail_cnt - 1];
    uint8_t failed_info = ((swd_last.last_fail_cnt & 0xF) << 4) | (swd_last.last_fail_idx & 0xF);
    
    /* save failed_info, 比如保存到mcu去 */
    hlogw("%s last_fail_idx : %d last_fail_cnt : %u", __func__, swd_last.last_fail_idx, swd_last.last_fail_cnt);
}

void soft_watchdog_test()
{
    
    /* uint8_t failed_info = read 如从mcu读取 */ 
    soft_watchdog_init(wditem_policy, ARRAY_SIZE(wditem_policy), soft_watchdog_fail_cb);
    soft_watchdog_timeout(WD_Wifi_Associated, 5000);
}

