/*
**********************************************************************************************
*	File: maat_stat.c
*   Description:
*	Authors: Liu WenTan <liuwentan@geedgenetworks.com>
*	Date:    2022-10-31
*   Copyright: (c) Since 2022 Geedge Networks, Ltd. All rights reserved.
***********************************************************************************************
*/

#include <assert.h>

#include "maat_utils.h"
#include "maat_stat.h"
#include "fieldstat.h"
#include "alignment.h"
#include "maat_ip.h"
#include "maat_compile.h"
#include "maat_group.h"
#include "maat_plugin.h"
#include "maat_expr.h"

#define MODULE_MAAT_STAT	 module_name_str("maat.stat")

enum MAAT_FS_STATUS {
	STATUS_VERSION = 0,
	STATUS_THRED_NUM,
	STATUS_TABLE_NUM,
	STATUS_PLUGIN_CACHE_NUM,
	STATUS_PLUGIN_ACC_NUM,
	STATUS_GROUP_REF_NUM,
    STATUS_GROUP_REF_NOT_NUM,
	STATUS_COMPILE_RULE_NUM,
	STATUS_MAAT_STATE_NUM,
	STATUS_GARBAGE_QSIZE,
	STATUS_TOTAL_SCAN_LEN,
	STATUS_TOTAL_SCAN_CNT,
	STATUS_UPDATE_ERR_CNT,
	STATUS_ICONV_ERR_CNT,
	STATUS_SCAN_ERR_CNT,
	STATUS_ZOMBIE_RS_STREAM,
    STATUS_NOT_GROUP_HIT,
	STATUS_CMD_NUM,
	STATUS_CMD_Q_SIZE,
	STATUS_CMD_LINE_NUM
};

enum MAAT_FS_COLUMN {
	COLUMN_TABLE_RULE_NUM = 0,
	COLUMN_TABLE_REGEX_NUM,
    COLUMN_TABLE_IPV6_NUM,
	COLUMN_TABLE_STREAM_NUM,
	COLUMN_TABLE_SCAN_CNT,
	COLUMN_TABLE_SCAN_BYTES,
	COLUMN_TABLE_CPU_TIME,//microseconds
	COLUMN_TABLE_HIT_CNT,
};

const char *common_column_name[] = {"rule", "reg", "v6", "stream", "scan_cnt", "scan_bytes", "scan_cpu_time", "hit_cnt"};
enum field_type common_column_type[] = {
    FIELD_TYPE_GAUGE, 
    FIELD_TYPE_GAUGE, 
    FIELD_TYPE_GAUGE, 
    FIELD_TYPE_GAUGE, 
    FIELD_TYPE_GAUGE, 
    FIELD_TYPE_GAUGE, 
    FIELD_TYPE_GAUGE, 
    FIELD_TYPE_GAUGE
};

