#include <ctype.h>
#include <getopt.h>

#include "config.h"
#include "mediator.h"

char *config_file_path;
unsigned int config_interval = MEDIATOR_DEFAULT_INTERVAL;
uint8_t config_dumpcdr = MEDIATOR_DEFAULT_DUMPCDR;
uint8_t config_daemonize = MEDIATOR_DEFAULT_DAEMONIZE;
char *config_pid_path;

char *config_med_host;
char *config_med_user;
char *config_med_pass;
char *config_med_db;
unsigned int config_med_port = MEDIATOR_DEFAULT_ACCPORT;

char *config_redis_host;
unsigned int config_redis_port = MEDIATOR_DEFAULT_REDISPORT;
unsigned int config_redis_db = MEDIATOR_DEFAULT_REDISDB;
char *config_redis_pass = NULL;

char *config_cdr_host;
char *config_cdr_user;
char *config_cdr_pass;
char *config_cdr_db;
unsigned int config_cdr_port = MEDIATOR_DEFAULT_CDRPORT;
char *config_intermediate_cdr_host;
unsigned int config_intermediate_cdr_port = MEDIATOR_DEFAULT_CDRPORT;

char *config_prov_host;
char *config_prov_user;
char *config_prov_pass;
char *config_prov_db;
unsigned int config_prov_port = MEDIATOR_DEFAULT_PROVPORT;

char *config_stats_host;
char *config_stats_user;
char *config_stats_pass;
char *config_stats_db;
unsigned int config_stats_port = MEDIATOR_DEFAULT_STATSPORT;
med_stats_period_t config_stats_period = MEDIATOR_DEFAULT_STATSPERIOD;

int config_maintenance = 0;
int strict_leg_tokens = 0;
int config_max_acc_age = 0;
int config_intermediate_interval = 0;

med_loglevel_t config_loglevel = MEDIATOR_DEFAULT_LOGLEVEL;

enum config_option {
    OPT_CONFIGFILE = 'c',
    OPT_DAEMONIZE = 'd',
    OPT_PIDFILE = 'D',
    OPT_SYSLOG = 'l',
    OPT_LOGLEVEL = 'L',
    OPT_INTERVAL = 'i',
    OPT_MED_HOST = 'h',
    OPT_MED_PORT = 'o',
    OPT_MED_USER = 'u',
    OPT_MED_PASS = 'p',
    OPT_MED_DB = 'b',
    OPT_CDR_HOST = 'H',
    OPT_CDR_PORT = 'O',
    OPT_CDR_USER = 'U',
    OPT_CDR_PASS = 'P',
    OPT_CDR_DB = 'B',
    OPT_INTERMEDIATE_CDR_HOST = 'y',
    OPT_INTERMEDIATE_CDR_PORT = 'Y',
    OPT_PROV_HOST = 'S',
    OPT_PROV_PORT = 'T',
    OPT_PROV_USER = 'R',
    OPT_PROV_PASS = 'A',
    OPT_PROV_DB = 'N',
    OPT_STATS_HOST = 'Z',
    OPT_STATS_PORT = 'z',
    OPT_STATS_USER = 'W',
    OPT_STATS_PASS = 'w',
    OPT_STATS_DB = 'X',
    OPT_REDIS_HOST = 'a',
    OPT_REDIS_PORT = 't',
    OPT_REDIS_DB = 'r',
    OPT_REDIS_PASS = 'e',
    OPT_STATS_PERIOD = 'x',
    OPT_MAINTENANCE = 'm',
    OPT_LEG_TOKENS = 's',
    OPT_MAX_ACC_AGE = 'M',
    OPT_INTERMEDIATE_INTERVAL = 'I',
};

static const char options[] = "?a:c:e:D:i:dlL:h:u:p:b:o:H:U:P:B:O:S:t:T:r:R:A:N:Z:z:W:w:X:x:msM:I:y:Y:";

