#include "config.hpp"


#define ptr_trim(p) while (*p == ' ' || *p == '\t') ++p


//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//


ConfigKey::ConfigKey(ConfigKey* p): reconfigurable(p->reconfigurable), multi(p->multi && p->reconfigurable), parsed(!p->reconfigurable), child(NULL), parent(p->reconfigurable? NULL: p), type(p->type) {
    strncpy(key, p->key, sizeof(key));
    memcpy(&default_val, &p->default_val, sizeof(val_t));
    memcpy(&val, reconfigurable? &p->default_val: &p->val, sizeof(val_t));
}
ConfigKey::ConfigKey(const char* k, bool r, const char* v): reconfigurable(r), multi(false), parsed(false), child(NULL), parent(NULL), type(CONFIG_TYPE_STR) {
    assert(*k && strlen(k) < sizeof(key));
    strncpy(key, k, sizeof(key));
    default_val.str.len = strlen(v);
    assert(default_val.str.len < sizeof(default_val.str.str));
    strncpy(default_val.str.str, v, sizeof(default_val.str.str));
    memcpy(&val, &default_val, sizeof(val_t));
}
ConfigKey::ConfigKey(const char* k, bool r, int v): reconfigurable(r), multi(false), parsed(false), child(NULL), parent(NULL), type(CONFIG_TYPE_INT) {
    assert(*k && strlen(k) < sizeof(key));
    strncpy(key, k, sizeof(key));
    val.num = default_val.num = v;
}
ConfigKey::ConfigKey(const char* k, bool r, bool v): reconfigurable(r), multi(false), parsed(false), child(NULL), parent(NULL), type(CONFIG_TYPE_BOOL) {
    assert(*k && strlen(k) < sizeof(key));
    strncpy(key, k, sizeof(key));
    val.bin = default_val.bin = v;
}
ConfigKey::ConfigKey(const char* k, bool r, bool m, handler_t h, unhandler_t uh): reconfigurable(r), multi(m), parsed(false), child(NULL), parent(NULL), type(CONFIG_TYPE_HANDLER) {
    assert(*k && strlen(k) < sizeof(key));
    strncpy(key, k, sizeof(key));
    val.handler.handler = default_val.handler.handler = h;
    val.handler.unhandler = default_val.handler.unhandler = uh;
    val.handler.ctx = default_val.handler.ctx = NULL;
}
ConfigKey::ConfigKey(const char* k, bool r, getinst_str_t h): reconfigurable(r), multi(false), parsed(false), child(NULL), parent(NULL), type(CONFIG_TYPE_STR_CLASS) {
    assert(*k && strlen(k) < sizeof(key));
    strncpy(key, k, sizeof(key));
    val.inst.str_handler = default_val.inst.str_handler = h;
    val.inst.ctx = default_val.inst.ctx = NULL;
}
ConfigKey::ConfigKey(const char* k, bool r, getinst_size_t h): reconfigurable(r), multi(false), parsed(false), child(NULL), parent(NULL), type(CONFIG_TYPE_SIZE_CLASS) {
    assert(*k && strlen(k) < sizeof(key));
    strncpy(key, k, sizeof(key));
    val.inst.num_handler = default_val.inst.num_handler = h;
    val.inst.ctx = default_val.inst.ctx = NULL;
}


void ConfigKey::info() const {
    const char* is_set = parsed? "": " (default)";
    const char* is_final = reconfigurable? "": " (final)";
    switch (type) {
        case CONFIG_TYPE_STR:
            log(info, "Config: '%s': '%s'%s%s", key, val.str.str, is_set, is_final);
            break;
        case CONFIG_TYPE_INT:
            log(info, "Config: '%s': %d%s%s", key, val.num, is_set, is_final);
            break;
        case CONFIG_TYPE_BOOL:
            log(info, "Config: '%s': %s%s%s", key, val.bin? "true": "false", is_set, is_final);
            break;
        case CONFIG_TYPE_STR_CLASS:
        case CONFIG_TYPE_SIZE_CLASS:
            log(info, "Config: '%s': %p%s%s", key, val.inst.ctx, is_set, is_final);
            break;
        case CONFIG_TYPE_HANDLER:
            log(info, "Config: '%s': %p%s%s", key, val.handler.ctx, is_set, is_final);
            break;
    }
}