#define	MAX_CONJ_NAME_LEN	22
void maat_fieldstat_register(struct maat_stat *stat)
{
    stat->fs_status_id[STATUS_VERSION] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE,
                                                            "version", NULL, 0);
    stat->fs_status_id[STATUS_THRED_NUM] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                              "threads", NULL, 0);
    stat->fs_status_id[STATUS_TABLE_NUM] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                              "tables", NULL, 0);
    stat->fs_status_id[STATUS_PLUGIN_CACHE_NUM] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                                     "plug_cached", NULL, 0);
    stat->fs_status_id[STATUS_PLUGIN_ACC_NUM] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                                   "plug_acc", NULL, 0);
    stat->fs_status_id[STATUS_GROUP_REF_NUM] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                                  "group", NULL, 0);
    stat->fs_status_id[STATUS_GROUP_REF_NOT_NUM] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                                      "not_grp", NULL, 0);
    stat->fs_status_id[STATUS_COMPILE_RULE_NUM] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                                     "compile", NULL, 0);
    stat->fs_status_id[STATUS_GARBAGE_QSIZE] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                                  "garbage_num", NULL, 0);
    stat->fs_status_id[STATUS_MAAT_STATE_NUM] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                                  "maat_state", NULL, 0);
    stat->fs_status_id[STATUS_ZOMBIE_RS_STREAM] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                                     "z_stream", NULL, 0);
    stat->fs_status_id[STATUS_NOT_GROUP_HIT] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                                  "nt_grp_hit", NULL, 0);
    stat->fs_status_id[STATUS_TOTAL_SCAN_LEN] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                                   "scan_bytes", NULL, 0);
    stat->fs_status_id[STATUS_TOTAL_SCAN_CNT] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                                  "scan_times", NULL, 0);
    stat->fs_status_id[STATUS_UPDATE_ERR_CNT] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                                   "update_err", NULL, 0);
    stat->fs_status_id[STATUS_SCAN_ERR_CNT] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                                 "scan_error", NULL, 0);
    stat->fs_status_id[STATUS_CMD_NUM] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                            "cmd_commit", NULL, 0);
    stat->fs_status_id[STATUS_CMD_Q_SIZE] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                               "cmd_in_q", NULL, 0);
    stat->fs_status_id[STATUS_CMD_LINE_NUM] = fieldstat_register(stat->fs_handle, FIELD_TYPE_GAUGE, 
                                                                 "line_cmd/s", NULL, 0);
}

int maat_fieldstat_table_row_register(struct maat_stat *stat, struct table_manager *tbl_mgr,
                                       int table_id)
{
    size_t max_table_cnt = table_manager_table_size(tbl_mgr);
    int ret = fieldstat_register_table_row(stat->fs_handle, table_id, "Sum", NULL, 0,
                                           stat->total_stat_id);
    if (ret < 0) {
        log_error(stat->logger, MODULE_MAAT_STAT, "fieldstat_register_table_row Sum failed.");
        return -1;
    }

    for (size_t i = 0; i < max_table_cnt; i++) {
        void *schema = table_manager_get_schema(tbl_mgr, i);
        if (NULL == schema) {
            continue;
        }

        const char *table_name = table_manager_get_table_name(tbl_mgr, i);
        assert(table_name != NULL);

        ret = fieldstat_register_table_row(stat->fs_handle, table_id, table_name, NULL, 0,
                                           stat->fs_column_id[i]);
        if (ret < 0) {
            log_error(stat->logger, MODULE_MAAT_STAT,
                      "fieldstat_register_table_row %s failed.", table_name);
            return -1;
        }
    }

    return 0; 
}

struct maat_stat *maat_stat_new(const char *stat_file, size_t max_thread_num, struct log_handle *logger)
{
    struct maat_stat *stat = ALLOC(struct maat_stat, 1);

    size_t str_len = MIN(sizeof(stat->stat_file), strlen(stat_file));
    memcpy(stat->stat_file, stat_file, str_len);

    stat->nr_worker_thread = max_thread_num;
    stat->logger = logger;
    stat->thread_call_cnt = alignment_int64_array_alloc(max_thread_num);
    stat->hit_cnt = alignment_int64_array_alloc(max_thread_num);
    stat->not_grp_hit_cnt = alignment_int64_array_alloc(max_thread_num);

    return stat;
}

void maat_stat_free(struct maat_stat *stat)
{
    if (NULL == stat) {
        return;
    }

    if (stat->thread_call_cnt != NULL) {
        alignment_int64_array_free(stat->thread_call_cnt);
        stat->thread_call_cnt = NULL;
    }

    if (stat->hit_cnt != NULL) {
        alignment_int64_array_free(stat->hit_cnt);
        stat->hit_cnt = NULL;
    }

    if (stat->not_grp_hit_cnt != NULL) {
        alignment_int64_array_free(stat->not_grp_hit_cnt);
        stat->not_grp_hit_cnt = NULL;
    }

    if (stat->fs_handle != NULL) {
        fieldstat_instance_free(stat->fs_handle);
        stat->fs_handle = NULL;
    }

    free(stat);
}

