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

#include "config.h"
#include "toml.h"

// return  0: success
// retuun -1: failed
static int parse_device_section(toml_table_t *root, struct device_options *opts)
{
    const char *ptr;
    toml_table_t *table;

    table = toml_table_in(root, "device");
    if (table == NULL)
    {
        CONFIG_LOG_ERROR("config file missing device section");
        return -1;
    }

    ptr = toml_raw_in(table, "base");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing device->base");
        return -1;
    }
    opts->base = atoi(ptr);

    ptr = toml_raw_in(table, "offset");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing device->offset");
        return -1;
    }
    opts->offset = atoi(ptr);

    return 0;
}

// return  0: success
// retuun -1: failed
static int parse_packet_io_section(toml_table_t *root, struct packet_io_options *opts)
{
    const char *ptr;
    toml_table_t *table;
    toml_array_t *mask_array;

    table = toml_table_in(root, "packet_io");
    if (table == NULL)
    {
        CONFIG_LOG_ERROR("config file missing packet_io section");
        return -1;
    }

    ptr = toml_raw_in(table, "mode");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing packet_io->mode");
        return -1;
    }
    if (strcmp(ptr, "dumpfile") == 0)
    {
        opts->mode = PACKET_IO_DUMPFILE;
    }
    else if (strcmp(ptr, "marsio") == 0)
    {
        opts->mode = PACKET_IO_MARSIO;
    }
    else
    {
        CONFIG_LOG_ERROR("config file invalid packet_io->mode %s, only support dumpfile and marsio", ptr);
        return -1;
    }

    if (opts->mode == PACKET_IO_DUMPFILE)
    {
        ptr = toml_raw_in(table, "dumpfile_dir");
        if (ptr == NULL)
        {
            CONFIG_LOG_ERROR("config file missing packet_io->dumpfile_dir");
            return -1;
        }
        // skip ""
        strncpy(opts->dumpfile_dir, ptr + 1, strlen(ptr) - 2);
    }
    else
    {
        ptr = toml_raw_in(table, "app_symbol");
        if (ptr == NULL)
        {
            CONFIG_LOG_ERROR("config file missing packet_io->app_symbol");
            return -1;
        }
        strncpy(opts->app_symbol, ptr, sizeof(opts->app_symbol) - 1);

        ptr = toml_raw_in(table, "dev_symbol");
        if (ptr == NULL)
        {
            CONFIG_LOG_ERROR("config file missing packet_io->dev_symbol");
            return -1;
        }
        strncpy(opts->dev_symbol, ptr, sizeof(opts->dev_symbol) - 1);
    }

    ptr = toml_raw_in(table, "nr_threads");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing packet_io->nr_threads");
        return -1;
    }
    if (atoi(ptr) <= 0 || atoi(ptr) > MAX_THREAD_NUM)
    {
        CONFIG_LOG_ERROR("config file invalid packet_io->nr_threads %d, range [1, %d]", atoi(ptr), MAX_THREAD_NUM);
        return -1;
    }
    opts->nr_threads = atoi(ptr);

    mask_array = toml_array_in(table, "cpu_mask");
    if (mask_array == NULL)
    {
        CONFIG_LOG_ERROR("config file missing packet_io->cpu_mask");
        return -1;
    }
    for (uint16_t i = 0; i < opts->nr_threads; i++)
    {
        ptr = toml_raw_at(mask_array, i);
        if (ptr == NULL)
        {
            CONFIG_LOG_ERROR("config file missing packet_io->cpu_mask[%d]", i);
            return -1;
        }
        opts->cpu_mask[i] = atoi(ptr);
    }

    return 0;
}

// return  0: success
// retuun -1: failed
static int parse_ip_reassembly_section(toml_table_t *root, struct ip_reassembly_options *opts)
{
    const char *ptr;
    toml_table_t *table;

    table = toml_table_in(root, "ip_reassembly");
    if (table == NULL)
    {
        CONFIG_LOG_ERROR("config file missing ip_reassembly section");
        return -1;
    }

    ptr = toml_raw_in(table, "enable");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing ip_reassembly->enable");
        return -1;
    }
    opts->enable = atoi(ptr);

    ptr = toml_raw_in(table, "timeout");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing ip_reassembly->timeout");
        return -1;
    }
    opts->timeout = atoi(ptr);

    ptr = toml_raw_in(table, "bucket_entries");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing ip_reassembly->bucket_entries");
        return -1;
    }
    opts->bucket_entries = atoi(ptr);

    ptr = toml_raw_in(table, "bucket_num");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing ip_reassembly->bucket_num");
        return -1;
    }
    opts->bucket_num = atoi(ptr);

    return 0;
}

