//
// Created by xajhu on 2021/7/6 0006.
//
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <uv.h>

#include "hardware.h"
#include "misc.h"
#include "user_errno.h"
#include "config.h"

typedef struct {
    unsigned long cpuTotal;
    unsigned long cpuIdle;
} CPU_TIME;

#define CMD_CPU_NUM        ("cat /proc/cpuinfo| grep \"physical id\" | sort | uniq | wc -l")
#define CMD_CPU_CORE       ("cat /proc/cpuinfo| grep \"cpu cores\" | uniq | awk \'{print $4}\'")
#define CMP_CPU_LOGIC_CORE ("cat /proc/cpuinfo| grep \"cpu cores\" | wc -l")

#define SUM_CPU_TIME(x) ((x).user + (x).nice + (x).sys + (x).idle + (x).irq)

static int      g_isInit = FALSE;
static CPU_INFO g_cpuInfo;

static int get_cpu_info_cmd(const char *pCmd) {
    char *pRet = NULL;
    char *pOver;
    long  val;

    if (shell_with_output(pCmd, &pRet) != ERR_OK || pRet == NULL || strlen(pRet) == 0) {
        if (pRet) {
            free(pRet);
        }
        return -ERR_CALL_SHELL;
    }

    errno = 0;

    val = strtol(pRet, &pOver, 10);

    free(pRet);

    if (/*(pOver != NULL && strlen(pOver)) ||*/ errno != 0) {
        return -ERR_STRING_TO_NUMBER;
    }

    return (int)val;
}

static int get_cpu_number() {
    return get_cpu_info_cmd(CMD_CPU_NUM);
}

static int get_cpu_core_number() {
    return get_cpu_info_cmd(CMD_CPU_CORE);
}

static int get_cpu_logic_core() {
    return get_cpu_info_cmd(CMP_CPU_LOGIC_CORE);
}

int get_cpu_desc(PCPU_DESC pDesc) {
    uv_cpu_info_t *pCpu = NULL;
    int            nCpu;

    if (uv_cpu_info(&pCpu, &nCpu) != 0 || nCpu <= 0) {
        if (pCpu) {
            uv_free_cpu_info(pCpu, nCpu);
        }
        return -ERR_SYS_GET_CPU_INFO;
    }

    if (pDesc) {
        memset(pDesc->cpuName, 0, MEM_VALUE_SIZE * 4);
        strcpy(pDesc->cpuName, (pCpu[0].model));
        pDesc->cpuSpeed = pCpu[0].speed;
    }

    uv_free_cpu_info(pCpu, nCpu);

    return ERR_OK;
}

static int get_cpu_time_info(unsigned long *pTotal, unsigned long *pIdle) {
    uv_cpu_info_t *pCpu = NULL;
    int            i, nCpu;

    if (pTotal == NULL || pIdle == NULL) {
        return -ERR_INPUT_PARAMS;
    }

    if (uv_cpu_info(&pCpu, &nCpu) != 0 || nCpu <= 0) {
        if (pCpu) {
            uv_free_cpu_info(pCpu, nCpu);
        }
        return -ERR_SYS_GET_CPU_INFO;
    }

    // 清零计算值
    *pTotal = *pIdle = 0;

    for (i = 0; i < nCpu; i++) {
        *pIdle += pCpu[i].cpu_times.idle;
        *pTotal += SUM_CPU_TIME(pCpu[i].cpu_times);
    }

    uv_free_cpu_info(pCpu, nCpu);

    return ERR_OK;
}

static void cpuUsedRefresh() {
    CPU_TIME beginTime, endTime;

    memset(&beginTime, 0, sizeof(CPU_TIME));
    memset(&endTime, 0, sizeof(CPU_TIME));

    if (get_cpu_time_info(&beginTime.cpuTotal, &beginTime.cpuIdle) != ERR_OK) {
        return;
    }

    // 延时 1s 后再获取1次CPU信息
    uv_sleep(1000);

    if (get_cpu_time_info(&endTime.cpuTotal, &endTime.cpuIdle) != ERR_OK) {
        return;
    } else {
        uint64_t idle       = endTime.cpuIdle - beginTime.cpuIdle;
        uint64_t total      = endTime.cpuTotal - beginTime.cpuTotal;
        g_cpuInfo.cpuUsed   = 1.0 - ((double)idle / (double)total);
        g_cpuInfo.timestamp = time(NULL);
    }
}

_Noreturn void cpuCalcCb(void *UNUSED(pArg)) {
    do {
        unsigned int period = cfg_get_cpu_refresh_period();

        if (cfg_get_watch_cpu()) {
            cpuUsedRefresh();
        }

        if (period < REFRESH_MAX_PERIOD && period >= 1) {
            uv_sleep(1000 * period);
        } else {
            uv_sleep(1000);
        }
    } while (TRUE);
}

int cpu_watch_init() {
    static uv_thread_t uvThread;

    memset(&g_cpuInfo, 0, sizeof(CPU_INFO));

    g_cpuInfo.nCpus       = get_cpu_number();
    g_cpuInfo.nLogicCores = get_cpu_logic_core();
    g_cpuInfo.nCores      = get_cpu_core_number();
    g_cpuInfo.timestamp   = time(NULL);
    get_cpu_desc(&g_cpuInfo.cpuCoreDesc);

    g_isInit = TRUE;

    uv_thread_create(&uvThread, cpuCalcCb, NULL);

    return ERR_OK;
}

int get_cpu_info(PCPU_INFO pInfo) {
    if (pInfo == NULL) {
        return -ERR_INPUT_PARAMS;
    }

    if (g_isInit == FALSE) {
        memset(&g_cpuInfo, 0, sizeof(CPU_INFO));

        g_cpuInfo.nCpus       = get_cpu_number();
        g_cpuInfo.nLogicCores = get_cpu_logic_core();
        g_cpuInfo.nCores      = get_cpu_core_number();
        g_cpuInfo.timestamp   = time(NULL);
        get_cpu_desc(&g_cpuInfo.cpuCoreDesc);

        g_isInit = TRUE;
    }

    memcpy(pInfo, &g_cpuInfo, sizeof(CPU_INFO));

    return ERR_OK;
}