#include <assert.h>
#include <tfe_cmsg.h>
#include <tfe_utils.h>
#include <tfe_resource.h>
#include <cjson/cJSON.h>
#include <MESA/maat.h>

#include "tcp_policy.h"

struct tcp_policy_enforcer
{
    struct maat *maat;
    int table_id;
    void *logger;
};

struct side_conn_param
{
    int maxseg_enable;
    int maxseg_vaule;
    int nodelay;
    int keepalive;
    int keepcnt;
    int keepidle;
    int keepintvl;
    int ttl;
    int user_timeout;
};

struct tcp_profile_param
{
    int ref_cnt;
    int tcp_passthrough;
    int bypass_duplicated_packet; // TODO no use, need delete from CM

    struct side_conn_param client_side;
    struct side_conn_param server_side;
};

static int parser_side_conn_param(const char *json_str, struct side_conn_param *out_val, void *logger)
{
    cJSON *json = NULL;
    cJSON *object = NULL;
    cJSON *item = NULL;

    json = cJSON_Parse(json_str);
    if (json == NULL)
    {
        TFE_LOG_ERROR(logger, "Invalid tcp option param %s", json_str);
        return -1;
    }

    object = cJSON_GetObjectItem(json, "tcp_maxseg");
    if (object)
    {
        item = cJSON_GetObjectItem(object, "enable");
        if (item && item->type == cJSON_Number)
        {
            out_val->maxseg_enable = item->valueint;
        }
        item = cJSON_GetObjectItem(object, "maxseg");
        if (item && item->type == cJSON_Number)
        {
            out_val->maxseg_vaule = item->valueint;
        }
    }

    item = cJSON_GetObjectItem(json, "nodelay");
    if (item && item->type == cJSON_Number)
    {
        out_val->nodelay = item->valueint;
    }

    object = cJSON_GetObjectItem(json, "keep_alive");
    if (object)
    {
        item = cJSON_GetObjectItem(object, "enable");
        if (item && item->type == cJSON_Number)
        {
            out_val->keepalive = item->valueint;
        }
        item = cJSON_GetObjectItem(object, "tcp_keepcnt");
        if (item && item->type == cJSON_Number)
        {
            out_val->keepcnt = item->valueint;
        }
        item = cJSON_GetObjectItem(object, "tcp_keepidle");
        if (item && item->type == cJSON_Number)
        {
            out_val->keepidle = item->valueint;
        }
        item = cJSON_GetObjectItem(object, "tcp_keepintvl");
        if (item && item->type == cJSON_Number)
        {
            out_val->keepintvl = item->valueint;
        }
    }

    item = cJSON_GetObjectItem(json, "ttl");
    if (item && item->type == cJSON_Number)
    {
        out_val->ttl = item->valueint;
    }

    item = cJSON_GetObjectItem(json, "user_timeout");
    if (item && item->type == cJSON_Number)
    {
        out_val->user_timeout = item->valueint;
    }
    cJSON_Delete(json);

    return 0;
}

static void profile_param_new_cb(const char *table_name, int table_id, const char *key, const char *table_line, void **ad, long argl, void *argp)
{
    int ret = 0;
    int profile_id = 0;
    int tcp_passthrough = 0;
    int bypass_duplicated_packet = 0;
    char client_side_conn_param[512] = {0};
    char server_side_conn_param[512] = {0};
    int is_valid = 0;
    struct tcp_profile_param *param = NULL;
    struct tcp_policy_enforcer *enforcer = (struct tcp_policy_enforcer *)argp;

    ret = sscanf(table_line, "%d\t%d\t%d\t%s\t%s\t%d", &profile_id, &tcp_passthrough, &bypass_duplicated_packet, client_side_conn_param, server_side_conn_param, &is_valid);
    if (ret != 6)
    {
        TFE_LOG_ERROR(enforcer->logger, "Invalid tcp option profile: %s", table_line);
        goto error_out;
    }

    param = ALLOC(struct tcp_profile_param, 1);
    param->ref_cnt = 1;
    param->tcp_passthrough = tcp_passthrough;
    param->bypass_duplicated_packet = bypass_duplicated_packet;

    if (parser_side_conn_param(client_side_conn_param, &param->client_side, enforcer->logger) == -1)
    {
        goto error_out;
    }

    if (parser_side_conn_param(server_side_conn_param, &param->server_side, enforcer->logger) == -1)
    {
        goto error_out;
    }

    *ad = param;
    TFE_LOG_INFO(enforcer->logger, "Add tcp option profile: %s", key);
    return;

error_out:
    if (param)
    {
        free(param);
    }
}

