#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <syslog.h>

#include "log_remote.h"
#include "log_common.h"
#include "ulog_in.h"
#include "rpc_conn.h"

#define LOG_CONF_REMOTE_FILE_NAME      "log-remote.conf"

#define RESET_SEEK(fp)                 fseek(fp, 0, SEEK_SET)

#define LOG_CONF_KEY_REMOTE_LEVEL       "remote.level"

typedef enum {
    LOG_REMOTE_OP_ADD = 0,
    LOG_REMOTE_OP_DEL,
    LOG_REMOTE_MAX
} log_op_t;

typedef int (*op_func)(const log_remote_host_t *conf);

static int add_remote_host(const log_remote_host_t *conf);
static int del_remote_host(const log_remote_host_t *conf);

static op_func remote_funcs[] = {
    add_remote_host,
    del_remote_host
};

static int match_remote_config(const char *line, const void *src)
{
    char text_level[MAX_LINE_SZ + 1], old_redirect[MAX_LINE_SZ + 1];
    int n;

    ULOG_DEBUG(g_log, "The line:%s will be matched", line); 
    n = sscanf(line, "%1024s"REDIRECT_SEPERATE"%1024s", text_level, old_redirect);
    if (n == 2) {
        // 匹配到
        // 是否相同配置判读
        ULOG_DEBUG(g_log, "%s will compare with %s", old_redirect, (const char *)src);
        if (strcmp(old_redirect, src) == 0) {
            return 0;
        }                
    } else if (n == EOF) {
        // 错误发生
        ULOG_ERR(g_log, "Parsing remote line is failure:%s", strerror(errno));
        return -1;
    } else {
        // 未能识别行
        ULOG_DEBUG(g_log, "The line:%s can't be parsed", line);         
    }
    
    return 1;
}

static int remote_conf_content(FILE *fp, const u8 level, const char *filter_mod,
        int (*match_cb)(const char *line, const void *src), 
        int (*final_cb)(FILE *fp, const u8 level, const char *filter_mod, void *arg),
        void *arg)
{
    int ret = -1;
    FILE *bak_fp = NULL;
    char path[MAX_PATH_SZ];
    char *line = NULL;
    snprintf(path, sizeof(path), BAK_FILE, LOG_CONF_REMOTE_FILE_NAME);
	bak_fp = fopen(path, "r");
    if (bak_fp == NULL) {
        if (errno == ENOENT) {
            goto FINAL_PHASE;
        } else {
            ULOG_ERR(g_log, "Opening remote backup file:%s is failure:%s", path, strerror(errno));
            return -1;
        }
    }

    
    ssize_t n, n1;
    while ((n = getline(&line, &n, bak_fp)) != -1) {
        int match = match_cb(line, arg);
        if (match == -1) {
            ULOG_ERR(g_log, "Configuration which is matched is failure");
        } else if (match == 0) {
            ULOG_DEBUG(g_log, "Be matched");
            continue;
        } else {
            ULOG_DEBUG(g_log, "Not be matched");
        }

        ULOG_DEBUG(g_log, "Recover old line:%s to file:%s", line, path);
        n1 = fwrite(line, 1, n, fp);
        if (n != n1) {
            ULOG_ERR(g_log, "Recovering old line:%s to file is failure:%s", line, strerror(errno));
            goto END;
        }
    }
FINAL_PHASE:
    if (final_cb != NULL) {
        if (final_cb(fp, level, filter_mod, arg) != 0) {           
            ULOG_ERR(g_log, "Executing final callback is failure");
            goto END;
        }
    }
    
    ret = 0;
END:
    if (line != NULL) {
        free(line);
    }
    if (bak_fp != NULL) {
        fclose(bak_fp);
    }

    return ret;
}

