//
// Created by xajhu on 2021/7/2 0002.
//
#include <string.h>
#include <uv.h>
#include <sys/vfs.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/sendfile.h>
#include <linux/if.h>
#include <sys/ioctl.h>
#include <linux/if_ether.h>
#include <sys/time.h>
#include <netinet/udp.h>
#include <netinet/ip.h>

#include "user_errno.h"
#include "misc.h"
#include "zlog_module.h"
#include "common.h"
#include "sds/sds.h"

PSYS_NIC_INFO g_sysNicInfo = NULL;

const char *basename_v2(const char *path) {
    const char *tail = strrchr(path, '/');
    return tail ? tail + 1 : path;
}

int dirname_v2(const char *path, char *dir) {
    const char *tail = strrchr(path, '/');

    if (tail) {
        memcpy(dir, path, tail - path);
        dir[tail - path] = 0;
    } else {
        strcpy(dir, "./");
    }

    return 0;
}

unsigned long long get_partition_free_size(const char *pPartPath) {
    struct statfs      myStatfs;
    unsigned long long freeSize;

    if (statfs(pPartPath, &myStatfs) == -1) {
        return 0;
    }

    freeSize = myStatfs.f_bsize * myStatfs.f_bfree;

    return freeSize;
}

int copy_file(const char *pSrc, const char *pDest) {
    int         fdSrc, fdDest;
    struct stat st;
    ssize_t     sz;

    if (stat(pSrc, &st) != 0) {
        LOG_MOD(error, ZLOG_MOD_MISC, "Get File %s Size Error\n", pSrc);
        return (-ERR_GET_FILE_SIZE);
    }

    fdSrc = open(pSrc, O_RDONLY);

    if (fdSrc < 0) {
        LOG_MOD(error, ZLOG_MOD_MISC, "Open File %s Error\n", pSrc);
        return (-ERR_OPEN_FILE);
    }

    fdDest = open(pDest, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);

    if (fdDest < 0) {
        close(fdSrc);
        LOG_MOD(error, ZLOG_MOD_MISC, "Open File %s Error\n", pDest);
        return (-ERR_OPEN_FILE);
    }

    sz = sendfile(fdDest, fdSrc, NULL, st.st_size);

    if (sz != st.st_size) {
        LOG_MOD(error, ZLOG_MOD_MISC, "Copy File Size Error: %zd, %ld\n", sz, st.st_size);
        close(fdSrc);
        close(fdDest);
        return (-ERR_COPY_FILE);
    }

    fsync(fdDest);

    close(fdSrc);
    close(fdDest);

    return (0);
}

char *bin2hex(char *p, const unsigned char *cp, unsigned int count) {
    static const char hex_asc[] = "0123456789abcdef";
    while (count) {
        unsigned char c = *cp++;
        /* put lowercase hex digits */
        *p++            = (char)(0x20 | hex_asc[c >> 4]);
        *p++            = (char)(0x20 | hex_asc[c & 0xf]);
        count--;
    }

    return p;
}

int shell_with_output(const char *pCmd, char **pResult) {
    FILE        *pFile = NULL;
    unsigned int uRdSize;
    char        *pCmdOut;

    *pResult = NULL;

    if (pCmd == NULL || strlen(pCmd) == 0) {
        return (-ERR_INPUT_PARAMS);
    }

    pFile = popen(pCmd, "r");

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

    *pResult = (char *)malloc(4096);
    pCmdOut  = *pResult;

    uRdSize = fread(pCmdOut, sizeof(char), 4096, pFile);

    pCmdOut[uRdSize] = 0;

    if (pCmdOut[strlen(pCmdOut) - 1] == '\n') {
        pCmdOut[strlen(pCmdOut) - 1] = 0;
    }

    pclose(pFile);
    return ERR_SUCCESS;
}

int file_exists(const char *pPath) {
    if ((access(pPath, F_OK)) == -1) {
        return FALSE;
    }

    return TRUE;
}

const char *get_cur_process_dir() {
    static char g_exePath[4096] = {0};

    size_t bufSize = 4096L;

    if (strlen((const char *)g_exePath) == 0) {
        memset(g_exePath, 0, 4096);
        uv_cwd(g_exePath, &bufSize);
    }

    return (const char *)g_exePath;
}

unsigned long long get_current_time_ms() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

int str_to_mac(const char *str, unsigned char mac[6]) {
    int   i;
    char *s, *e;

    if ((mac == NULL) || (str == NULL)) {
        return -ERR_INPUT_PARAMS;
    }

    s = (char *)str;
    for (i = 0; i < 6; ++i) {
        mac[i] = s ? strtoul(s, &e, 16) : 0;
        if (s) {
            s = (*e) ? e + 1 : e;
        }
    }
    return ERR_SUCCESS;
}

int str_to_ipaddr(const char *pIp, unsigned int *ipAddr) {
    struct in_addr addr;
    int            ret = inet_aton(pIp, &addr);

    if (ret != 0) {
        *ipAddr = addr.s_addr;
    }

    return ret;
}