struct option long_options[] = {
    { "configfile", required_argument, NULL, OPT_CONFIGFILE },
    { "pidfile", required_argument, NULL, OPT_PIDFILE },
    { "daemonize", no_argument, NULL, OPT_DAEMONIZE },
    { "logquery", no_argument, NULL, OPT_SYSLOG },
    { "loglevel",  required_argument, NULL, OPT_LOGLEVEL },
    { "interval", required_argument, NULL, OPT_INTERVAL },
    { "med-host", required_argument, NULL, OPT_MED_HOST },
    { "med-port", required_argument, NULL, OPT_MED_PORT },
    { "med-user", required_argument, NULL, OPT_MED_USER },
    { "med-pass", required_argument, NULL, OPT_MED_PASS },
    { "med-db", required_argument, NULL, OPT_MED_DB },
    { "cdr-host", required_argument, NULL, OPT_CDR_HOST },
    { "cdr-port", required_argument, NULL, OPT_CDR_PORT },
    { "cdr-user", required_argument, NULL, OPT_CDR_USER },
    { "cdr-pass", required_argument, NULL, OPT_CDR_PASS },
    { "cdr-db", required_argument, NULL, OPT_CDR_DB },
    { "intermediate-cdr-host", required_argument, NULL, OPT_INTERMEDIATE_CDR_HOST },
    { "intermediate-cdr-port", required_argument, NULL, OPT_INTERMEDIATE_CDR_PORT },
    { "prov-host", required_argument, NULL, OPT_PROV_HOST },
    { "prov-port", required_argument, NULL, OPT_PROV_PORT },
    { "prov-user", required_argument, NULL, OPT_PROV_USER },
    { "prov-pass", required_argument, NULL, OPT_PROV_PASS },
    { "prov-db", required_argument, NULL, OPT_PROV_DB },
    { "stats-host", required_argument, NULL, OPT_STATS_HOST },
    { "stats-port", required_argument, NULL, OPT_STATS_PORT },
    { "stats-user", required_argument, NULL, OPT_STATS_USER },
    { "stats-pass", required_argument, NULL, OPT_STATS_PASS },
    { "stats-db", required_argument, NULL, OPT_STATS_DB },
    { "redis-host", required_argument, NULL, OPT_REDIS_HOST },
    { "redis-port", required_argument, NULL, OPT_REDIS_PORT },
    { "redis-db", required_argument, NULL, OPT_REDIS_DB },
    { "redis-pass", required_argument, NULL, OPT_REDIS_PASS },
    { "stats-period", required_argument, NULL, OPT_STATS_PERIOD },
    { "maintenance", no_argument, NULL, OPT_MAINTENANCE },
    { "leg-tokens", no_argument, NULL, OPT_LEG_TOKENS },
    { "max-acc-age", required_argument, NULL, OPT_MAX_ACC_AGE },
    { "intermediate-interval", required_argument, NULL, OPT_INTERMEDIATE_INTERVAL },
    { NULL, 0, NULL, 0 },
};