int maat_stat_init(struct maat_stat *stat, struct table_manager *tbl_mgr, 
                    struct maat_garbage_bin *garbage_bin)
{
    if (NULL == stat || NULL == tbl_mgr || NULL == garbage_bin) {
        return -1;
    }

    const char *instance_name = "maat_stat";
	stat->fs_handle = fieldstat_instance_new(instance_name);
    if (NULL == stat->fs_handle) {
        log_error(stat->logger, MODULE_MAAT_STAT, "fieldstat_instance_new failed.");
        return -1;
    }

    int ret = fieldstat_set_local_output(stat->fs_handle, stat->stat_file, "default");
    if (ret < 0) {
        log_error(stat->logger, MODULE_MAAT_STAT, "fieldstat_set_local_output failed.");
        return -1;
    }

    ret = fieldstat_disable_background_thread(stat->fs_handle);
    if (ret < 0) {
        log_error(stat->logger, MODULE_MAAT_STAT, "fieldstat_disable_background_thread failed.");
        return -1;
    }

    maat_fieldstat_register(stat);

    stat->ref_tbl_mgr = tbl_mgr;
    stat->ref_garbage_bin = garbage_bin;
    size_t n_column = sizeof(common_column_name) / sizeof(common_column_name[0]);
    int fs_table_id = fieldstat_register_table(stat->fs_handle, instance_name, 
                                               common_column_name, common_column_type, 
                                               n_column);
    if (fs_table_id < 0) {
        log_error(stat->logger, MODULE_MAAT_STAT, "fieldstat_register_table failed.");
        return -1;
    }

    ret = maat_fieldstat_table_row_register(stat, tbl_mgr, fs_table_id);
    if (ret < 0) {
        return -1;
    }

    fieldstat_instance_start(stat->fs_handle);

    return 0;
}