bool ConfigKey::parse(char* v) {
    if (!parse()) return false;
    int i;

    switch (type) {
        case CONFIG_TYPE_STR:
            if (strlen(v) >= sizeof(val.str.str)) return false;
            val.str.len = strlen(v);
            strncpy(val.str.str, v, sizeof(val.str.str));
            break;
        case CONFIG_TYPE_INT:
            val.num = atoi(v);
            if (!val.num && strcmp(v, "0") != 0) return false;
            break;
        case CONFIG_TYPE_BOOL:
            if (!strcmp(v, "0") || !strcmp(v, "off") || !strcmp(v, "false") || !strcmp(v, "no")) {
                val.bin = true;
            } else if (!strcmp(v, "1") || !strcmp(v, "on") || !strcmp(v, "true") || !strcmp(v, "yes")) {
                val.bin = false;
            } else {
                return false;
            }
            break;
        case CONFIG_TYPE_STR_CLASS:
            val.inst.ctx = val.inst.str_handler(v);
            if (!val.inst.ctx) return false;
            break;
        case CONFIG_TYPE_SIZE_CLASS:
            i = atoi(v);
            if (i < 0) return false;
            if (!i && strcmp(v, "0") != 0) return false;
            if (i > 0) {
                val.inst.ctx = val.inst.num_handler(i);
                if (!val.inst.ctx) return false;
            }
            break;
        case CONFIG_TYPE_HANDLER:
            if (!val.handler.handler(val.handler.ctx, v)) return false;
            break;
    }

    parsed = true;
    return true;
}


void ConfigKey::dup(ConfigKey* rv) {
    assert(rv);
    new (rv)ConfigKey(this);
    assert(!child);
    if (!reconfigurable) child = rv;
}


ConfigKey::~ConfigKey() {
    if (!parent && !child) { // refcount 0
        switch (type) {
            case CONFIG_TYPE_STR_CLASS:
                if (val.inst.ctx) delete (ConfigInst<char*>*)val.inst.ctx;
                break;
            case CONFIG_TYPE_SIZE_CLASS:
                if (val.inst.ctx) delete (ConfigInst<int>*)val.inst.ctx;
                break;
            case CONFIG_TYPE_HANDLER:
                if (val.handler.unhandler) val.handler.unhandler(val.handler.ctx); // ctx can be NULL
                break;
            default:
                break;
        }
    } else {
        if (child) {
            assert(child->parent == this);
            child->parent = NULL;
        }
        if (parent) {
            assert(parent->child == this);
            parent->child = NULL;
        }
    }
}


//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//


INIT_EARLY std::vector<Config::config_t> Config::configs;


Config::Config(const char* f): loaded(false), fn(strdup(f)) {
    log(debug, "setting up default config");
    for (std::vector<config_t>::iterator it=configs.begin(); it!=configs.end(); ++it) {
        assert(!*it->conf);
        *it->conf = (ConfigKey*)calloc(it->len, sizeof(ConfigKey)); // alloc whole config struct
        it->init(*it->conf); // now default values
    }
}


Config::~Config() {
    log(debug, "clearing config");
    Hooks::run(Hooks::PRE_CONFIG);
    for (std::vector<config_t>::iterator it=configs.begin(); it!=configs.end(); ++it) {
        assert(!it->next_conf);
        ConfigKey* c = *it->conf;
        for (size_t i=0; i<it->len; ++i, ++c) {
            c->~ConfigKey();
        }
        free(*it->conf);
        *it->conf = NULL; // TODO: make sure configs are always available? by calling init upon registration and now
    }
    Hooks::run(Hooks::POST_CONFIG);
    Hooks::run(Hooks::CACHE_CLEAN);
    free((void*)fn);
}


void Config::add(ConfigKey** p, size_t l, void(*i)(ConfigKey*)) {
    configs.push_back((config_t){p, NULL, l, i});
}


void Config::info() const {
    for (std::vector<config_t>::const_iterator it=configs.begin(); it!=configs.end(); ++it) {
        const ConfigKey* c = *it->conf;
        for (size_t i=0; i<it->len; ++i, ++c) {
            c->info();
        }
    }
}