static void config_help(const char *self, int rc)
{
    printf(
"mediator %s - Creates call detail records from OpenSER accounting.\n" \
"Usage: %s [<option>...]\n" \
"\n" \
"Options:\n" \
"  -c, --configfile FILE\tThe config file to use (default = '%s').\n" \
"  -D, --pidfile PIDFILE\tThe path of the PID file (default = '%s').\n" \
"  -d, --daemonize\tEnables daemonization of the process (default = disabled).\n" \
"  -l, --logquery\t\tEnables logging of CDR sql query to file (default = disabled).\n" \
"  -L, --loglevel\tThe loglevel from 7=debug to 0=emergency (default = %d).\n" \
"  -i, --interval INT\tThe creation interval (default = %d).\n" \
"  -h, --med-host HOST\tThe ACC db host (default = '%s').\n" \
"  -o, --med-port PORT\tThe ACC db port (default = '%d').\n" \
"  -u, --med-user USER\tThe ACC db user (default = '%s').\n" \
"  -p, --med-pass PASS\tThe ACC db pass (default = '%s').\n" \
"  -b, --med-db DB\tThe ACC db name (default = '%s').\n" \
"  -H, --cdr-host HOST\tThe CDR db host (default = '%s').\n" \
"  -O, --cdr-port PORT\tThe CDR db port (default = '%d').\n" \
"  -U, --cdr-user USER\tThe CDR db user (default = '%s').\n" \
"  -P, --cdr-pass PASS\tThe CDR db pass (default = '%s').\n" \
"  -B, --cdr-db DB\tThe CDR db name (default = '%s').\n" \
"  -y, --intermediate-cdr-host HOST\tThe CDR db host (default = '%s').\n" \
"  -Y, --intermediate-cdr-port PORT\tThe CDR db port (default = '%d').\n" \
"  -S, --prov-host HOST\tThe prov db host (default = '%s').\n" \
"  -T, --prov-port PORT\tThe prov db port (default = '%d').\n" \
"  -R, --prov-user USER\tThe prov db user (default = '%s').\n" \
"  -A, --prov-pass PASS\tThe prov db pass (default = '%s').\n" \
"  -N, --prov-db DB\tThe prov db name (default = '%s').\n" \
"  -Z, --stats-host HOST\tThe stats db host (default = '%s').\n" \
"  -z, --stats-port PORT\tThe stats db port (default = '%d').\n" \
"  -W, --stats-user USER\tThe stats db user (default = '%s').\n" \
"  -w, --stats-pass PASS\tThe stats db pass (default = '%s').\n" \
"  -X, --stats-db DB\tThe stats db name (default = '%s').\n" \
"  -x, --stats-period INT\tThe stats db period (default = '%d', 1=hour, 2=day, 3=month).\n" \
"  -a, --redis-host HOST\tThe redis db host (default = '%s').\n" \
"  -t, --redis-port PORT\tThe redis db port (default = '%d').\n" \
"  -e, --redis-pass PASS\tThe redis db pass (default = none).\n" \
"  -r, --redis-db DB\tThe redis usrloc db number (default = '%d').\n" \
"  -m, --maintenance\tMaintenance mode (do nothing, just sleep).\n" \
"  -s, --leg-tokens\tStrict acc fields (move to trash otherwise).\n" \
"  -M, --max-acc-age\tMaximum age of acc records before trashing them (default = disabled).\n" \
"  -I, --intermediate-interval\tHow often to write/update intermediate CDRs (default = disabled).\n" \
"  -?, --help\t\tDisplays this message.\n",
        MEDIATOR_VERSION, self, MEDIATOR_DEFAULT_CONFIG_FILE,
        MEDIATOR_DEFAULT_PIDPATH, MEDIATOR_DEFAULT_LOGLEVEL,
        MEDIATOR_DEFAULT_INTERVAL,
        MEDIATOR_DEFAULT_ACCHOST, MEDIATOR_DEFAULT_ACCPORT,
        MEDIATOR_DEFAULT_ACCUSER, MEDIATOR_DEFAULT_ACCPASS,
        MEDIATOR_DEFAULT_ACCDB,
        MEDIATOR_DEFAULT_CDRHOST, MEDIATOR_DEFAULT_CDRPORT,
        MEDIATOR_DEFAULT_CDRUSER, MEDIATOR_DEFAULT_CDRPASS,
        MEDIATOR_DEFAULT_CDRDB,
        MEDIATOR_DEFAULT_CDRHOST, MEDIATOR_DEFAULT_CDRPORT,
        MEDIATOR_DEFAULT_PROVHOST, MEDIATOR_DEFAULT_PROVPORT,
        MEDIATOR_DEFAULT_PROVUSER, MEDIATOR_DEFAULT_PROVPASS,
        MEDIATOR_DEFAULT_PROVDB,
        MEDIATOR_DEFAULT_STATSHOST, MEDIATOR_DEFAULT_STATSPORT,
        MEDIATOR_DEFAULT_STATSUSER, MEDIATOR_DEFAULT_STATSPASS,
        MEDIATOR_DEFAULT_STATSDB, MEDIATOR_DEFAULT_STATSPERIOD,
        MEDIATOR_DEFAULT_REDISHOST, MEDIATOR_DEFAULT_REDISPORT,
        MEDIATOR_DEFAULT_REDISDB);

    exit(rc);
}

static void config_set_string_option(char **str, const char *opt)
{
    free(*str);
    *str = strdup(opt);
}

static void config_set_string_default(char **str, const char *def)
{
    if (*str == NULL)
        *str = strdup(def);
}