void maat_fieldstat_table_row_output(struct maat_stat *stat, int perf_on)
{
    long long plugin_cache_num = 0, plugin_rule_num = 0, not_group_num = 0;
    long long compile_rule_num = 0, g2c_rule_num = 0, g2g_rule_num = 0;
    long long total_rule_num = 0, total_stream_num = 0, total_input_bytes = 0;
    long long total_scan_cnt = 0, total_scan_cpu_time = 0, total_regex_num = 0;
    long long total_ipv6_num = 0, total_hit_cnt = 0, total_update_err = 0;
    size_t max_table_count = table_manager_table_size(stat->ref_tbl_mgr);

    for (size_t i = 0; i < max_table_count; i++) {
        long long regex_rule_num = 0;
        long long ipv6_rule_num = 0;
        void *schema = table_manager_get_schema(stat->ref_tbl_mgr, i);
        if (NULL == schema) {
            continue;
        }

        enum table_type table_type = table_manager_get_table_type(stat->ref_tbl_mgr, i);
        assert(table_type != TABLE_TYPE_INVALID);

        void *runtime = table_manager_get_runtime(stat->ref_tbl_mgr, i);

        switch (table_type) {
            case TABLE_TYPE_PLUGIN:
                plugin_cache_num += plugin_runtime_cached_row_count(runtime);
                plugin_rule_num += plugin_runtime_rule_count(runtime);
                break;
            case TABLE_TYPE_COMPILE:
                compile_rule_num += compile_runtime_rule_count(runtime);
                break;
            case TABLE_TYPE_GROUP2COMPILE:
                g2c_rule_num += group2compile_runtime_rule_count(runtime);
                not_group_num += group2compile_runtime_not_group_count(runtime);
                break;
            case TABLE_TYPE_GROUP2GROUP:
                g2g_rule_num += group2group_runtime_rule_count(runtime);
                //TODO exclude group num
                break;
            case TABLE_TYPE_EXPR:
            case TABLE_TYPE_EXPR_PLUS:
                //regex rule cnt
                regex_rule_num = expr_runtime_regex_rule_count(runtime);
                break;
            case TABLE_TYPE_IP_PLUS:
                ipv6_rule_num = ip_runtime_ipv6_rule_count(runtime);
                break;
            default:
                break;
        }

        if (table_type == TABLE_TYPE_PLUGIN || table_type == TABLE_TYPE_COMPILE ||
            table_type == TABLE_TYPE_GROUP2COMPILE || table_type == TABLE_TYPE_GROUP2GROUP) {
            continue;
        }

        total_regex_num += regex_rule_num;
        total_ipv6_num += ipv6_rule_num;

        long long rule_num = table_manager_runtime_rule_count(stat->ref_tbl_mgr, i);
        fieldstat_value_set(stat->fs_handle, stat->fs_column_id[i][COLUMN_TABLE_RULE_NUM], rule_num);
        total_rule_num += rule_num;

        if (table_type == TABLE_TYPE_EXPR || table_type == TABLE_TYPE_EXPR_PLUS) {
            fieldstat_value_set(stat->fs_handle, stat->fs_column_id[i][COLUMN_TABLE_REGEX_NUM], regex_rule_num);
            long long stream_num = expr_runtime_stream_num(runtime);
            fieldstat_value_set(stat->fs_handle, stat->fs_column_id[i][COLUMN_TABLE_STREAM_NUM], stream_num);
            total_stream_num += stream_num;

            long long input_bytes = expr_runtime_scan_bytes(runtime);
            fieldstat_value_set(stat->fs_handle, stat->fs_column_id[i][COLUMN_TABLE_SCAN_BYTES], input_bytes);
            total_input_bytes += input_bytes;
        }

        if (table_type == TABLE_TYPE_IP_PLUS) {
            fieldstat_value_set(stat->fs_handle, stat->fs_column_id[i][COLUMN_TABLE_IPV6_NUM], ipv6_rule_num);
        }

        if (1 == perf_on) {
            long long scan_cpu_time = table_manager_runtime_scan_cpu_time(stat->ref_tbl_mgr, i);
            fieldstat_value_set(stat->fs_handle, stat->fs_column_id[i][COLUMN_TABLE_CPU_TIME], scan_cpu_time);
            total_scan_cpu_time += scan_cpu_time;
        }

        long long scan_cnt = table_manager_runtime_scan_count(stat->ref_tbl_mgr, i);
        fieldstat_value_set(stat->fs_handle, stat->fs_column_id[i][COLUMN_TABLE_SCAN_CNT], scan_cnt);
        total_scan_cnt += scan_cnt;

        long long hit_cnt = table_manager_runtime_hit_count(stat->ref_tbl_mgr, i);
        fieldstat_value_set(stat->fs_handle, stat->fs_column_id[i][COLUMN_TABLE_HIT_CNT], hit_cnt);
        total_hit_cnt += hit_cnt;

        total_update_err += table_manager_runtime_update_err_count(stat->ref_tbl_mgr, i);
    }

    fieldstat_value_set(stat->fs_handle, stat->total_stat_id[COLUMN_TABLE_RULE_NUM], total_rule_num);
    fieldstat_value_set(stat->fs_handle, stat->total_stat_id[COLUMN_TABLE_REGEX_NUM], total_regex_num);
    fieldstat_value_set(stat->fs_handle, stat->total_stat_id[COLUMN_TABLE_IPV6_NUM], total_ipv6_num);
    fieldstat_value_set(stat->fs_handle, stat->total_stat_id[COLUMN_TABLE_STREAM_NUM], total_stream_num);
    fieldstat_value_set(stat->fs_handle, stat->total_stat_id[COLUMN_TABLE_SCAN_CNT], total_scan_cnt);
    fieldstat_value_set(stat->fs_handle, stat->total_stat_id[COLUMN_TABLE_SCAN_BYTES], total_input_bytes);
    fieldstat_value_set(stat->fs_handle, stat->total_stat_id[COLUMN_TABLE_HIT_CNT], total_hit_cnt);
    if (1 == perf_on) {
        fieldstat_value_set(stat->fs_handle, stat->total_stat_id[COLUMN_TABLE_CPU_TIME], total_scan_cpu_time);
    }

    stat->scan_cnt += total_scan_cnt;
    stat->scan_bytes += total_input_bytes;
    stat->update_err_cnt += total_update_err;

    fieldstat_value_set(stat->fs_handle, stat->fs_status_id[STATUS_TOTAL_SCAN_LEN], stat->scan_bytes);
    fieldstat_value_set(stat->fs_handle, stat->fs_status_id[STATUS_TOTAL_SCAN_CNT], stat->scan_cnt);
    fieldstat_value_set(stat->fs_handle, stat->fs_status_id[STATUS_UPDATE_ERR_CNT], stat->update_err_cnt);
    fieldstat_value_set(stat->fs_handle, stat->fs_status_id[STATUS_SCAN_ERR_CNT], stat->scan_err_cnt);
    fieldstat_value_set(stat->fs_handle, stat->fs_status_id[STATUS_ZOMBIE_RS_STREAM], stat->zombie_rs_stream);
    fieldstat_value_set(stat->fs_handle, stat->fs_status_id[STATUS_PLUGIN_CACHE_NUM], plugin_cache_num);
    fieldstat_value_set(stat->fs_handle, stat->fs_status_id[STATUS_PLUGIN_ACC_NUM], plugin_rule_num);
    fieldstat_value_set(stat->fs_handle, stat->fs_status_id[STATUS_GROUP_REF_NUM], g2c_rule_num);
    fieldstat_value_set(stat->fs_handle, stat->fs_status_id[STATUS_GROUP_REF_NOT_NUM], not_group_num);
    fieldstat_value_set(stat->fs_handle, stat->fs_status_id[STATUS_COMPILE_RULE_NUM], compile_rule_num);
}