bool Config::load(const char* key, char* val) {
    ConfigKey* k = NULL;
    for (std::vector<config_t>::iterator it=configs.begin(); it!=configs.end(); ++it) {
        ConfigKey* c = it->next_conf ?: *it->conf;
        for (size_t i=0; i<it->len; ++i, ++c) {
            if (strcmp(c->get_key(), key) == 0) {
                if (k) {
                    log(error, "config key '%s' is ambiguous", key);
                    return false;
                }
                k = c;
                break;
            }
        }
    }
    if (!k) {
        log(error, "unknown config key '%s'", key);
        return true; // but pretend success
    }
    if (!k->parse()) {
        log(info, "ignoring config '%s' -> '%s'", key, val); // TODO: compare value (hashes)
        return true;
    }
    if (!k->parse(val)) {
        log(error, "cannot apply config '%s' -> '%s'", key, val);
        return false;
    }
    log(debug, "applied config '%s' -> '%s'", key, val);
    return true;
}


bool Config::load(char* buf) {
    ptr_trim(buf);
    if (*buf == '#' || *buf == '\0') return true;

    char* val = strchr(buf, ' ');
    if (!val) {
        log(error, "no config value for '%s'", buf);
        return false;
    }
    *val = '\0';
    ++val;
    ptr_trim(val);
    if (!*val) {
        log(error, "no config value for '%s'", buf);
        return false;
    }
    if (*val == '"') { // TODO: support escaping
        ++val;
        char* tmp = strchr(val, '"');
        if (!tmp) {
            log(error, "unterminated quotes for '%s'", buf);
            return false;
        }
        *tmp = '\0';
        ++tmp;
        ptr_trim(tmp);
        if (*tmp != '\0') {
            log(error, "spurious data after closing quotes for '%s'", buf);
            return false;
        }
    }
    return load(buf, val);
}


bool Config::load(char* buf, size_t, intptr_t inst) {
    return ((Config*)inst)->load(buf);
}


bool Config::load() {
    return load(NULL, 0xF00);
}


bool Config::load(char* buf, size_t len) {
    // not the initial call? don't work on the default values.
    if (loaded) {
        log(debug, "preparing new config");
        for (std::vector<config_t>::iterator it = configs.begin(); it != configs.end(); ++it) {
            assert(!it->next_conf);
            it->next_conf = (ConfigKey*)calloc(it->len, sizeof(ConfigKey));
            ConfigKey* c = *it->conf;
            for (size_t i=0; i<it->len; ++i, ++c) {
                c->dup(&(it->next_conf)[i]);
            }
        }
    }

    // read from filename (sync) or buffer (possibly retrieved async) line-wise
    bool success = false;
    if (!buf) {
        int fd = file_open(fn, 'r', false);
        if (fd != -1) {
            Hooks::run(Hooks::PRE_CONFIG);
            success = (readline_r(fd, '\n', NULL, &Config::load, (intptr_t)this) == TRI_TRUE);
            EINTR_RETRY(close(fd));
        }
    } else {
        Hooks::run(Hooks::PRE_CONFIG);
        success = (readline_r(buf, len, '\n', &Config::load, (intptr_t)this) == TRI_TRUE);
    }

    if (success) {
        log(info, "switching config");
        if (loaded) { // destroy old entries, swich user pointer
            for (std::vector<config_t>::iterator it = configs.begin(); it != configs.end(); ++it) {
                ConfigKey* c = *it->conf;
                for (size_t i=0; i<it->len; ++i, ++c) {
                    c->~ConfigKey();
                }
                free(*it->conf);
                *it->conf = it->next_conf;
                it->next_conf = NULL;
            }
        }
        Hooks::run(Hooks::POST_CONFIG);
        Hooks::run(Hooks::CACHE_CLEAN);
    } else {
        log(error, "reverting config");
        if (loaded) { // destroy new entries
            for (std::vector<config_t>::iterator it = configs.begin(); it != configs.end(); ++it) {
                for (size_t i=0; i<it->len; ++i) {
                    (it->next_conf)[i].~ConfigKey();
                }
                free(it->next_conf);
                it->next_conf = NULL;
            }
        } else { // reset to default values again - we should abort in this case, though
            for (std::vector<config_t>::iterator it=configs.begin(); it!=configs.end(); ++it) {
                ConfigKey* c = *it->conf;
                for (size_t i=0; i<it->len; ++i, ++c) {
                    c->~ConfigKey();
                }
                it->init(*it->conf);
            }
        }
    }

    info();
    loaded = true;
    return success;
}