/*
 * Copyright (C) 2011-2013 The Android Open Source Project
 *
 * Licensed 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.
 */

#define LOG_TAG "str_params"
//#define LOG_NDEBUG 0

#define _GNU_SOURCE 1
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <cutils/hashmap.h>
#include <cutils/memory.h>
#include <cutils/str_parms.h>
#include <log/log.h>

#define UNUSED __attribute__((unused))

struct str_parms {
    Hashmap *map;
};


static bool str_eq(void *key_a, void *key_b)
{
    return !strcmp((const char *)key_a, (const char *)key_b);
}

/* use djb hash unless we find it inadequate */
static int str_hash_fn(void *str)
{
    uint32_t hash = 5381;
    char *p;

    for (p = str; p && *p; p++)
        hash = ((hash << 5) + hash) + *p;
    return (int)hash;
}

struct str_parms *str_parms_create(void)
{
    struct str_parms *str_parms;

    str_parms = calloc(1, sizeof(struct str_parms));
    if (!str_parms)
        return NULL;

    str_parms->map = hashmapCreate(5, str_hash_fn, str_eq);
    if (!str_parms->map)
        goto err;

    return str_parms;

err:
    free(str_parms);
    return NULL;
}

struct remove_ctxt {
    struct str_parms *str_parms;
    const char *key;
};

static bool remove_pair(void *key, void *value, void *context)
{
    struct remove_ctxt *ctxt = context;
    bool should_continue;

    /*
     * - if key is not supplied, then we are removing all entries,
     *   so remove key and continue (i.e. return true)
     * - if key is supplied and matches, then remove it and don't
     *   continue (return false). Otherwise, return true and keep searching
     *   for key.
     *
     */
    if (!ctxt->key) {
        should_continue = true;
        goto do_remove;
    } else if (!strcmp(ctxt->key, key)) {
        should_continue = false;
        goto do_remove;
    }

    return true;

do_remove:
    hashmapRemove(ctxt->str_parms->map, key);
    free(key);
    free(value);
    return should_continue;
}

void str_parms_del(struct str_parms *str_parms, const char *key)
{
    struct remove_ctxt ctxt = {
        .str_parms = str_parms,
        .key = key,
    };
    hashmapForEach(str_parms->map, remove_pair, &ctxt);
}

void str_parms_destroy(struct str_parms *str_parms)
{
    struct remove_ctxt ctxt = {
        .str_parms = str_parms,
    };

    hashmapForEach(str_parms->map, remove_pair, &ctxt);
    hashmapFree(str_parms->map);
    free(str_parms);
}

struct str_parms *str_parms_create_str(const char *_string)
{
    struct str_parms *str_parms;
    char *str;
    char *kvpair;
    char *tmpstr;
    int items = 0;

    str_parms = str_parms_create();
    if (!str_parms)
        goto err_create_str_parms;

    str = strdup(_string);
    if (!str)
        goto err_strdup;

    ALOGV("%s: source string == '%s'\n", __func__, _string);

    kvpair = strtok_r(str, ";", &tmpstr);
    while (kvpair && *kvpair) {
        char *eq = strchr(kvpair, '='); /* would love strchrnul */
        char *value;
        char *key;
        void *old_val;

        if (eq == kvpair)
            goto next_pair;

        if (eq) {
            key = strndup(kvpair, eq - kvpair);
            if (*(++eq))
                value = strdup(eq);
            else
                value = strdup("");
        } else {
            key = strdup(kvpair);
            value = strdup("");
        }

        /* if we replaced a value, free it */
        old_val = hashmapPut(str_parms->map, key, value);
        if (old_val) {
            free(old_val);
            free(key);
        }

        items++;
next_pair:
        kvpair = strtok_r(NULL, ";", &tmpstr);
    }

    if (!items)
        ALOGV("%s: no items found in string\n", __func__);

    free(str);

    return str_parms;

err_strdup:
    str_parms_destroy(str_parms);
err_create_str_parms:
    return NULL;
}

int str_parms_add_str(struct str_parms *str_parms, const char *key,
                      const char *value)
{
    void *tmp_key = NULL;
    void *tmp_val = NULL;
    void *old_val = NULL;

    // strdup and hashmapPut both set errno on failure.
    // Set errno to 0 so we can recognize whether anything went wrong.
    int saved_errno = errno;
    errno = 0;

    tmp_key = strdup(key);
    if (tmp_key == NULL) {
        goto clean_up;
    }

    tmp_val = strdup(value);
    if (tmp_val == NULL) {
        goto clean_up;
    }

    old_val = hashmapPut(str_parms->map, tmp_key, tmp_val);
    if (old_val == NULL) {
        // Did hashmapPut fail?
        if (errno == ENOMEM) {
            goto clean_up;
        }
        // For new keys, hashmap takes ownership of tmp_key and tmp_val.
        tmp_key = tmp_val = NULL;
    } else {
        // For existing keys, hashmap takes ownership of tmp_val.
        // (It also gives up ownership of old_val.)
        tmp_val = NULL;
    }

clean_up:
    free(tmp_key);
    free(tmp_val);
    free(old_val);
    int result = -errno;
    errno = saved_errno;
    return result;
}

