#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include "toml.h"
#include "pcap_io.h"
#include "marsio_io.h"
#include "log_private.h"

#define PACKET_IO_LOG_ERROR(format, ...) STELLAR_LOG_ERROR(__thread_local_logger, "packet_io", format, ##__VA_ARGS__)
#define PACKET_IO_LOG_INFO(format, ...) STELLAR_LOG_INFO(__thread_local_logger, "packet_io", format, ##__VA_ARGS__)

struct packet_io
{
    void *handle;

    void *(*new_func)(const struct packet_io_config *cfg);
    void (*free_func)(void *handle);
    int (*isbreak_func)(void *handle);

    int (*init_func)(void *handle, uint16_t thr_idx);
    uint16_t (*input_func)(void *handle, uint16_t thr_idx, struct packet *pkts, uint16_t nr_pkts);
    void (*output_func)(void *handle, uint16_t thr_idx, struct packet *pkts, uint16_t nr_pkts);
    void (*drop_func)(void *handle, uint16_t thr_idx, struct packet *pkts, uint16_t nr_pkts);
    uint16_t (*inject_func)(void *handle, uint16_t thr_idx, struct packet *pkts, uint16_t nr_pkts);
    void (*yield_func)(void *handle, uint16_t thr_idx);
    struct packet_io_stat *(*stat_func)(void *handle, uint16_t thr_idx);
};

int packet_io_config_load(struct packet_io_config *cfg, const char *toml_file)
{
    int ret = -1;
    const char *ptr;
    char *ptr_mode = NULL;
    char *ptr_pcap_path = NULL;
    char *ptr_app_symbol = NULL;
    char *ptr_dev_symbol = NULL;
    char errbuf[200];
    FILE *fp = NULL;
    toml_table_t *root = NULL;
    toml_table_t *table = NULL;
    toml_array_t *mask;

    fp = fopen(toml_file, "r");
    if (fp == NULL)
    {
        PACKET_IO_LOG_ERROR("config file %s open failed, %s", toml_file, strerror(errno));
        goto error_out;
    }

    root = toml_parse_file(fp, errbuf, sizeof(errbuf));
    if (root == NULL)
    {
        PACKET_IO_LOG_ERROR("config file %s parse failed, %s", toml_file, errbuf);
        goto error_out;
    }

    table = toml_table_in(root, "packet_io");
    if (table == NULL)
    {
        PACKET_IO_LOG_ERROR("config file %s missing packet_io", toml_file);
        goto error_out;
    }

    ptr = toml_raw_in(table, "mode");
    if (ptr == NULL || toml_rtos(ptr, &ptr_mode) != 0)
    {
        PACKET_IO_LOG_ERROR("config file missing packet_io.mode");
        goto error_out;
    }
    if (strcmp(ptr_mode, "pcapfile") == 0)
    {
        cfg->mode = PACKET_IO_PCAPFILE;
    }
    else if (strcmp(ptr_mode, "pcaplist") == 0)
    {
        cfg->mode = PACKET_IO_PCAPLIST;
    }
    else if (strcmp(ptr_mode, "marsio") == 0)
    {
        cfg->mode = PACKET_IO_MARSIO;
    }
    else
    {
        PACKET_IO_LOG_ERROR("config file invalid packet_io.mode %s", ptr);
        goto error_out;
    }

    if (cfg->mode == PACKET_IO_PCAPFILE || cfg->mode == PACKET_IO_PCAPLIST)
    {
        ptr = toml_raw_in(table, "pcap_path");
        if (ptr == NULL || toml_rtos(ptr, &ptr_pcap_path) != 0)
        {
            PACKET_IO_LOG_ERROR("config file missing packet_io.pcap_path");
            goto error_out;
        }
        strcpy(cfg->pcap_path, ptr_pcap_path);
    }
    else
    {
        ptr = toml_raw_in(table, "app_symbol");
        if (ptr == NULL || toml_rtos(ptr, &ptr_app_symbol) != 0)
        {
            PACKET_IO_LOG_ERROR("config file missing packet_io.app_symbol");
            goto error_out;
        }
        strcpy(cfg->app_symbol, ptr_app_symbol);

        ptr = toml_raw_in(table, "dev_symbol");
        if (ptr == NULL || toml_rtos(ptr, &ptr_dev_symbol) != 0)
        {
            PACKET_IO_LOG_ERROR("config file missing packet_io.dev_symbol");
            goto error_out;
        }
        strcpy(cfg->dev_symbol, ptr_dev_symbol);
    }

    ptr = toml_raw_in(table, "nr_worker_thread");
    if (ptr == NULL)
    {
        PACKET_IO_LOG_ERROR("config file missing packet_io.nr_worker_thread");
        goto error_out;
    }
    cfg->nr_worker_thread = atoi(ptr);
    if (cfg->nr_worker_thread == 0 || cfg->nr_worker_thread > MAX_THREAD_NUM)
    {
        PACKET_IO_LOG_ERROR("config file invalid packet_io.nr_worker_thread %d, range [1, %d]", cfg->nr_worker_thread, MAX_THREAD_NUM);
        goto error_out;
    }

    mask = toml_array_in(table, "cpu_mask");
    if (mask == NULL)
    {
        PACKET_IO_LOG_ERROR("config file missing packet_io.cpu_mask");
        goto error_out;
    }
    for (uint16_t i = 0; i < cfg->nr_worker_thread; i++)
    {
        ptr = toml_raw_at(mask, i);
        if (ptr == NULL)
        {
            PACKET_IO_LOG_ERROR("config file missing packet_io.cpu_mask[%d]", i);
            goto error_out;
        }
        cfg->cpu_mask[i] = atoi(ptr);
    }

    ptr = toml_raw_in(table, "idle_yield_interval_ms");
    if (ptr == NULL)
    {
        PACKET_IO_LOG_ERROR("config file missing packet_io.idle_yield_interval_ms");
        goto error_out;
    }
    cfg->idle_yield_interval_ms = atoll(ptr);
    if (cfg->idle_yield_interval_ms > 60000)
    {
        PACKET_IO_LOG_ERROR("config file invalid packet_io.idle_yield_interval_ms %d, range [0, %d]", cfg->idle_yield_interval_ms, 60000);
        goto error_out;
    }

    ret = 0;
error_out:
    if (ptr_mode)
    {
        free(ptr_mode);
    }
    if (ptr_pcap_path)
    {
        free(ptr_pcap_path);
    }
    if (ptr_app_symbol)
    {
        free(ptr_app_symbol);
    }
    if (ptr_dev_symbol)
    {
        free(ptr_dev_symbol);
    }
    if (root)
    {
        toml_free(root);
    }
    if (fp)
    {
        fclose(fp);
    }

    return ret;
}