// return  0: success
// retuun -1: failed
static int parse_session_manager_section(toml_table_t *root, struct session_manager_options *opts)
{
    const char *ptr;
    toml_table_t *table;

    table = toml_table_in(root, "session_manager");
    if (table == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager section");
        return -1;
    }

    // max session number
    ptr = toml_raw_in(table, "max_tcp_session_num");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->max_tcp_session_num");
        return -1;
    }
    opts->max_tcp_session_num = atoll(ptr);

    ptr = toml_raw_in(table, "max_udp_session_num");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->max_udp_session_num");
        return -1;
    }
    opts->max_udp_session_num = atoll(ptr);

    // session overload (1: evict old session, 0: bypass new session)
    ptr = toml_raw_in(table, "tcp_overload_evict_old_sess");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->tcp_overload_evict_old_sess");
        return -1;
    }
    opts->tcp_overload_evict_old_sess = atoi(ptr);

    ptr = toml_raw_in(table, "udp_overload_evict_old_sess");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->udp_overload_evict_old_sess");
        return -1;
    }
    opts->udp_overload_evict_old_sess = atoi(ptr);

    // TCP timeout
    ptr = toml_raw_in(table, "tcp_init_timeout");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->tcp_init_timeout");
        return -1;
    }
    opts->tcp_init_timeout = atoll(ptr);

    ptr = toml_raw_in(table, "tcp_handshake_timeout");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->tcp_handshake_timeout");
        return -1;
    }
    opts->tcp_handshake_timeout = atoll(ptr);

    ptr = toml_raw_in(table, "tcp_data_timeout");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->tcp_data_timeout");
        return -1;
    }
    opts->tcp_data_timeout = atoll(ptr);

    ptr = toml_raw_in(table, "tcp_half_closed_timeout");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->tcp_half_closed_timeout");
        return -1;
    }
    opts->tcp_half_closed_timeout = atoll(ptr);

    ptr = toml_raw_in(table, "tcp_time_wait_timeout");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->tcp_time_wait_timeout");
        return -1;
    }
    opts->tcp_time_wait_timeout = atoll(ptr);

    ptr = toml_raw_in(table, "tcp_discard_timeout");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->tcp_discard_timeout");
        return -1;
    }
    opts->tcp_discard_timeout = atoll(ptr);

    ptr = toml_raw_in(table, "tcp_unverified_rst_timeout");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->tcp_unverified_rst_timeout");
        return -1;
    }
    opts->tcp_unverified_rst_timeout = atoll(ptr);

    // UDP timeout
    ptr = toml_raw_in(table, "udp_data_timeout");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->udp_data_timeout");
        return -1;
    }
    opts->udp_data_timeout = atoll(ptr);

    ptr = toml_raw_in(table, "udp_discard_timeout");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->udp_discard_timeout");
        return -1;
    }
    opts->udp_discard_timeout = atoll(ptr);

    // duplicate packet filter
    ptr = toml_raw_in(table, "duplicated_packet_filter_enable");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->duplicated_packet_filter_enable");
        return -1;
    }
    opts->duplicated_packet_filter_enable = atoi(ptr);

    ptr = toml_raw_in(table, "duplicated_packet_filter_capacity");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->duplicated_packet_filter_capacity");
        return -1;
    }
    opts->duplicated_packet_filter_capacity = atoi(ptr);

    ptr = toml_raw_in(table, "duplicated_packet_filter_timeout");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->duplicated_packet_filter_timeout");
        return -1;
    }
    opts->duplicated_packet_filter_timeout = atoi(ptr);

    ptr = toml_raw_in(table, "duplicated_packet_filter_error_rate");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->duplicated_packet_filter_error_rate");
        return -1;
    }
    opts->duplicated_packet_filter_error_rate = atof(ptr);

    // eviction session filter
    ptr = toml_raw_in(table, "evicted_session_filter_enable");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->evicted_session_filter_enable");
        return -1;
    }
    opts->evicted_session_filter_enable = atoi(ptr);

    ptr = toml_raw_in(table, "evicted_session_filter_capacity");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->evicted_session_filter_capacity");
        return -1;
    }
    opts->evicted_session_filter_capacity = atoi(ptr);

    ptr = toml_raw_in(table, "evicted_session_filter_timeout");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->evicted_session_filter_timeout");
        return -1;
    }
    opts->evicted_session_filter_timeout = atoi(ptr);

    ptr = toml_raw_in(table, "evicted_session_filter_error_rate");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->evicted_session_filter_error_rate");
        return -1;
    }
    opts->evicted_session_filter_error_rate = atof(ptr);

    // TCP reassembly
    ptr = toml_raw_in(table, "tcp_reassembly_enable");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->tcp_reassembly_enable");
        return -1;
    }
    opts->tcp_reassembly_enable = atoi(ptr);

    ptr = toml_raw_in(table, "tcp_reassembly_max_timeout");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->tcp_reassembly_max_timeout");
        return -1;
    }
    opts->tcp_reassembly_max_timeout = atoi(ptr);

    ptr = toml_raw_in(table, "tcp_reassembly_max_segments");
    if (ptr == NULL)
    {
        CONFIG_LOG_ERROR("config file missing session_manager->tcp_reassembly_max_segments");
        return -1;
    }
    opts->tcp_reassembly_max_segments = atoi(ptr);

    return 0;
}