int get_nic_info(const char    *pName,
                 unsigned int  *pIp,
                 unsigned int  *pNetmask,
                 unsigned int  *pBoardcast,
                 unsigned char *pMac) {
    int          sock;
    struct ifreq ifr;
    int          err = ERR_SUCCESS;
    sock             = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    if (sock < 0) {
        LOG_MOD(error, ZLOG_MOD_MISC, "Get local NIC information failed\n");
        return -ERR_SYS_INIT;
    }

    memset(&ifr, 0, sizeof(ifr));
    strcpy(ifr.ifr_name, pName);

    if (pIp) {
        if (ioctl(sock, SIOCGIFADDR, &ifr) != 0) {
            err = ERR_MISC_GET_IPADDR;
        } else {
            *pIp = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr;
        }
    }

    if (pNetmask) {
        if (ioctl(sock, SIOCGIFNETMASK, &ifr) == 0) {
            *pNetmask = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr;
        } else {
            err = ERR_MISC_GET_NETMASK;
        }
    }

    if (pBoardcast) {
        if (ioctl(sock, SIOCGIFBRDADDR, &ifr) == 0) {
            *pBoardcast = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr;
        } else {
            err = ERR_MISC_GET_GATEWAY;
        }
    }

    if (pMac) {
        if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) {
            memcpy(pMac, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
        } else {
            err = ERR_MISC_GET_MACADDR;
        }
    }

    close(sock);
    return err;
}

const char *u32_to_str_ip(unsigned int ip) {
    struct in_addr s = {.s_addr = ip};
    if (ip == 0) {
        return "";
    }
    return inet_ntoa(s);
}

const char *u32_to_str_ip_safe(unsigned int ip) {
    struct in_addr s = {.s_addr = ip};

    if (ip == 0) {
        return strdup("");
    }
    return strdup(inet_ntoa(s));
}

unsigned long long ntohll(unsigned long long val)
{
    return (((unsigned long long) ntohl(val)) << 32) + ntohl(val >> 32);
}

int get_all_network_info(PSYS_NIC_INFO pInfo) {
    int           i;
    unsigned long nicCnt;
    struct ifreq  buf[4096];
    struct ifconf ifc;
    PSYS_NIC_INFO pNicInfo;
    PNIC_CTX      pNic;

    if (pInfo == NULL) {
        LOG_MOD(error, ZLOG_MOD_MISC, "Malloc memory failed, size: %lu\n", sizeof(SYS_NIC_INFO));
        return -ERR_INPUT_PARAMS;
    }

    if (g_sysNicInfo != NULL) {
        pInfo->nicCnt  = g_sysNicInfo->nicCnt;
        pInfo->pNicCtx = g_sysNicInfo->pNicCtx;
        return ERR_SUCCESS;
    }

    pNicInfo = (PSYS_NIC_INFO)malloc(sizeof(SYS_NIC_INFO));

    if (pNicInfo == NULL) {
        LOG_MOD(error, ZLOG_MOD_MISC, "Malloc memory failed, size: %lu\n", sizeof(SYS_NIC_INFO));
        return -ERR_MALLOC_MEMORY;
    }

    memset(pNicInfo, 0, sizeof(SYS_NIC_INFO));

    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
    if (sock == -1) {
        LOG_MOD(error, ZLOG_MOD_MISC, "Create socket failed\n");
        free(pNicInfo);
        return -ERR_SOCK_CREATE;
    }
    memset(&ifc, 0, sizeof(struct ifconf));
    ifc.ifc_len = sizeof(struct ifreq) * 4096;
    ifc.ifc_buf = (char *)buf;

    if (ioctl(sock, SIOCGIFCONF, &ifc) == -1) {
        close(sock);
        free(pNicInfo);
        LOG_MOD(error, ZLOG_MOD_MISC, "IOCTL SIOCGIFCONF socket failed\n");
        return -ERR_SYS_CALL;
    }

    nicCnt            = ifc.ifc_len / sizeof(struct ifreq);
    pNicInfo->pNicCtx = (PNIC_CTX)malloc(sizeof(NIC_CTX) * nicCnt);

    if (pNicInfo->pNicCtx == NULL) {
        LOG_MOD(error, ZLOG_MOD_MISC, "Malloc memory failed, size: %lu * %lu\n", sizeof(NIC_CTX), nicCnt);
        free(pNicInfo);
        close(sock);
        return -ERR_MALLOC_MEMORY;
    }

    LOG_MOD(trace, ZLOG_MOD_MISC, "Malloc memory size: %lu * %lu\n", sizeof(NIC_CTX), nicCnt);

    memset(pNicInfo->pNicCtx, 0, sizeof(NIC_CTX) * nicCnt);

    pNic = &pNicInfo->pNicCtx[0];
    for (i = 0; i < nicCnt; i++) {
        U32 ipAddr;
        U32 netmaskAddr;
        U32 bcAddr;
        U8  mac[ETH_ALEN];
        int ret = get_nic_info(buf[i].ifr_name, &ipAddr, &netmaskAddr, &bcAddr, mac);

        if (ret == ERR_SUCCESS) {
            strcpy(pNic->ethName, buf[i].ifr_name);
            pNic->ipv4Addr      = ipAddr;
            pNic->ipv4Mask      = netmaskAddr;
            pNic->ipv4Boardcast = bcAddr;
            memcpy(pNic->mac, mac, ETH_ALEN);

            LOG_MOD(trace, ZLOG_MOD_MISC, "Network intreface(%d): %s\n", pNicInfo->nicCnt, pNic->ethName);
            LOG_MOD(trace, ZLOG_MOD_MISC, "\t\tIP Address\t:%s\n", u32_to_str_ip(ipAddr));
            LOG_MOD(trace, ZLOG_MOD_MISC, "\t\tNetmask Address\t:%s\n", u32_to_str_ip(netmaskAddr));
            LOG_MOD(trace, ZLOG_MOD_MISC, "\t\tBoardcast Address\t:%s\n", u32_to_str_ip(bcAddr));

            pNicInfo->nicCnt++;
            pNic++;

        } else {
            LOG_MOD(warn,
                    ZLOG_MOD_MISC,
                    "Get system nic(%s) information error:%s(%d)\n",
                    buf[i].ifr_name,
                    getErrorEnumNameString(ret),
                    ret);
        }
    }
    close(sock);

    g_sysNicInfo   = pNicInfo;
    pInfo->nicCnt  = g_sysNicInfo->nicCnt;
    pInfo->pNicCtx = g_sysNicInfo->pNicCtx;
    return ERR_SUCCESS;
}