struct packet_io_config *packet_io_config_new(const char *toml_file)
{
    if (toml_file == NULL)
    {
        return NULL;
    }

    struct packet_io_config *cfg = (struct packet_io_config *)calloc(1, sizeof(struct packet_io_config));
    if (cfg == NULL)
    {
        return NULL;
    }

    if (packet_io_config_load(cfg, toml_file) == -1)
    {
        packet_io_config_free(cfg);
        return NULL;
    }

    return cfg;
}

void packet_io_config_free(struct packet_io_config *cfg)
{
    if (cfg)
    {
        free(cfg);
        cfg = NULL;
    }
}

void packet_io_config_print(const struct packet_io_config *cfg)
{
    if (cfg)
    {
        PACKET_IO_LOG_INFO("packet_io.mode                   : %s", cfg->mode == PACKET_IO_PCAPFILE ? "pcapfile" : (cfg->mode == PACKET_IO_PCAPLIST ? "pcaplist" : "marsio"));
        if (cfg->mode == PACKET_IO_PCAPFILE || cfg->mode == PACKET_IO_PCAPLIST)
        {
            PACKET_IO_LOG_INFO("packet_io.pcap_path              : %s", cfg->pcap_path);
        }
        else
        {
            PACKET_IO_LOG_INFO("packet_io.app_symbol             : %s", cfg->app_symbol);
            PACKET_IO_LOG_INFO("packet_io.dev_symbol             : %s", cfg->dev_symbol);
        }
        PACKET_IO_LOG_INFO("packet_io.nr_worker_thread       : %d", cfg->nr_worker_thread);
        for (uint16_t i = 0; i < cfg->nr_worker_thread; i++)
        {
            PACKET_IO_LOG_INFO("packet_io.cpu_mask[%03d]        : %d", i, cfg->cpu_mask[i]);
        }
        PACKET_IO_LOG_INFO("packet_io.idle_yield_interval_ms : %lu", cfg->idle_yield_interval_ms);
    }
}