static void profile_param_free_cb(int table_id, void **ad, long argl, void *argp)
{
    struct tcp_profile_param *param = (struct tcp_profile_param *)*ad;
    if (param == NULL)
    {
        return;
    }

    if ((__sync_sub_and_fetch(&param->ref_cnt, 1) == 0))
    {
        free(param);
        *ad = NULL;
    }
}

static void profile_param_dup_cb(int table_id, void **to, void **from, long argl, void *argp)
{
    struct tcp_profile_param *param = (struct tcp_profile_param *)*from;
    if (param)
    {
        __sync_add_and_fetch(&(param->ref_cnt), 1);
        *to = param;
    }
    else
    {
        *to = NULL;
    }
}

static void profile_param_free(struct tcp_profile_param *param)
{
    profile_param_free_cb(0, (void **)&param, 0, NULL);
}

struct tcp_policy_enforcer *tcp_policy_enforcer_create(void *logger)
{
    int ret = 0;
    struct tcp_policy_enforcer *enforcer = ALLOC(struct tcp_policy_enforcer, 1);
    enforcer->maat = (struct maat *)tfe_bussiness_resouce_get(STATIC_MAAT);
    enforcer->logger = logger;
    enforcer->table_id = maat_get_table_id(enforcer->maat, "PXY_PROFILE_TCP_OPTION");
    if (enforcer->table_id < 0)
    {
        TFE_LOG_ERROR(enforcer->logger, "failed at register table of PXY_PROFILE_TCP_OPTION, ret = %d", enforcer->table_id);
        goto error_out;
    }

    ret = maat_plugin_table_ex_schema_register(enforcer->maat, "PXY_PROFILE_TCP_OPTION",
                                               profile_param_new_cb,
                                               profile_param_free_cb,
                                               profile_param_dup_cb,
                                               0, enforcer);
    if (ret < 0)
    {
        TFE_LOG_ERROR(enforcer->logger, "failed at register callback of PXY_PROFILE_TCP_OPTION, ret = %d", ret);
        goto error_out;
    }
    return enforcer;

error_out:
    tcp_policy_enforcer_destory(enforcer);
    return NULL;
}

void tcp_policy_enforcer_destory(struct tcp_policy_enforcer *enforcer)
{
    if (enforcer)
    {
        free(enforcer);
        enforcer = NULL;
    }
}

