//
// Created by xajhuang on 2023/3/23.
//

#include <time.h>
#include "lease.h"
#include "user_errno.h"
#include "zlog_module.h"
#include "database.h"
#include "user_mgr.h"

#define LEASE_DB_NAME       ("lease")
#define PREALLOC_IP_TIMEOUT (60)

typedef struct {
    U32            ipAddr;
    char           macAddr[20];
    U32            bitset;
    PIPPOOL_INFO   pCtx;
    U32            timeStamp;
    UT_hash_handle hh;
} PRE_ALLOC_IP, *PPRE_ALLOC_IP;

//static PMAC_FILTER   g_allowTbl     = NULL;
//static PMAC_FILTER   g_blackListTbl = NULL;
static PPRE_ALLOC_IP g_pPreAllocIp = NULL;

#define CREATE_LEASE_TABLE(name)                        \
    "CREATE TABLE IF NOT EXISTS " #name                 \
    " (  id       INTEGER PRIMARY KEY AUTOINCREMENT,"   \
    "    uid      INTEGER         NOT NULL,"            \
    "    mac      CHAR(20)        NOT NULL,"            \
    "    ip       INTEGER         NOT NULL,"            \
    "    lease    INTEGER         NOT NULL,"            \
    "    createTm INTEGER         NOT NULL,"            \
    "    netmask  INTEGER,"                             \
    "    gateway  INTEGER,"                             \
    "    dns1     INTEGER,"                             \
    "    dns2     INTEGER,"                             \
    "    server   INTEGER         NOT NULL,"            \
    "    hostname CHAR(64)        DEFAULT '' NOT NULL," \
    "    keyType  INTEGER         NOT NULL"             \
    ");"                                                \
    "CREATE INDEX IF NOT EXISTS " #name "_index ON " #name " (uid, mac);"

int pre_alloc_dhcp_res(U32 uid, const char *pMac, U32 *pOutIp, PIPPOOL_INFO *pOutPool) {
    PIPPOOL_INFO  pPool, pTemp;
    PPRE_ALLOC_IP pNewIp, pTmp, pCache = NULL;
    PIPPOOL_INFO  pUserPool;

    if (pOutIp == NULL || pOutPool == NULL) {
        LOG_MOD(error, ZLOG_MOD_DHCPD, "Input params error: %p, %p\n", pOutIp, pOutPool);
        return -ERR_INPUT_PARAMS;
    }

    pUserPool = user_get_pool(uid);
    if (pUserPool == NULL) {
        LOG_MOD(error, ZLOG_MOD_DHCPD, "Can't found avaliable address pool\n");
        return -ERR_DHCP_NO_POOL;
    }

    // 遍历当前用户所有IP地址池
    HASH_ITER(hh, pUserPool, pPool, pTemp) {
        U32 addr;

        // 查看是否预分配过该设备
        if (pMac && strlen(pMac) > 0) {
            HASH_ITER(hh, g_pPreAllocIp, pNewIp, pTmp) {
                if (strcmp(pNewIp->macAddr, pMac) == 0) {
                    *pOutIp   = pNewIp->ipAddr;
                    *pOutPool = pPool;

                    return ERR_SUCCESS;
                }
            }
        }

        while ((addr = bitset_minimum(pPool->assignPool)) > 0) {
            U32 ipAddr = (pPool->minAddr & pPool->netMask) + addr;

            // 查找租约配置文件中是否记录了曾经分配的地址, 如果已经分配则获取下一个
            // TODO: add process
            //            if (FALSE) {
            //                bitset_set(pPool->assignPool, addr);
            //                continue;
            //            }

            // 查找IP地址是否已经被预分配
            HASH_FIND_INT(g_pPreAllocIp, &ipAddr, pNewIp);

            if (pNewIp == NULL) {
                pNewIp = (PPRE_ALLOC_IP)malloc(sizeof(PRE_ALLOC_IP));

                if (!pNewIp) {
                    LOG_MOD(error, ZLOG_MOD_DHCPD, "Malloc memory error: %lu\n", sizeof(PRE_ALLOC_IP));
                    continue;
                }

                memset(pNewIp, 0, sizeof(PRE_ALLOC_IP));

                pNewIp->timeStamp = time(NULL);
                pNewIp->pCtx      = pPool;
                pNewIp->ipAddr    = ipAddr;
                pNewIp->bitset    = addr;
                if (pMac) {
                    strcpy(pNewIp->macAddr, pMac);
                }

                HASH_ADD_INT(g_pPreAllocIp, ipAddr, pNewIp);

                *pOutIp   = ipAddr;
                *pOutPool = pPool;

                bitset_cls_bit(pPool->assignPool, addr);
                return ERR_SUCCESS;
            } else {
                if (time(NULL) - pNewIp->timeStamp < PREALLOC_IP_TIMEOUT) {
                    continue;
                }

                *pOutIp   = pNewIp->ipAddr;
                *pOutPool = pPool;

                return ERR_SUCCESS;
            }
        }
    }

    // 如果没有分配到IP,清理过期的预分配IP
    HASH_ITER(hh, g_pPreAllocIp, pNewIp, pTmp) {
        if (time(NULL) - pNewIp->timeStamp > PREALLOC_IP_TIMEOUT) {
            if (pCache == NULL) {
                pCache = pNewIp;
            } else {
                HASH_DEL(g_pPreAllocIp, pNewIp);
                free(pNewIp);
                bitset_cls_bit(pNewIp->pCtx->assignPool, pNewIp->bitset);
            }
        }
    }

    if (pCache) {
        *pOutIp   = pCache->ipAddr;
        *pOutPool = pPool;
        return ERR_SUCCESS;
    }

    // 没有可预分配的IP,报错
    LOG_MOD(error, ZLOG_MOD_DHCPD, "No free ipaddress in poll:  uid = %u, pool = 0x%08X\n", uid, pUserPool->poolKey);
    return -ERR_DHCP_NO_ADDR;
}

int dhcp_lease_init() {
    int rc = 0;

    rc = db_sqlite3_sql_exec(CREATE_LEASE_TABLE(LEASE_DB_NAME), NULL, NULL, NULL);

    if (rc != ERR_SUCCESS) {
        return rc;
    }

    return ERR_SUCCESS;
}