static void config_set_option(enum config_option option, const char *value)
{
    switch (option) {
    case OPT_DAEMONIZE:
        config_daemonize = 1;
        break;
    case OPT_SYSLOG:
        config_dumpcdr = 1;
        break;
    case OPT_LOGLEVEL:
        config_loglevel = atoi(value);
        break;
    case OPT_CONFIGFILE:
        config_set_string_option(&config_file_path, value);
        break;
    case OPT_PIDFILE:
        config_set_string_option(&config_pid_path, value);
        break;
    case OPT_INTERVAL:
        config_interval = atoi(value);
        break;
    case OPT_MED_HOST:
        config_set_string_option(&config_med_host, value);
        break;
    case OPT_MED_USER:
        config_set_string_option(&config_med_user, value);
        break;
    case OPT_MED_PASS:
        config_set_string_option(&config_med_pass, value);
        break;
    case OPT_MED_DB:
        config_set_string_option(&config_med_db, value);
        break;
    case OPT_MED_PORT:
        config_med_port = atoi(value);
        break;
    case OPT_CDR_HOST:
        config_set_string_option(&config_cdr_host, value);
        break;
    case OPT_CDR_USER:
        config_set_string_option(&config_cdr_user, value);
        break;
    case OPT_CDR_PASS:
        config_set_string_option(&config_cdr_pass, value);
        break;
    case OPT_CDR_DB:
        config_set_string_option(&config_cdr_db, value);
        break;
    case OPT_CDR_PORT:
        config_cdr_port = atoi(value);
        break;
    case OPT_INTERMEDIATE_CDR_HOST:
        config_set_string_option(&config_intermediate_cdr_host, value);
        break;
    case OPT_INTERMEDIATE_CDR_PORT:
        config_intermediate_cdr_port = atoi(value);
        break;
    case OPT_PROV_HOST:
        config_set_string_option(&config_prov_host, value);
        break;
    case OPT_PROV_USER:
        config_set_string_option(&config_prov_user, value);
        break;
    case OPT_PROV_PASS:
        config_set_string_option(&config_prov_pass, value);
        break;
    case OPT_PROV_DB:
        config_set_string_option(&config_prov_db, value);
        break;
    case OPT_PROV_PORT:
        config_prov_port = atoi(value);
        break;
    case OPT_STATS_HOST:
        config_set_string_option(&config_stats_host, value);
        break;
    case OPT_STATS_USER:
        config_set_string_option(&config_stats_user, value);
        break;
    case OPT_STATS_PASS:
        config_set_string_option(&config_stats_pass, value);
        break;
    case OPT_STATS_PORT:
        config_stats_port = atoi(value);
        break;
    case OPT_STATS_DB:
        config_set_string_option(&config_stats_db, value);
        break;
    case OPT_STATS_PERIOD:
        config_stats_period = (med_stats_period_t)atoi(value);
        break;
    case OPT_REDIS_HOST:
        config_set_string_option(&config_redis_host, value);
        break;
    case OPT_REDIS_PORT:
        config_redis_port = atoi(value);
        break;
    case OPT_REDIS_DB:
        config_redis_db = atoi(value);
        break;
    case OPT_REDIS_PASS:
        config_set_string_option(&config_redis_pass, value);
        break;
    case OPT_MAINTENANCE:
        config_maintenance = 1;
        break;
    case OPT_LEG_TOKENS:
        strict_leg_tokens = 1;
        break;
    case OPT_MAX_ACC_AGE:
        config_max_acc_age = atoi(value);
        break;
    case OPT_INTERMEDIATE_INTERVAL:
        config_intermediate_interval = atoi(value);
        break;
    }
}

