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

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

#define CMD_MEMORY_INFO ("cat /proc/meminfo  | awk \'{print $1\"|\"$2\"|\"$3}\'")

#define M_SIZE (1024 * 1024)

#define KEY_NAME   (32)
#define KEY_VALUE  (32)
#define VALUE_UNIT (8)

typedef struct {
    char          itemName[KEY_NAME];
    char          itemValue[KEY_VALUE];
    char          valUnit[VALUE_UNIT];
    unsigned long timestamp;

    UT_hash_handle hh;
} KERNEL_MEM_INFO, *PKERNEL_MEM_INFO;

static uv_rwlock_t      g_uvLock;
static PKERNEL_MEM_INFO g_pMemInfo     = NULL;
static const char      *g_memKeyName[] = {"MemTotal", "MemFree", "MemAvailable", "Buffers", "Cached"};

unsigned int get_sys_free_memory() {
    return uv_get_free_memory() / M_SIZE;
}

unsigned int get_sys_total_memory() {
    return uv_get_total_memory() / M_SIZE;
}

static int memory_info_refresh() {
    int   errCode = ERR_SUCCESS;
    FILE *fp;
    char  buf[MAX_PATH * 2];

    fp = popen(CMD_MEMORY_INFO, "r");

    if (fp == NULL) {
        return -ERR_OPEN_FILE;
    }

    while (fgets(buf, 1024, fp) != NULL) {
        int  nItems;
        sds  tmpStr = sdsnew(buf);
        sds *pToken = sdssplitlen(tmpStr, (int)sdslen(tmpStr), "|", 1, &nItems);

        if (nItems == 3) {
            PKERNEL_MEM_INFO pTmp = NULL;

            HASH_FIND_STR(g_pMemInfo, pToken[0], pTmp);

            if (pTmp) {
                uv_rwlock_wrlock(&g_uvLock);
                strcpy(pTmp->itemName, sdstrim(pToken[0], ": \n"));
                strcpy(pTmp->itemValue, sdstrim(pToken[1], " \n"));
                strcpy(pTmp->valUnit, sdstrim(pToken[2], " \n"));
                pTmp->timestamp = time(NULL);
                uv_rwlock_wrunlock(&g_uvLock);
            } else {
                pTmp = (PKERNEL_MEM_INFO)malloc(sizeof(KERNEL_MEM_INFO));

                if (pTmp) {
                    uv_rwlock_wrlock(&g_uvLock);
                    strcpy(pTmp->itemName, sdstrim(pToken[0], ": \n"));
                    strcpy(pTmp->itemValue, sdstrim(pToken[1], " \n"));
                    strcpy(pTmp->valUnit, sdstrim(pToken[2], " \n"));
                    pTmp->timestamp = time(NULL);
                    HASH_ADD_STR(g_pMemInfo, itemName, pTmp);
                    uv_rwlock_wrunlock(&g_uvLock);
                } else {
                    errCode = -ERR_MALLOC_MEMORY;
                }
            }
        }

        sdsfreesplitres(pToken, nItems);
        sdsfree(tmpStr);
    }

    return errCode;
}

_Noreturn void memRefreshCb(void *UNUSED(pArg)) {
    do {
        unsigned int period = cfg_get_mem_refresh_period();

        if (cfg_get_watch_memory()) {
            memory_info_refresh();
        }

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

int memory_watch_init() {
    static uv_thread_t uvThread;

    uv_rwlock_init(&g_uvLock);

    if (memory_info_refresh() != ERR_SUCCESS) {
        return -ERR_SYS_DISK_GET_INFO;
    }

    uv_thread_create(&uvThread, memRefreshCb, NULL);

    return ERR_SUCCESS;
}

#define MEM_VALUE_SET(dst, src)                                      \
    do {                                                             \
        if ((src) == NULL)                                           \
            continue;                                                \
        if ((src)->valUnit && strlen((src)->valUnit) > 0) {          \
            sprintf(dst, "%s %s", (src)->itemValue, (src)->valUnit); \
        } else {                                                     \
            sprintf(dst, "%s", (src)->itemValue);                    \
        }                                                            \
    } while (0)

int get_memory_info(PMEMORY_INFO pInfo) {
    int i, n = ARRAY_SIZE(g_memKeyName);
    if (pInfo == NULL) {
        return -ERR_INPUT_PARAMS;
    }

    uv_rwlock_rdlock(&g_uvLock);

    for (i = 0; i < n; i++) {
        PKERNEL_MEM_INFO pTmp = NULL;

        HASH_FIND_STR(g_pMemInfo, g_memKeyName[i], pTmp);

        if (pTmp) {
            switch (i) {
                case 0:
                    MEM_VALUE_SET(pInfo->totalMemSize, pTmp);
                    break;
                case 1:
                    MEM_VALUE_SET(pInfo->freeMemSize, pTmp);
                    break;
                case 2:
                    MEM_VALUE_SET(pInfo->availMemSize, pTmp);
                    break;
                case 3:
                    MEM_VALUE_SET(pInfo->bufferMemSize, pTmp);
                    break;
                case 4:
                    MEM_VALUE_SET(pInfo->cachedMemSize, pTmp);
                    break;
                default:
                    continue;
            }
        }
    }

    uv_rwlock_rdunlock(&g_uvLock);

    pInfo->timestamp = time(NULL);

    return ERR_SUCCESS;
}