// return  0: success
// retuun -1: failed
int stellar_load_config(const char *file, struct stellar_config *config)
{
    int ret = -1;
    char errbuf[200];
    FILE *fp = NULL;
    toml_table_t *table = NULL;
    memset(config, 0, sizeof(*config));

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

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

    if (parse_device_section(table, &config->dev_opts) != 0)
    {
        goto error_out;
    }

    if (parse_packet_io_section(table, &config->io_opts) != 0)
    {
        goto error_out;
    }

    if (parse_ip_reassembly_section(table, &config->ip_opts) != 0)
    {
        goto error_out;
    }

    if (parse_session_manager_section(table, &config->sess_mgr_opts) != 0)
    {
        goto error_out;
    }

    ret = 0;

error_out:
    if (table)
    {
        toml_free(table);
    }

    if (fp)
    {
        fclose(fp);
    }

    return ret;
}

void stellar_print_config(struct stellar_config *config)
{
    if (config == NULL)
    {
        return;
    }

    struct device_options *dev_opts = &config->dev_opts;
    struct packet_io_options *io_opts = &config->io_opts;
    struct ip_reassembly_options *ip_opts = &config->ip_opts;
    struct session_manager_options *sess_mgr_opts = &config->sess_mgr_opts;

    // device config
    CONFIG_LOG_DEBUG("device->base                                         : %d", dev_opts->base);
    CONFIG_LOG_DEBUG("device->offset                                       : %d", dev_opts->offset);

    // packet io config
    CONFIG_LOG_DEBUG("packet_io->mode                                      : %s", io_opts->mode == PACKET_IO_DUMPFILE ? "dumpfile" : "marsio");
    if (io_opts->mode == PACKET_IO_DUMPFILE)
    {
        CONFIG_LOG_DEBUG("packet_io->dumpfile_dir                              : %s", io_opts->dumpfile_dir);
    }
    else
    {
        CONFIG_LOG_DEBUG("packet_io->app_symbol                                : %s", io_opts->app_symbol);
        CONFIG_LOG_DEBUG("packet_io->dev_symbol                                : %s", io_opts->dev_symbol);
    }
    CONFIG_LOG_DEBUG("packet_io->nr_threads                                : %d", io_opts->nr_threads);
    for (uint16_t i = 0; i < io_opts->nr_threads; i++)
    {
        CONFIG_LOG_DEBUG("packet_io->cpu_mask[%3d]                             : %d", i, io_opts->cpu_mask[i]);
    }

    // ip reassemble config
    CONFIG_LOG_DEBUG("ip_reassembly->enable                                : %d", ip_opts->enable);
    CONFIG_LOG_DEBUG("ip_reassembly->timeout                               : %d", ip_opts->timeout);
    CONFIG_LOG_DEBUG("ip_reassembly->bucket_entries                        : %d", ip_opts->bucket_entries);
    CONFIG_LOG_DEBUG("ip_reassembly->bucket_num                            : %d", ip_opts->bucket_num);

    // session manager config -> max session number
    CONFIG_LOG_DEBUG("session_manager->max_tcp_session_num                 : %ld", sess_mgr_opts->max_tcp_session_num);
    CONFIG_LOG_DEBUG("session_manager->max_udp_session_num                 : %ld", sess_mgr_opts->max_udp_session_num);

    // session manager config -> session overload evict
    CONFIG_LOG_DEBUG("session_manager->tcp_overload_evict_old_sess         : %d", sess_mgr_opts->tcp_overload_evict_old_sess);
    CONFIG_LOG_DEBUG("session_manager->udp_overload_evict_old_sess         : %d", sess_mgr_opts->udp_overload_evict_old_sess);

    // session manager config -> session timeout
    CONFIG_LOG_DEBUG("session_manager->tcp_init_timeout                    : %ld", sess_mgr_opts->tcp_init_timeout);
    CONFIG_LOG_DEBUG("session_manager->tcp_handshake_timeout               : %ld", sess_mgr_opts->tcp_handshake_timeout);
    CONFIG_LOG_DEBUG("session_manager->tcp_data_timeout                    : %ld", sess_mgr_opts->tcp_data_timeout);
    CONFIG_LOG_DEBUG("session_manager->tcp_half_closed_timeout             : %ld", sess_mgr_opts->tcp_half_closed_timeout);
    CONFIG_LOG_DEBUG("session_manager->tcp_time_wait_timeout               : %ld", sess_mgr_opts->tcp_time_wait_timeout);
    CONFIG_LOG_DEBUG("session_manager->tcp_discard_timeout                 : %ld", sess_mgr_opts->tcp_discard_timeout);
    CONFIG_LOG_DEBUG("session_manager->tcp_unverified_rst_timeout          : %ld", sess_mgr_opts->tcp_unverified_rst_timeout);
    CONFIG_LOG_DEBUG("session_manager->udp_data_timeout                    : %ld", sess_mgr_opts->udp_data_timeout);
    CONFIG_LOG_DEBUG("session_manager->udp_discard_timeout                 : %ld", sess_mgr_opts->udp_discard_timeout);

    // session manager config -> duplicated packet filter
    CONFIG_LOG_DEBUG("session_manager->duplicated_packet_filter_enable     : %d", sess_mgr_opts->duplicated_packet_filter_enable);
    CONFIG_LOG_DEBUG("session_manager->duplicated_packet_filter_capacity   : %d", sess_mgr_opts->duplicated_packet_filter_capacity);
    CONFIG_LOG_DEBUG("session_manager->duplicated_packet_filter_timeout    : %d", sess_mgr_opts->duplicated_packet_filter_timeout);
    CONFIG_LOG_DEBUG("session_manager->duplicated_packet_filter_error_rate : %f", sess_mgr_opts->duplicated_packet_filter_error_rate);

    // session manager config -> evicted session filter
    CONFIG_LOG_DEBUG("session_manager->evicted_session_filter_enable       : %d", sess_mgr_opts->evicted_session_filter_enable);
    CONFIG_LOG_DEBUG("session_manager->evicted_session_filter_capacity     : %d", sess_mgr_opts->evicted_session_filter_capacity);
    CONFIG_LOG_DEBUG("session_manager->evicted_session_filter_timeout      : %d", sess_mgr_opts->evicted_session_filter_timeout);
    CONFIG_LOG_DEBUG("session_manager->evicted_session_filter_error_rate   : %f", sess_mgr_opts->evicted_session_filter_error_rate);

    // session manager config -> TCP reassembly
    CONFIG_LOG_DEBUG("session_manager->tcp_reassembly_enable               : %d", sess_mgr_opts->tcp_reassembly_enable);
    CONFIG_LOG_DEBUG("session_manager->tcp_reassembly_max_timeout          : %d", sess_mgr_opts->tcp_reassembly_max_timeout);
    CONFIG_LOG_DEBUG("session_manager->tcp_reassembly_max_segments         : %d", sess_mgr_opts->tcp_reassembly_max_segments);
}