struct packet_io *packet_io_new(const struct packet_io_config *cfg)
{
    struct packet_io *pkt_io = (struct packet_io *)calloc(1, sizeof(struct packet_io));
    if (pkt_io == NULL)
    {
        return NULL;
    }

    if (cfg->mode == PACKET_IO_MARSIO)
    {
        pkt_io->new_func = marsio_io_new;
        pkt_io->free_func = marsio_io_free;
        pkt_io->isbreak_func = marsio_io_isbreak;
        pkt_io->init_func = marsio_io_init;
        pkt_io->input_func = marsio_io_input;
        pkt_io->output_func = marsio_io_output;
        pkt_io->drop_func = marsio_io_drop;
        pkt_io->inject_func = marsio_io_inject;
        pkt_io->yield_func = marsio_io_yield;
        pkt_io->stat_func = marsio_io_stat;
    }
    else
    {
        pkt_io->new_func = pcap_io_new;
        pkt_io->free_func = pcap_io_free;
        pkt_io->isbreak_func = pcap_io_isbreak;
        pkt_io->init_func = pcap_io_init;
        pkt_io->input_func = pcap_io_input;
        pkt_io->output_func = pcap_io_output;
        pkt_io->drop_func = pcap_io_drop;
        pkt_io->inject_func = pcap_io_inject;
        pkt_io->yield_func = pcap_io_yield;
        pkt_io->stat_func = pcap_io_stat;
    }

    pkt_io->handle = pkt_io->new_func(cfg);
    if (pkt_io->handle == NULL)
    {
        packet_io_free(pkt_io);
        return NULL;
    }

    return pkt_io;
}

void packet_io_free(struct packet_io *pkt_io)
{
    if (pkt_io)
    {
        if (pkt_io->handle)
        {
            pkt_io->free_func(pkt_io->handle);
        }
        free(pkt_io);
        pkt_io = NULL;
    }
}

int packet_io_isbreak(struct packet_io *pkt_io)
{
    return pkt_io->isbreak_func(pkt_io->handle);
}

int packet_io_init(struct packet_io *pkt_io, uint16_t thr_idx)
{
    return pkt_io->init_func(pkt_io->handle, thr_idx);
}

uint16_t packet_io_input(struct packet_io *pkt_io, uint16_t thr_idx, struct packet *pkts, uint16_t nr_pkts)
{
    return pkt_io->input_func(pkt_io->handle, thr_idx, pkts, nr_pkts);
}

void packet_io_output(struct packet_io *pkt_io, uint16_t thr_idx, struct packet *pkts, uint16_t nr_pkts)
{
    pkt_io->output_func(pkt_io->handle, thr_idx, pkts, nr_pkts);
}

void packet_io_drop(struct packet_io *pkt_io, uint16_t thr_idx, struct packet *pkts, uint16_t nr_pkts)
{
    pkt_io->drop_func(pkt_io->handle, thr_idx, pkts, nr_pkts);
}

uint16_t packet_io_inject(struct packet_io *pkt_io, uint16_t thr_idx, struct packet *pkts, uint16_t nr_pkts)
{
    return pkt_io->inject_func(pkt_io->handle, thr_idx, pkts, nr_pkts);
}

void packet_io_yield(struct packet_io *pkt_io, uint16_t thr_idx)
{
    pkt_io->yield_func(pkt_io->handle, thr_idx);
}

struct packet_io_stat *packet_io_stat(struct packet_io *pkt_io, uint16_t thr_idx)
{
    return pkt_io->stat_func(pkt_io->handle, thr_idx);
}