static int remote_conf_level_content(FILE *fp, const u8 level)
{
    int ret = -1;
    FILE *bak_fp = NULL;
    char path[MAX_PATH_SZ];
    if (snprintf(path, sizeof(path), BAK_FILE, LOG_CONF_REMOTE_FILE_NAME) < 0) {        
        ULOG_DEBUG(g_log, "[remote-level]:Buildbackup path(%s) is failure", BAK_FILE, LOG_CONF_REMOTE_FILE_NAME);
        return -1;
    }
	bak_fp = fopen(path, "r");
    if (bak_fp == NULL) {        
        ULOG_ERR(g_log, "Opening remote backup file:%s is failure:%s", path, strerror(errno));
        return -1;       
    }

    char *line = NULL;
    ssize_t n, n1;
    ssize_t  n3;
    char text_level[MAX_LINE_SZ], old_redirect[MAX_LINE_SZ];
    char rewrite_line[MAX_LINE_SZ];
    while ((n1 = getline(&line, &n, bak_fp)) != -1) {
        n3 = sscanf(line, "%1024s"REDIRECT_SEPERATE"%1024s", text_level, old_redirect);      
        if (n3 == 2) {
            // 匹配到
            // 改变该行日志级别
            memset(rewrite_line, 0, sizeof(rewrite_line));
            if (log_level_to_str(level, rewrite_line, sizeof(rewrite_line)) != 0) {
                ULOG_ERR(g_log, "Log level converts str is failure");
                goto END;
            }
            strcat(rewrite_line, REDIRECT_SEPERATE);
            strcat(rewrite_line, old_redirect);  
            strcat(rewrite_line, "\n");
            n3 = strlen(rewrite_line);
        } else if (n == EOF) {
            // 错误发生
            ULOG_ERR(g_log, "Parsing remote line is failure:%s", strerror(errno));
            goto END;
        } else {
            // 未能识别行
            ULOG_DEBUG(g_log, "The line:%s can't be parsed", line); 
        }

        ULOG_DEBUG(g_log, "Recover old line:%s to file:%s", rewrite_line, path);
        n1 = fwrite(rewrite_line, 1, n3, fp);
        if (n3 != n1) {
            ULOG_ERR(g_log, "Recovering old line:%s to file is failure:%s", rewrite_line, strerror(errno));
            goto END;
        }
    }
    
    ret = 0;
END:
    if (line != NULL) {
        free(line);
    }
    if (bak_fp != NULL) {
        fclose(bak_fp);
    }
    return ret;
}

static int add_remote_conf_content(FILE *fp, const u8 level, const char *filter_mod, void *arg)
{
    return remote_conf_content(fp, level, filter_mod, match_remote_config, write_conf_content, arg);
}

static int del_remote_conf_content(FILE *fp, const u8 level, const char *filter_mod, void *arg)
{
    return remote_conf_content(fp, level, filter_mod, match_remote_config, NULL, arg);
}

static int modify_remote_conf_log_level(FILE *fp, const u8 level, const char *filter_mod, void *arg)
{
    return remote_conf_level_content(fp, level);
}

static int get_log_level()
{
    char value[MAX_LINE_SZ];
    if (get_log_file_conf(LOG_CONF_KEY_REMOTE_LEVEL, value, sizeof(value)) == 0) {
        return atoi(value);
    }

    return LOG_INFO;
}

static int add_remote_host(const log_remote_host_t *conf) 
{      
    ULOG_INFO(g_log, "Adding remote log server[%s:%u], rfc is %s", conf->host, conf->port, rfc_tbl[conf->rfc].fmt);
    char prefix[2] = {0};
    if (conf->rfc == LOG_RFC_5424) {
        strcat(prefix, "@");
    };
    
    char redirect[MAX_LINE_SZ];
    if (snprintf(redirect, sizeof(redirect), "%s@%s:%u;%s", 
                    prefix, conf->host, conf->port, rfc_tbl[conf->rfc].fmt) < 0) {
        ULOG_ERR(g_log, "Setting remote redirect[%s:%u;%s] is faulure", conf->host, conf->port, rfc_tbl[conf->rfc].fmt);
        return -1;
    }
                    
    int level = get_log_level();
    if (level == -1) {        
        ULOG_ERR(g_log, "Getting log level is failure");
        return -1;
    }
    
    if (log_conf(level, LOG_CONF_PATH, LOG_CONF_REMOTE_FILE_NAME, NULL,
                    add_remote_conf_content, (void *)redirect) != 0) {        
        ULOG_ERR(g_log, "Adding remote server[%s:%u;%s] is faulure", conf->host, conf->port, rfc_tbl[conf->rfc].fmt);
        return -1;
    }

    return 0;
}

static int del_remote_host(const log_remote_host_t *conf)
{
    int ret = -1;
    char prefix[1] = "";
    
    ULOG_INFO(g_log, "Deleting remote log server[%s:%u], rfc is %s", conf->host, conf->port, rfc_tbl[conf->rfc].fmt);
    if (conf->rfc == LOG_RFC_5424) {
        strcat(prefix, "@");
    };
    char redirect[MAX_LINE_SZ];
    if (snprintf(redirect, sizeof(redirect), "%s@%s:%u;%s", 
                    prefix, conf->host, conf->port, rfc_tbl[conf->rfc].fmt) < 0) {
        ULOG_ERR(g_log, "Setting remote redirect[%s:%u;%s] is faulure", conf->host, conf->port, rfc_tbl[conf->rfc].fmt);
        return ret;
    }

    int level = get_log_level();
    if (level == -1) {        
        ULOG_ERR(g_log, "Getting log level is failure");
        return ret;
    }
    ret = log_conf(level, LOG_CONF_PATH, LOG_CONF_REMOTE_FILE_NAME, NULL,
                    del_remote_conf_content, (void *)redirect);
    if (ret != 0) {        
        ULOG_ERR(g_log, "Adding remote server[%s:%u;%s] is faulure", conf->host, conf->port, rfc_tbl[conf->rfc].fmt);
        return ret;
    }
    
    return ret;
}

