#include "config.hpp"
#include <assert.h>


#define CONFIG_FLAG(name, dflt, mult, spn_s, spn_e, help) .name = { STRINGIFY(name), dflt, mult, NULL, std::vector<const char*>(), spn_s, spn_e, help }
#define for_each(new_node) for (config_node_t* new_node = (config_node_t*)&config; new_node < (config_node_t*)&config + sizeof(config_t)/sizeof(config_node_t); new_node++)


config_t config = {
    CONFIG_FLAG(port, "", true, "123456789", "0123456789", "port to listen on"),
    CONFIG_FLAG(server_name, "", false, NULL, "0123456789abcdefghijklmnopqrstuvwxyz._-", "irc server/domain name"),
    CONFIG_FLAG(server_pw, NULL, false, NULL, NULL, "global server password"),
    CONFIG_FLAG(oper_pw, NULL, false, NULL, NULL, "global oper password"),
    CONFIG_FLAG(channel, NULL, true, "@+#", NULL, "pre-defined persistent channel. #-prefixed for normal channel, @-prefixed for oper-only, +-prefixed for voice/nickserv-only. supports an appended :topic."),
    CONFIG_FLAG(oper_channels, "0", false, "01", "", "enable/disable (0|1) the creation of new channels for operators."),
    CONFIG_FLAG(nickserv_dir, NULL, false, NULL, NULL, "nickserv db directory. disabled if not set."),
    CONFIG_FLAG(chroot_user, "nobody", false, NULL, "abcdefghijklmnopqrstuvwxyz-", "username to drop to when starting as root"),
    CONFIG_FLAG(chroot_dir, NULL, false, "/", NULL, "directory to chroot to when starting as root"),
#ifdef USE_OPENSSL
    CONFIG_FLAG(ssl, "0", false, "01", "", "enable/disable (0|1) ssl support for all connections."), // would make not much sense e.g. for a particular port only security-wise
    CONFIG_FLAG(ssl_key, NULL, false, NULL, NULL, "private key when using ssl"),
    CONFIG_FLAG(ssl_cert, NULL, false, NULL, NULL, "certificate for ssl"),
#else
    CONFIG_FLAG(ssl, "0", false, "0", "", "ssl support not available."),
#endif
};


static config_node_t* config_node_find(const char* name) {
    for_each (tmp) {
        if (strcasecmp(name, tmp->name) == 0) {
            return tmp;
        }
    }
    return NULL;
}


static bool config_node_set(const char* name, char* val) {
    config_node_t* node = config_node_find(name);
    if (!node) {
        LOG("unknown option '%s'", name);
        return false;
    }
    if (node->str && !node->multi) {
        LOG("option '%s' already set", name);
        return false;
    }

    if (!*val) {
        LOG("empty value for option '%s'", name);
        return false;
    }
    if (node->spn_start && strspn(val, node->spn_start) < 1) {
        LOG("invalid value for option '%s'", name);
        return false;
    }
    if (node->spn && strspn(val+1, node->spn) != strlen(val)-1) {
        LOG("invalid value for option '%s'", name);
        return false;
    }

    node->strs.push_back(strdup(val));
    node->str = node->strs.front();
    return true;
}


static bool parse_line(char* l) {
    while (*l == ' ' || *l == '\t') ++l;
    if (*l == '#' || *l == '\r' || *l == '\n' || *l == '\0') return true;

    char* p = strchr(l, '\n');
    if (p) {
        *p = '\0';
    }

    p = strchr(l, ' ');
    if (!p) {
        LOG("no config value at: '%s'", l);
        return false;
    }
    *(p++) = '\0';
    while (*p == ' ' || *p == '\t') ++p;

    return config_node_set(l, p);
}


bool parse_config(const char* fn) {
    FILE* fp = fopen(fn, "r");
    if (!fp) {
        LOG_ERRNO("cannot open %s", fn);
        return false;
    }

    char buf[1024];
    while (fgets(buf, sizeof(buf), fp)) {
        if (!parse_line(buf)) {
            fclose(fp);
            free_config();
            return false;
        }
    }
    fclose(fp);

    for_each (node) {
        if (!node->str) {
            if (node->dflt) {
                if (*(node->dflt)) {
                    node->strs.push_back(strdup(node->dflt));
                    node->str = node->strs.front();
                } else {
                    LOG("option '%s' missing", node->name);
                    free_config();
                    return false;
                }
            }
        }
    }

    return true;
}


void log_usage() {
    LOG("Supported config flags:");
    for_each (node) {
        LOG(" * %s (%s)%s: %s",
            node->name,
            (node->dflt)? (*(node->dflt)? node->dflt: "required"): "optional",
            node->multi? " (multi)": "",
            node->help
        );
    }
}


void free_config() {
    for_each (node) {
        while (!node->strs.empty()) {
            free((void*)*node->strs.rbegin());
            node->strs.pop_back();
        }
        node->str = NULL;
    }
}