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

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

#define MAX_SIZE_LEN (16)

//#define CMD_DISK_FILESYSTEM ("df -h | awk \'{if (NR > 1){print $0}}\'")
#define CMD_DISK_FILESYSTEM ("df -h | awk \'{if (NR > 1){print $1\"|\"$2\"|\"$3\"|\"$4\"|\"$5\"|\"$6}}\'")

typedef struct {
    char          fileSystem[MAX_PATH];
    char          mntDevice[MAX_PATH];
    char          diskSize[MAX_SIZE_LEN];
    char          diskUsed[MAX_SIZE_LEN];
    char          diskFree[MAX_SIZE_LEN];
    char          diskUsedPercent[MAX_SIZE_LEN];
    unsigned long timestamp;

    UT_hash_handle hh;
} DISK_PART_INFO, *PDISK_PART_INFO;

static uv_rwlock_t     g_uvLock;
static PDISK_PART_INFO g_diskPartInfo = NULL;

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

    fp = popen(CMD_DISK_FILESYSTEM, "r");

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

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

        if (nItems == 6) {
            PDISK_PART_INFO pTmp = NULL;

            sdstrim(pToken[0], "\n");
            sdstrim(pToken[1], "\n");
            sdstrim(pToken[2], "\n");
            sdstrim(pToken[3], "\n");
            sdstrim(pToken[4], "\n");
            sdstrim(pToken[5], "\n");

            HASH_FIND_STR(g_diskPartInfo, pToken[5], pTmp);

            if (pTmp) {
                uv_rwlock_wrlock(&g_uvLock);
                strcpy(pTmp->diskSize, pToken[1]);
                strcpy(pTmp->diskFree, pToken[3]);
                strcpy(pTmp->diskUsedPercent, pToken[4]);
                pTmp->timestamp = time(NULL);
                uv_rwlock_wrunlock(&g_uvLock);
            } else {
                pTmp = (PDISK_PART_INFO)malloc(sizeof(DISK_PART_INFO));

                if (pTmp) {
                    uv_rwlock_wrlock(&g_uvLock);
                    strcpy(pTmp->fileSystem, pToken[0]);
                    strcpy(pTmp->diskSize, pToken[1]);
                    strcpy(pTmp->diskUsed, pToken[2]);
                    strcpy(pTmp->diskFree, pToken[3]);
                    strcpy(pTmp->diskUsedPercent, pToken[4]);
                    strcpy(pTmp->mntDevice, pToken[5]);
                    HASH_ADD_STR(g_diskPartInfo, mntDevice, pTmp);
                    uv_rwlock_wrunlock(&g_uvLock);
                } else {
                    errCode = -ERR_MALLOC_MEMORY;
                }
            }

            i++;
        }

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

    pclose(fp);
    return errCode;
}

int get_disk_info(PDISK_INFO pInfo) {
    PDISK_PART_INFO pItem, pTmp;
    PDISK_PART_USED pDisk;

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

    uv_rwlock_rdlock(&g_uvLock);
    pInfo->nItems    = HASH_COUNT(g_diskPartInfo);
    pInfo->timestamp = time(NULL);
    pDisk            = pInfo->diskPartInfo;

    HASH_ITER(hh, g_diskPartInfo, pItem, pTmp) {
        pDisk->deviceName  = pItem->mntDevice;
        pDisk->diskSize    = pItem->diskSize;
        pDisk->diskUsed    = pItem->diskUsed;
        pDisk->usedPercent = pItem->diskUsedPercent;
        pDisk++;
    }
    uv_rwlock_rdunlock(&g_uvLock);

    return ERR_SUCCESS;
}

_Noreturn void diskRefreshCb(void *UNUSED(pArg)) {
    do {
        unsigned int period = cfg_get_disk_refresh_period();

        if (cfg_get_watch_disk()) {
            disk_info_refresh();
        }

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

int disk_watch_info() {
    static uv_thread_t uvThread;

    uv_rwlock_init(&g_uvLock);

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

    uv_thread_create(&uvThread, diskRefreshCb, NULL);

    return ERR_SUCCESS;
}