void maat_stat_output(struct maat_stat *stat, long long maat_version, int perf_on)
{
    if (NULL == stat) {
        return;
    }

    long long active_thread_num = alignment_int64_array_cnt(stat->thread_call_cnt, 
                                                            stat->nr_worker_thread);
    //TODO: maat_state_cnt
    long long not_grp_hit_cnt = alignment_int64_array_sum(stat->not_grp_hit_cnt,
                                                          stat->nr_worker_thread);
    size_t table_cnt = table_manager_table_count(stat->ref_tbl_mgr);
    size_t garbage_q_len = maat_garbage_bin_get_size(stat->ref_garbage_bin);

    fieldstat_value_set(stat->fs_handle, stat->fs_status_id[STATUS_VERSION], maat_version);
    fieldstat_value_set(stat->fs_handle, stat->fs_status_id[STATUS_THRED_NUM], active_thread_num);
    fieldstat_value_set(stat->fs_handle, stat->fs_status_id[STATUS_TABLE_NUM], table_cnt);
    fieldstat_value_set(stat->fs_handle, stat->fs_status_id[STATUS_NOT_GROUP_HIT], not_grp_hit_cnt);
    fieldstat_value_set(stat->fs_handle, stat->fs_status_id[STATUS_CMD_Q_SIZE], stat->cmd_q_cnt);
    fieldstat_value_set(stat->fs_handle, stat->fs_status_id[STATUS_CMD_LINE_NUM], stat->line_cmd_acc_num);
    fieldstat_value_set(stat->fs_handle, stat->fs_status_id[STATUS_GARBAGE_QSIZE], garbage_q_len);

    maat_fieldstat_table_row_output(stat, perf_on);

	fieldstat_passive_output(stat->fs_handle);
}