int str_parms_add_int(struct str_parms *str_parms, const char *key, int value)
{
    char val_str[12];
    int ret;

    ret = snprintf(val_str, sizeof(val_str), "%d", value);
    if (ret < 0)
        return -EINVAL;

    ret = str_parms_add_str(str_parms, key, val_str);
    return ret;
}

int str_parms_add_float(struct str_parms *str_parms, const char *key,
                        float value)
{
    char val_str[23];
    int ret;

    ret = snprintf(val_str, sizeof(val_str), "%.10f", value);
    if (ret < 0)
        return -EINVAL;

    ret = str_parms_add_str(str_parms, key, val_str);
    return ret;
}

int str_parms_has_key(struct str_parms *str_parms, const char *key) {
    return hashmapGet(str_parms->map, (void *)key) != NULL;
}

int str_parms_get_str(struct str_parms *str_parms, const char *key, char *val,
                      int len)
{
    char *value;

    value = hashmapGet(str_parms->map, (void *)key);
    if (value)
        return strlcpy(val, value, len);

    return -ENOENT;
}

int str_parms_get_int(struct str_parms *str_parms, const char *key, int *val)
{
    char *value;
    char *end;

    value = hashmapGet(str_parms->map, (void *)key);
    if (!value)
        return -ENOENT;

    *val = (int)strtol(value, &end, 0);
    if (*value != '\0' && *end == '\0')
        return 0;

    return -EINVAL;
}

int str_parms_get_float(struct str_parms *str_parms, const char *key,
                        float *val)
{
    float out;
    char *value;
    char *end;

    value = hashmapGet(str_parms->map, (void *)key);
    if (!value)
        return -ENOENT;

    out = strtof(value, &end);
    if (*value == '\0' || *end != '\0')
        return -EINVAL;

    *val = out;
    return 0;
}

static bool combine_strings(void *key, void *value, void *context)
{
    char **old_str = context;
    char *new_str;
    int ret;

    ret = asprintf(&new_str, "%s%s%s=%s",
                   *old_str ? *old_str : "",
                   *old_str ? ";" : "",
                   (char *)key,
                   (char *)value);
    if (*old_str)
        free(*old_str);

    if (ret >= 0) {
        *old_str = new_str;
        return true;
    }

    *old_str = NULL;
    return false;
}

char *str_parms_to_str(struct str_parms *str_parms)
{
    char *str = NULL;

    if (hashmapSize(str_parms->map) > 0)
        hashmapForEach(str_parms->map, combine_strings, &str);
    else
        str = strdup("");
    return str;
}

static bool dump_entry(void *key, void *value, void *context UNUSED)
{
    ALOGI("key: '%s' value: '%s'\n", (char *)key, (char *)value);
    return true;
}

void str_parms_dump(struct str_parms *str_parms)
{
    hashmapForEach(str_parms->map, dump_entry, str_parms);
}

#ifdef TEST_STR_PARMS
static void test_str_parms_str(const char *str)
{
    struct str_parms *str_parms;
    char *out_str;

    str_parms = str_parms_create_str(str);
    str_parms_add_str(str_parms, "dude", "woah");
    str_parms_add_str(str_parms, "dude", "woah");
    str_parms_del(str_parms, "dude");
    str_parms_dump(str_parms);
    out_str = str_parms_to_str(str_parms);
    str_parms_destroy(str_parms);
    ALOGI("%s: '%s' stringified is '%s'", __func__, str, out_str);
    free(out_str);
}

int main(void)
{
    test_str_parms_str("");
    test_str_parms_str(";");
    test_str_parms_str("=");
    test_str_parms_str("=;");
    test_str_parms_str("=bar");
    test_str_parms_str("=bar;");
    test_str_parms_str("foo=");
    test_str_parms_str("foo=;");
    test_str_parms_str("foo=bar");
    test_str_parms_str("foo=bar;");
    test_str_parms_str("foo=bar;baz");
    test_str_parms_str("foo=bar;baz=");
    test_str_parms_str("foo=bar;baz=bat");
    test_str_parms_str("foo=bar;baz=bat;");
    test_str_parms_str("foo=bar;baz=bat;foo=bar");

    // hashmapPut reports errors by setting errno to ENOMEM.
    // Test that we're not confused by running in an environment where this is already true.
    errno = ENOMEM;
    test_str_parms_str("foo=bar;baz=");
    if (errno != ENOMEM) {
        abort();
    }
    test_str_parms_str("foo=bar;baz=");

    return 0;
}
#endif