// return  0 : success
// return -1 : error (need passthrough)
int tcp_policy_enforce(struct tcp_policy_enforcer *tcp_enforcer, struct tfe_cmsg *cmsg)
{
    int ret = 0;
    int profile_id = 0;
    uint16_t size = 0;
    char buffer[16] = {0};

    ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_OPTION_PROFILE_ID, (unsigned char *)&profile_id, sizeof(profile_id), &size);
    if (ret < 0)
    {
        TFE_LOG_ERROR(g_default_logger, "Failed at fetch tcp_option_profile from cmsg: %s", strerror(-ret));
        return -1;
    }

    snprintf(buffer, sizeof(buffer), "%d", profile_id);
    struct tcp_profile_param *param = (struct tcp_profile_param *)maat_plugin_table_get_ex_data(tcp_enforcer->maat, tcp_enforcer->table_id, buffer);
    if (param == NULL)
    {
        TFE_LOG_INFO(tcp_enforcer->logger, "Failed to get tcp option parameter of profile %d.", profile_id);
        return -1;
    }

    tfe_cmsg_set(cmsg, TFE_CMSG_TCP_PASSTHROUGH, (unsigned char *)&param->tcp_passthrough, sizeof(param->tcp_passthrough));

    struct side_conn_param *client_side = &param->client_side;
    tfe_cmsg_set(cmsg, TFE_CMSG_DOWNSTREAM_TCP_MSS_ENABLE, (unsigned char *)&client_side->maxseg_enable, sizeof(client_side->maxseg_enable));
    tfe_cmsg_set(cmsg, TFE_CMSG_DOWNSTREAM_TCP_MSS_VALUE, (unsigned char *)&client_side->maxseg_vaule, sizeof(client_side->maxseg_vaule));
    tfe_cmsg_set(cmsg, TFE_CMSG_DOWNSTREAM_TCP_NODELAY, (unsigned char *)&client_side->nodelay, sizeof(client_side->nodelay));
    tfe_cmsg_set(cmsg, TFE_CMSG_DOWNSTREAM_TCP_TTL, (unsigned char *)&client_side->ttl, sizeof(client_side->ttl));
    tfe_cmsg_set(cmsg, TFE_CMSG_DOWNSTREAM_TCP_KEEPALIVE, (unsigned char *)&client_side->keepalive, sizeof(client_side->keepalive));
    tfe_cmsg_set(cmsg, TFE_CMSG_DOWNSTREAM_TCP_KEEPCNT, (unsigned char *)&client_side->keepcnt, sizeof(client_side->keepcnt));
    tfe_cmsg_set(cmsg, TFE_CMSG_DOWNSTREAM_TCP_KEEPIDLE, (unsigned char *)&client_side->keepidle, sizeof(client_side->keepidle));
    tfe_cmsg_set(cmsg, TFE_CMSG_DOWNSTREAM_TCP_KEEPINTVL, (unsigned char *)&client_side->keepintvl, sizeof(client_side->keepintvl));
    tfe_cmsg_set(cmsg, TFE_CMSG_DOWNSTREAM_TCP_USER_TIMEOUT, (unsigned char *)&client_side->user_timeout, sizeof(client_side->user_timeout));

    struct side_conn_param *server_side = &param->server_side;
    tfe_cmsg_set(cmsg, TFE_CMSG_UPSTREAM_TCP_MSS_ENABLE, (unsigned char *)&server_side->maxseg_enable, sizeof(server_side->maxseg_enable));
    tfe_cmsg_set(cmsg, TFE_CMSG_UPSTREAM_TCP_MSS_VALUE, (unsigned char *)&server_side->maxseg_vaule, sizeof(server_side->maxseg_vaule));
    tfe_cmsg_set(cmsg, TFE_CMSG_UPSTREAM_TCP_NODELAY, (unsigned char *)&server_side->nodelay, sizeof(server_side->nodelay));
    tfe_cmsg_set(cmsg, TFE_CMSG_UPSTREAM_TCP_TTL, (unsigned char *)&server_side->ttl, sizeof(server_side->ttl));
    tfe_cmsg_set(cmsg, TFE_CMSG_UPSTREAM_TCP_KEEPALIVE, (unsigned char *)&server_side->keepalive, sizeof(server_side->keepalive));
    tfe_cmsg_set(cmsg, TFE_CMSG_UPSTREAM_TCP_KEEPCNT, (unsigned char *)&server_side->keepcnt, sizeof(server_side->keepcnt));
    tfe_cmsg_set(cmsg, TFE_CMSG_UPSTREAM_TCP_KEEPIDLE, (unsigned char *)&server_side->keepidle, sizeof(server_side->keepidle));
    tfe_cmsg_set(cmsg, TFE_CMSG_UPSTREAM_TCP_KEEPINTVL, (unsigned char *)&server_side->keepintvl, sizeof(server_side->keepintvl));
    tfe_cmsg_set(cmsg, TFE_CMSG_UPSTREAM_TCP_USER_TIMEOUT, (unsigned char *)&server_side->user_timeout, sizeof(server_side->user_timeout));

    TFE_LOG_INFO(tcp_enforcer->logger, "hit tcp_option_profile %d tcp_passthrough %d "
                                       "client_side={maxseg_enable:%d, maxseg_vaule:%d, nodelay:%d, ttl:%d, keepalive:%d, keepcnt:%d, keepidle:%d, keepintvl:%d, user_timeout:%d} "
                                       "server_side={maxseg_enable:%d, maxseg_vaule:%d, nodelay:%d, ttl:%d, keepalive:%d, keepcnt:%d, keepidle:%d, keepintvl:%d, user_timeout:%d} ",
                 profile_id, param->tcp_passthrough,
                 client_side->maxseg_enable, client_side->maxseg_vaule, client_side->nodelay, client_side->ttl, client_side->keepalive, client_side->keepcnt, client_side->keepidle, client_side->keepintvl, client_side->user_timeout,
                 server_side->maxseg_enable, server_side->maxseg_vaule, server_side->nodelay, server_side->ttl, server_side->keepalive, server_side->keepcnt, server_side->keepidle, server_side->keepintvl, server_side->user_timeout);
    profile_param_free(param);

    return 0;
}