/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "core/conf.h"
#include "core/htrace.h"
#include "util/htable.h"
#include "util/log.h"

#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#define HTRACE_DEFAULT_CONF_KEYS (\
     HTRACE_PROB_SAMPLER_FRACTION_KEY "=0.01"\
     ";" HTRACED_BUFFER_SIZE_KEY "=67108864"\
     ";" HTRACED_FLUSH_INTERVAL_MS_KEY "=120000"\
     ";" HTRACED_WRITE_TIMEO_MS_KEY "=60000"\
     ";" HTRACED_READ_TIMEO_MS_KEY "=60000"\
     ";" HTRACE_TRACER_ID "=%{tname}/%{ip}"\
     ";" HTRACED_ADDRESS_KEY "=localhost:9096"\
     ";" HTRACED_BUFFER_SEND_TRIGGER_FRACTION "=0.50"\
    )

static int parse_key_value(char *str, char **key, char **val)
{
    char *eq = strchr(str, '=');
    if (eq) {
        *eq = '\0';
        *val = strdup(eq + 1);
    } else {
        *val = strdup("true");
    }
    if (!*val) {
        return ENOMEM;
    }
    *key = strdup(str);
    if (!*key) {
        free(*val);
        return ENOMEM;
    }
    return 0;
}

static struct htable *htable_from_str(const char *str)
{
    struct htable *ht;
    char *cstr = NULL, *saveptr = NULL, *tok;
    int ret = ENOMEM;

    ht = htable_alloc(8, ht_hash_string, ht_compare_string);
    if (!ht) {
        goto done;
    }
    if (!str) {
        ret = 0;
        goto done;
    }
    cstr = strdup(str);
    if (!cstr) {
        goto done;
    }

    for (tok = strtok_r(cstr, ";", &saveptr); tok;
             tok = strtok_r(NULL, ";", &saveptr)) {
        char *key = NULL, *val = NULL;
        ret = parse_key_value(tok, &key, &val);
        if (ret) {
            goto done;
        }
        ret = htable_put(ht, key, val);
        if (ret) {
            goto done;
        }
    }
    ret = 0;
done:
    if (ret) {
        htable_free(ht);
        ht = NULL;
    }
    free(cstr);
    return ht;
}

struct htrace_conf *htrace_conf_from_strs(const char *values,
                                          const char *defaults)
{
    struct htrace_conf *cnf;

    cnf = calloc(1, sizeof(*cnf));
    if (!cnf) {
        return NULL;
    }
    cnf->values = htable_from_str(values);
    if (!cnf->values) {
        htrace_conf_free(cnf);
        return NULL;
    }
    cnf->defaults = htable_from_str(defaults);
    if (!cnf->defaults) {
        htrace_conf_free(cnf);
        return NULL;
    }
    return cnf;
}

struct htrace_conf *htrace_conf_from_str(const char *values)
{
    return htrace_conf_from_strs(values, HTRACE_DEFAULT_CONF_KEYS);
}

static void htrace_tuple_free(void *ctx, void *key, void *val)
{
    free(key);
    free(val);
}

void htrace_conf_free(struct htrace_conf *cnf)
{
    if (!cnf) {
        return;
    }
    if (cnf->values) {
        htable_visit(cnf->values, htrace_tuple_free, NULL);
        htable_free(cnf->values);
    }
    if (cnf->defaults) {
        htable_visit(cnf->defaults, htrace_tuple_free, NULL);
        htable_free(cnf->defaults);
    }
    free(cnf);
}

const char *htrace_conf_get(const struct htrace_conf *cnf, const char *key)
{
    const char *val;

    val = htable_get(cnf->values, key);
    if (val)
        return val;
    val = htable_get(cnf->defaults, key);
    return val;
}

static int convert_double(struct htrace_log *log, const char *key,
                   const char *in, double *out)
{
    char *endptr = NULL;
    int err;
    double ret;

    errno = 0;
    ret = strtod(in, &endptr);
    if (errno) {
        err = errno;
        htrace_log(log, "error parsing %s for %s: %d (%s)\n",
                   in, key, err, terror(err));
        return 0;
    }
    while (1) {
        char c = *endptr;
        if (c == '\0') {
            break;
        }
        if (!((c == ' ') || (c == '\t'))) {
            htrace_log(log, "error parsing %s for %s: garbage at end "
                       "of string.\n", in, key);
            return 0;
        }
        endptr++;
    }
    *out = ret;
    return 1;
}

double htrace_conf_get_double(struct htrace_log *log,
                             const struct htrace_conf *cnf, const char *key)
{
    const char *val;
    double out = 0;

    val = htable_get(cnf->values, key);
    if (val) {
        if (convert_double(log, key, val, &out)) {
            return out;
        }
    }
    val = htable_get(cnf->defaults, key);
    if (val) {
        if (convert_double(log, key, val, &out)) {
            return out;
        }
    }
    return 0;
}

static int convert_u64(struct htrace_log *log, const char *key,
                   const char *in, uint64_t *out)
{
    char *endptr = NULL;
    int err;
    uint64_t ret;

    errno = 0;
    ret = strtoull(in, &endptr, 10);
    if (errno) {
        err = errno;
        htrace_log(log, "error parsing %s for %s: %d (%s)\n",
                   in, key, err, terror(err));
        return 0;
    }
    while (1) {
        char c = *endptr;
        if (c == '\0') {
            break;
        }
        if (!((c == ' ') || (c == '\t'))) {
            htrace_log(log, "error parsing %s for %s: garbage at end "
                       "of string.\n", in, key);
            return 0;
        }
        endptr++;
    }
    *out = ret;
    return 1;
}

uint64_t htrace_conf_get_u64(struct htrace_log *log,
                             const struct htrace_conf *cnf, const char *key)
{
    const char *val;
    uint64_t out = 0;

    val = htable_get(cnf->values, key);
    if (val) {
        if (convert_u64(log, key, val, &out)) {
            return out;
        }
    }
    val = htable_get(cnf->defaults, key);
    if (val) {
        if (convert_u64(log, key, val, &out)) {
            return out;
        }
    }
    return 0;
}

// vim:ts=4:sw=4:et