static int config_log_remote_host(const log_op_t          op, const log_remote_host_t *conf)
{   
    if ((sizeof(rfc_tbl) / sizeof(rfc_key_fmt)) < conf->rfc) {       
        ULOG_WARNING(g_log, "Unknown rfc format key:%u", conf->rfc);
        return -1;
    }
    if (op >= LOG_REMOTE_MAX) {
        ULOG_WARNING(g_log, "Unknown operation type:%u", op);
        return -1;
    }
    return remote_funcs[op](conf);
}

static int config_log_remote_level(const log_remote_level_t *level)
{
    char str_level[4] = {0};
    if (snprintf(str_level, sizeof(str_level), "%u", level->level) < 0) {        
        ULOG_ERR(g_log, "Building level value:%u to string is failure", level->level);
        return -1;
    }
    if (write_log_file_conf(LOG_CONF_KEY_REMOTE_LEVEL, str_level) != 0) {
        ULOG_ERR(g_log, "Writeing key-value[%s-%s] to config file is failure", LOG_CONF_KEY_REMOTE_LEVEL, str_level);
        return -1;
    }

    //判断是否有rsyslog的remote配置,如果有,才写入
    char path[MAX_PATH_SZ];
    if (snprintf(path, sizeof(path), BAK_FILE, LOG_CONF_REMOTE_FILE_NAME) < 0) {        
        ULOG_ERR(g_log, "Build backup path(%s) is failure", BAK_FILE, LOG_CONF_REMOTE_FILE_NAME);
        return -1;
    }
    if(access(path, F_OK) != 0)  {   
        ULOG_ERR(g_log, "Backup file:%s is not exist:%s", path, strerror(errno));
        return 1;
    } else {      
        if (log_conf(level->level, LOG_CONF_PATH, LOG_CONF_REMOTE_FILE_NAME, NULL,
                        modify_remote_conf_log_level, NULL) != 0) {        
            ULOG_ERR(g_log, "Modifing log level:%u of remote server is faulure", level->level);
            return -1;
        }
    }

    return 0;
}

static void rpc_conf_log_remote(const log_op_t         op, rpc_conn *conn, pointer input, int input_len,	pointer data)
{
    char str_err[1024];
    
    if (input == NULL) {
        strncpy(str_err, "Remote log configure can't be null", sizeof(str_err));
        ULOG_WARNING(g_log, str_err);
        goto FAIL;
    }
    
    u32 need_len = sizeof(log_remote_host_t);
    if (input_len < need_len) {
        ULOG_WARNING(g_log, 
               "The input paramter of rpc log remote host is needed length of %u, but the actual length is %u",
               need_len, input_len);
        return;
    }

    if (config_log_remote_host(op, (const log_remote_host_t *)input) != 0) {
        ULOG_ERR(g_log, "Configuring remote of log is faiure");
        rpc_return_error(conn, RET_ERR, "Configuring remote of log is faiure");
        return;
    }

    rpc_return_null(conn);
FAIL:
    rpc_return_error(conn, RET_ERR, str_err);
}

static int __rpc_conf_log_remote(pointer input, const void *arg, char *str_err, int str_len)
{
    log_op_t op;
    memcpy(&op, arg, sizeof(op));

    int ret = config_log_remote_host(op, (const log_remote_host_t *)input);
    if (ret < 0) {
        strncpy(str_err, "Configuring remote of log is faiure", str_len);
    }
    return ret;
}

void rpc_conf_log_add_remote(rpc_conn *conn, pointer input, int input_len,	pointer data)
{
    //rpc_conf_log_remote(LOG_REMOTE_OP_ADD, conn, input, input_len, data);

    log_op_t op = LOG_REMOTE_OP_ADD;
    rpc_conf_proc(conn, input, input_len, sizeof(log_remote_host_t), __rpc_conf_log_remote, (void *)&op);
}

void rpc_conf_log_del_remote(rpc_conn *conn, pointer input, int input_len,	pointer data)
{
    //rpc_conf_log_remote(LOG_REMOTE_OP_DEL, conn, input, input_len, data);

    log_op_t op = LOG_REMOTE_OP_DEL;
    rpc_conf_proc(conn, input, input_len, sizeof(log_remote_host_t), __rpc_conf_log_remote, (void *)&op);
}

static int __rpc_conf_log_remote_level(pointer input, const void *arg, char *str_err, int str_len)
{
    int ret = config_log_remote_level((const log_remote_level_t *)input);
    if (ret < 0) {
        strncpy(str_err, "Configuring remote level of log is faiure", str_len);
    }
    return ret;
}

void rpc_conf_log_remote_level(rpc_conn *conn, pointer input, int input_len,	pointer data)
{
    rpc_conf_proc(conn, input, input_len, sizeof(log_remote_level_t), __rpc_conf_log_remote_level, NULL);
}