const char *get_cur_process_name() {
    static char g_exeName[1024] = {0};

    if (strlen(g_exeName) > 0) {
        return basename_v2(g_exeName);
    }

    memset(g_exeName, 0, 1024);
    if (readlink("/proc/self/exe", g_exeName, 1023) <= 0) {
        return NULL;
    }

    return basename_v2(g_exeName);
}

unsigned short calc_checksum(unsigned short *buffer, int size, U32 sum) {
    unsigned long cksum = ((sum & 0xFFFF0000) >> 16) + (sum & 0xFFFF);

    while (size > 1) {
        cksum += *buffer++;
        size  -= sizeof(unsigned short);
    }

    if (size) {
        cksum += *(unsigned short *)buffer;
    }

    cksum  = (cksum >> 16) + (cksum & 0xffff);
    cksum += (cksum >> 16);

    return cksum;
    //return (unsigned short)(~cksum);
}

unsigned short ip_checksum(unsigned char *pIp) {
    unsigned int  csum;
    struct iphdr *pIph = (struct iphdr *)pIp;
    pIph->check        = 0;

    csum = calc_checksum((unsigned short *)pIp, 20, 0);

    csum = ((csum & 0xFFFF0000) >> 16) + (csum & 0xFFFF);
    return (unsigned short)(~csum);
}

unsigned short udp_checksum(unsigned int saddr, unsigned int daddr, unsigned char *pUdp) {
    unsigned char  padBuf[12];
    unsigned int   csum;
    struct udphdr *pUhd = (struct udphdr *)pUdp;

    memcpy(&padBuf[0], &saddr, 4);
    memcpy(&padBuf[4], &daddr, 4);
    padBuf[8]  = 0;
    padBuf[9]  = 17;
    padBuf[10] = pUdp[4];
    padBuf[11] = pUdp[5];

    pUhd->check = 0;
    csum        = calc_checksum((unsigned short *)pUdp, ntohs(pUhd->len), 0);
    csum        = calc_checksum((unsigned short *)padBuf, 12, csum);

    csum = ((csum & 0xFFFF0000) >> 16) + (csum & 0xFFFF);
    return (unsigned short)(~csum);
}

int string_mac_to_bytes(const char *pStrMac, unsigned char macByte[6]) {
    sds *tokens;
    int  i, nCnt;

    if (pStrMac == NULL || strlen(pStrMac) != strlen("00:00:00:00:00:00")) {
        LOG_MOD(error, ZLOG_MOD_MISC, "Input params error\n");
        return -ERR_INPUT_PARAMS;
    }

    tokens = sdssplitlen(pStrMac, (int)strlen(pStrMac), ":", 1, &nCnt);

    if (nCnt != 6) {
        LOG_MOD(error, ZLOG_MOD_MISC, "Input MAC[%s] error: %d\n", pStrMac, nCnt);
        return -ERR_INPUT_PARAMS;
    }

    for (i = 0; i < nCnt; i++) {
        macByte[i] = strtoul(tokens[i], NULL, 16);
    }

    if (tokens) {
        sdsfreesplitres(tokens, nCnt);
    }

    return ERR_SUCCESS;
}