static void config_set_defaults(void)
{
    /* Set defaults for string values. */
    config_set_string_default(&config_pid_path, MEDIATOR_DEFAULT_PIDPATH);
    config_set_string_default(&config_med_host, MEDIATOR_DEFAULT_ACCHOST);
    config_set_string_default(&config_med_user, MEDIATOR_DEFAULT_ACCUSER);
    config_set_string_default(&config_med_pass, MEDIATOR_DEFAULT_ACCPASS);
    config_set_string_default(&config_med_db, MEDIATOR_DEFAULT_ACCDB);
    config_set_string_default(&config_cdr_host, MEDIATOR_DEFAULT_CDRHOST);
    config_set_string_default(&config_cdr_user, MEDIATOR_DEFAULT_CDRUSER);
    config_set_string_default(&config_cdr_pass, MEDIATOR_DEFAULT_CDRPASS);
    config_set_string_default(&config_cdr_db, MEDIATOR_DEFAULT_CDRDB);
    config_set_string_default(&config_intermediate_cdr_host, MEDIATOR_DEFAULT_CDRHOST);
    config_set_string_default(&config_prov_host, MEDIATOR_DEFAULT_PROVHOST);
    config_set_string_default(&config_prov_user, MEDIATOR_DEFAULT_PROVUSER);
    config_set_string_default(&config_prov_pass, MEDIATOR_DEFAULT_PROVPASS);
    config_set_string_default(&config_prov_db, MEDIATOR_DEFAULT_PROVDB);
    config_set_string_default(&config_stats_host, MEDIATOR_DEFAULT_STATSHOST);
    config_set_string_default(&config_stats_user, MEDIATOR_DEFAULT_STATSUSER);
    config_set_string_default(&config_stats_pass, MEDIATOR_DEFAULT_STATSPASS);
    config_set_string_default(&config_stats_db, MEDIATOR_DEFAULT_STATSDB);
    config_set_string_default(&config_redis_host, MEDIATOR_DEFAULT_REDISHOST);
}

static int config_parse_line(char *line)
{
    char *sep;
    const char *value;
    struct option *option;

    for (sep = line; *sep; sep++)
        if (isspace(*sep) || *sep == '=')
            break;
    for (value = sep; *value; value++)
        if (!isspace(*value) && *value != '=')
            break;
    if (value != sep)
        *sep = '\0';

    for (option = long_options; option->name; option++)
        if (strcmp(option->name, line) == 0)
            break;

    if (option->name == NULL) {
        L_ERROR("No option found in config file line '%s'", line);
        return -1;
    }

    if (option->has_arg == no_argument && *value != '\0') {
        L_ERROR("Unexpected value in config file option '%s'",
               option->name);
        return -1;
    } else if (option->has_arg == required_argument && *value == '\0') {
        L_ERROR("Missing value in config file option '%s'",
               option->name);
        return -1;
    }

    config_set_option(option->val, value);

    return 0;
}

static int config_parse_file(const char *filename)
{
    FILE *conffile;
    char *line = NULL;
    size_t len = 0;
    ssize_t nread;

    L_DEBUG("Loading config file '%s'", filename);

    conffile = fopen(filename, "r");
    if (conffile == NULL) {
        if (errno == ENOENT)
            return 0;

        L_ERROR("Error loading config file: %s", strerror(errno));
        return -1;
    }

    while ((nread = getline(&line, &len, conffile)) >= 0) {
        if (line[0] == '#')
            continue;

        if (line[nread -1] == '\n')
            line[nread - 1] = '\0';

        if (config_parse_line(line) < 0) {
            L_ERROR("Error parsing config file");
            return -1;
        }
    }

    free(line);
    fclose(conffile);

    return 0;
}

static int config_parse_cmdopts(int argc, char **argv)
{
    int c;

    while ((c = getopt_long(argc, argv, options, long_options, NULL)) != -1)
    {
        if (c == '?' || c == ':')
            config_help(argv[0], 0);
        else
            config_set_option(c, optarg);
    }

    return 0;
}

int config_parse(int argc, char **argv)
{
    int rc = 0;

    rc |= config_parse_cmdopts(argc, argv);
    config_set_string_default(&config_file_path, MEDIATOR_DEFAULT_CONFIG_FILE);
    rc |= config_parse_file(config_file_path);
    config_set_defaults();

    return rc;
}

void config_cleanup()
{
    free(config_pid_path);
    free(config_cdr_host);
    free(config_cdr_user);
    free(config_cdr_pass);
    free(config_cdr_db);
    free(config_intermediate_cdr_host);
    free(config_med_host);
    free(config_med_user);
    free(config_med_pass);
    free(config_med_db);
    free(config_prov_host);
    free(config_prov_user);
    free(config_prov_pass);
    free(config_prov_db);
    free(config_stats_host);
    free(config_stats_user);
    free(config_stats_pass);
    free(config_stats_db);
    free(config_redis_host);
    if (config_redis_pass)
        free(config_redis_pass);
}