/*
    INI LIBRARY

    Value interpretation functions for single values
    and corresponding memory cleanup functions.

    Copyright (C) Dmitri Pal <dpal@redhat.com> 2012

    INI Library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    INI Library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with INI Library.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "config.h"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "trace.h"
#include "collection.h"
#include "collection_tools.h"
#include "ini_defines.h"
#include "ini_configobj.h"
#include "ini_config_priv.h"
#include "ini_valueobj.h"

/* Macro co convert to HEX value */
#define HEXVAL(c) (isdigit(c) ? (c - '0') : (tolower(c) - 'a') + 10)

static int is_same_section(struct ini_cfgobj *ini_config,
                           const char *section)
{
    int len = 0;

    TRACE_FLOW_ENTRY();

    /* If section is not defined it is different */
    if (ini_config->section == NULL) {
        TRACE_FLOW_RETURN(0);
        return 0;
    }

    len = strlen(section);

    /* If values are same this is the same section */
    if ((strncasecmp(ini_config->section, section, ini_config->section_len) == 0) &&
        (ini_config->section_len == len)) {
        TRACE_FLOW_RETURN(1);
        return 1;
    }

    /* Otherwise the values are different */
    TRACE_FLOW_RETURN(0);
    return 0;
}

static int is_same_name(struct ini_cfgobj *ini_config,
                        const char *name,
                        int name_len)
{
    TRACE_FLOW_ENTRY();

    /* If name is not defined it is different */
    if (ini_config->name == NULL) {
        TRACE_FLOW_RETURN(0);
        return 0;
    }

    /* If values are same this is the same value */
    if ((strncasecmp(ini_config->name, name, ini_config->name_len) == 0) &&
        (ini_config->name_len == name_len)) {
        TRACE_FLOW_RETURN(1);
        return 1;
    }

    /* Otherwise the values are different */
    TRACE_FLOW_RETURN(0);
    return 0;
}


/* Function to get value object from the configuration handle */
int ini_get_config_valueobj(const char *section,
                            const char *name,
                            struct ini_cfgobj *ini_config,
                            int mode,
                            struct value_obj **vo)
{
    int error = EOK;
    struct collection_item *section_handle = NULL;
    struct collection_item *item = NULL;
    struct collection_item *last_found = NULL;
    const char *to_find;
    char default_section[] = INI_DEFAULT_SECTION;
    uint64_t hash = 0;
    int len = 0, name_len = 0;

    TRACE_FLOW_ENTRY();

    /* Do we have the accepting memory ? */
    if (vo == NULL) {
        TRACE_ERROR_NUMBER("Invalid argument vo.", EINVAL);
        return EINVAL;
    }

    *vo = NULL;

    if (ini_config == NULL) {
        TRACE_ERROR_NUMBER("Invalid argument ini_config.", EINVAL);
        return EINVAL;
    }

    if ((mode < INI_GET_FIRST_VALUE) ||
        (mode > INI_GET_LAST_VALUE)) {
        TRACE_ERROR_NUMBER("Invalid argument mode:", mode);
        return EINVAL;
    }

    /* Do we have a name ? */
    if (name == NULL) {
        TRACE_ERROR_NUMBER("Name is NULL it will not be found.", EINVAL);
        return EINVAL;
    }

    /* Empty section means look for the default one */
    if (section == NULL) to_find = default_section;
    else to_find = section;

    TRACE_INFO_STRING("Getting Name:", name);
    TRACE_INFO_STRING("In Section:", to_find);

    /* Make sure we start over if this is the first value */
    if (mode == INI_GET_FIRST_VALUE) ini_config_clean_state(ini_config);

    /* Are we looking in the same section ? */
    if (!is_same_section(ini_config, to_find)) {

        /* This is a different section */
        ini_config_clean_state(ini_config);

        /* Get Subcollection */
        error = col_get_collection_reference(ini_config->cfg, &section_handle, to_find);
        /* Check error */
        if (error && (error != ENOENT)) {
            TRACE_ERROR_NUMBER("Failed to get section", error);
            return error;
        }

        /* Did we find a section */
        if ((error == ENOENT) || (section_handle == NULL)) {
            /* We have not found section - return success */
                TRACE_FLOW_EXIT();
            return EOK;
        }

        /* Create an iterator */
        error = col_bind_iterator(&(ini_config->iterator),
                                  section_handle,
                                  COL_TRAVERSE_ONELEVEL);
        /* Make sure we free the section we found */
        col_destroy_collection(section_handle);
        /* Check error */
        if (error) {
            TRACE_ERROR_NUMBER("Failed to bind to section", error);
            return error;
        }

        /* Save section */
        ini_config->section_len = strlen(to_find);
        ini_config->section = strndup(to_find, ini_config->section_len);
        /* Check error */
        if (ini_config->section == NULL) {
            TRACE_ERROR_NUMBER("Failed to save section name ", ENOMEM);
            ini_config_clean_state(ini_config);
            return ENOMEM;
        }
    }

    hash = col_make_hash(name, 0, &name_len);

    /* Check if this is the same name */
    if (!is_same_name(ini_config, name, name_len)) {
        TRACE_INFO_STRING("Saved name:", ini_config->name);
        TRACE_INFO_STRING("Passed name:", name);
        TRACE_INFO_NUMBER("Length of the saved name", ini_config->name_len);
        TRACE_INFO_NUMBER("Length of the passed name", name_len);
        col_rewind_iterator(ini_config->iterator);
        free(ini_config->name);
        ini_config->name = NULL;
        ini_config->name_len = 0;
    }

    /* Iterate through the section */
    do {

        /* Loop through a collection */
        error = col_iterate_collection(ini_config->iterator, &item);
        if (error) {
            TRACE_ERROR_NUMBER("Failed to iterate", error);
            ini_config_clean_state(ini_config);
            return error;
        }

        /* Are we done ? */
        if (item == NULL) {
            /* We looped to the end and found last value */
            if ((mode == INI_GET_LAST_VALUE) && (last_found)) {
                item = last_found;
                break;
            }
            /* There is nothing left to look for */
            ini_config_clean_state(ini_config);
            TRACE_FLOW_EXIT();
            return EOK;
        }

        if ((hash == col_get_item_hash(item)) &&
            (col_get_item_type(item) == COL_TYPE_BINARY) &&
            (strncasecmp(col_get_item_property(item, &len), name, name_len) == 0) &&
            (len == name_len)) {
                TRACE_INFO_STRING("Item is found", name);
                last_found = item;
                if (mode == INI_GET_LAST_VALUE) continue;
                break;
        }
    }
    while(1);

    if (!is_same_name(ini_config, name, name_len)) {
        /* Save name */
        ini_config->name_len = name_len;
        ini_config->name = strndup(name, name_len);
        /* Check error */
        if (ini_config->name == NULL) {
            TRACE_ERROR_NUMBER("Failed to save key name ", ENOMEM);
            ini_config_clean_state(ini_config);
            return ENOMEM;
        }
    }

    *vo = *((struct value_obj **)(col_get_item_data(item)));

    TRACE_FLOW_EXIT();
    return error;
}

/* Get long long value from config value object */
static long long ini_get_llong_config_value(struct value_obj *vo,
                                            int strict,
                                            long long def,
                                            int *error)
{
    int err;
    const char *str;
    char *endptr;
    long long val = 0;

    TRACE_FLOW_ENTRY();

    /* Do we have the vo ? */
    if (vo == NULL) {
        TRACE_ERROR_NUMBER("Invalid argument.", EINVAL);
        if (error) *error = EINVAL;
        return def;
    }

    if (error) *error = EOK;

    /* Get value - no error checking as we checked it above
     * and there is no other reson the function could fail.
     */
    value_get_concatenated(vo, &str);

    /* Try to parse the value */
    errno = 0;
    val = strtoll(str, &endptr, 10);
    err = errno;

    /* Check for various possible errors */
    if (err != 0) {
        TRACE_ERROR_NUMBER("Conversion failed", err);
        if (error) *error = err;
        return def;
    }

    /* Other error cases */
    if ((endptr == str) || (strict && (*endptr != '\0'))) {
        TRACE_ERROR_NUMBER("More characters or nothing processed", EIO);
        if (error) *error = EIO;
        return def;
    }

    TRACE_FLOW_NUMBER("ini_get_llong_config_value returning", (long)val);
    return val;
}

/* Get unsigned long long value from config value object */
static unsigned long long ini_get_ullong_config_value(struct value_obj *vo,
                                                      int strict,
                                                      unsigned long long def,
                                                      int *error)
{
    int err;
    const char *str;
    char *endptr;
    unsigned long long val = 0;

    TRACE_FLOW_ENTRY();

    /* Do we have the vo ? */
    if (vo == NULL) {
        TRACE_ERROR_NUMBER("Invalid argument.", EINVAL);
        if (error) *error = EINVAL;
        return def;
    }

    if (error) *error = EOK;

    /* Get value - no error checking as we checked it above
     * and there is no other reson the function could fail.
     */
    value_get_concatenated(vo, &str);

    errno = 0;
    val = strtoull(str, &endptr, 10);
    err = errno;

    /* Check for various possible errors */
    if (err != 0) {
        TRACE_ERROR_NUMBER("Conversion failed", err);
        if (error) *error = err;
        return def;
    }

    /* Other error cases */
    if ((endptr == str) || (strict && (*endptr != '\0'))) {
        TRACE_ERROR_NUMBER("More characters or nothing processed", EIO);
        if (error) *error = EIO;
        return def;
    }

    TRACE_FLOW_NUMBER("ini_get_ullong_config_value returning", val);
    return val;
}


/* Get integer value from config value */
int ini_get_int_config_value(struct value_obj *vo,
                             int strict,
                             int def,
                             int *error)
{
    long long val = 0;
    int err = 0;

    TRACE_FLOW_ENTRY();

    val = ini_get_llong_config_value(vo, strict, def, &err);
    if (err == 0) {
        if ((val > INT_MAX) || (val < INT_MIN)) {
            TRACE_ERROR_NUMBER("Value is out of range", ERANGE);
            val = def;
            err = ERANGE;
        }
    }

    if (error) *error = err;

    TRACE_FLOW_NUMBER("ini_get_int_config_value returning", (int)val);
    return (int)val;
}

/* Get unsigned integer value from config value object */
unsigned ini_get_unsigned_config_value(struct value_obj *vo,
                                       int strict,
                                       unsigned def,
                                       int *error)
{
    unsigned long long val = 0;
    int err = 0;

    TRACE_FLOW_ENTRY();

    val = ini_get_ullong_config_value(vo, strict, def, &err);
    if (err == 0) {
        if (val > UINT_MAX) {
            TRACE_ERROR_NUMBER("Value is out of range", ERANGE);
            val = def;
            err = ERANGE;
        }
    }

    if (error) *error = err;

    TRACE_FLOW_NUMBER("ini_get_unsigned_config_value returning",
                      (unsigned)val);
    return (unsigned)val;
}

/* Get long value from config value object */
long ini_get_long_config_value(struct value_obj *vo,
                               int strict,
                               long def,
                               int *error)
{
    long long val = 0;
    int err = 0;

    TRACE_FLOW_ENTRY();

    val = ini_get_llong_config_value(vo, strict, def, &err);
    if (err == 0) {
        if ((val > LONG_MAX) || (val < LONG_MIN)) {
            TRACE_ERROR_NUMBER("Value is out of range", ERANGE);
            val = def;
            err = ERANGE;
        }
    }

    if (error) *error = err;

    TRACE_FLOW_NUMBER("ini_get_long_config_value returning",
                      (long)val);
    return (long)val;
}

/* Get unsigned long value from config value object */
unsigned long ini_get_ulong_config_value(struct value_obj *vo,
                                         int strict,
                                         unsigned long def,
                                         int *error)
{
    unsigned long long val = 0;
    int err = 0;

    TRACE_FLOW_ENTRY();

    val = ini_get_ullong_config_value(vo, strict, def, &err);
    if (err == 0) {
        if (val > ULONG_MAX) {
            TRACE_ERROR_NUMBER("Value is out of range", ERANGE);
            val = def;
            err = ERANGE;
        }
    }

    if (error) *error = err;

    TRACE_FLOW_NUMBER("ini_get_ulong_config_value returning",
                      (unsigned long)val);
    return (unsigned long)val;
}

/* Get int32_t value from config value object */
int32_t ini_get_int32_config_value(struct value_obj *vo,
                                   int strict,
                                   int32_t def,
                                   int *error)
{
    long long val = 0;
    int err = 0;

    TRACE_FLOW_ENTRY();

    val = ini_get_llong_config_value(vo, strict, def, &err);
    if (err == 0) {
        if ((val > INT32_MAX) || (val < INT32_MIN)) {
            TRACE_ERROR_NUMBER("Value is out of range", ERANGE);
            val = def;
            err = ERANGE;
        }
    }

    if (error) *error = err;

    TRACE_FLOW_NUMBER("ini_get_int32_config_value returning",
                      (int32_t)val);
    return (int32_t)val;
}

/* Get uint32_t value from config value object */
uint32_t ini_get_uint32_config_value(struct value_obj *vo,
                                     int strict,
                                     uint32_t def,
                                     int *error)
{
    unsigned long long val = 0;
    int err = 0;

    TRACE_FLOW_ENTRY();

    val = ini_get_ullong_config_value(vo, strict, def, &err);
    if (err == 0) {
        if (val > UINT32_MAX) {
            TRACE_ERROR_NUMBER("Value is out of range", ERANGE);
            val = def;
            err = ERANGE;
        }
    }

    if (error) *error = err;

    TRACE_FLOW_NUMBER("ini_get_uint32_config_value returning",
                      (uint32_t)val);
    return (uint32_t)val;
}

/* Get int64_t value from config value ovject */
int64_t ini_get_int64_config_value(struct value_obj *vo,
                                   int strict,
                                   int64_t def,
                                   int *error)
{
    long long val = 0;
    int err = 0;

    TRACE_FLOW_ENTRY();

    val = ini_get_llong_config_value(vo, strict, def, &err);
    if (err == 0) {
        if ((val > INT64_MAX) || (val < INT64_MIN)) {
            TRACE_ERROR_NUMBER("Value is out of range", ERANGE);
            val = def;
            err = ERANGE;
        }
    }

    if (error) *error = err;

    TRACE_FLOW_NUMBER("ini_get_int64_config_value returning",
                      (int64_t)val);
    return (int64_t)val;
}

/* Get uint64_t value from config value object */
uint64_t ini_get_uint64_config_value(struct value_obj *vo,
                                     int strict,
                                     uint64_t def,
                                     int *error)
{
    unsigned long long val = 0;
    int err = 0;

    TRACE_FLOW_ENTRY();

    val = ini_get_ullong_config_value(vo, strict, def, &err);
    if (err == 0) {
        if (val > UINT64_MAX) {
            TRACE_ERROR_NUMBER("Value is out of range", ERANGE);
            val = def;
            err = ERANGE;
        }
    }

    if (error) *error = err;

    TRACE_FLOW_NUMBER("ini_get_uint64_config_value returning",
                      (uint64_t)val);
    return (uint64_t)val;
}

/* Get double value */
double ini_get_double_config_value(struct value_obj *vo,
                                   int strict, double def, int *error)
{
    const char *str;
    char *endptr;
    double val = 0;

    TRACE_FLOW_ENTRY();

    /* Do we have the vo ? */
    if (vo == NULL) {
        TRACE_ERROR_NUMBER("Invalid argument.", EINVAL);
        if (error) *error = EINVAL;
        return def;
    }

    if (error) *error = EOK;

    /* Get value - no error checking as we checked it above
     * and there is no other reason the function could fail.
     */
    value_get_concatenated(vo, &str);

    errno = 0;
    val = strtod(str, &endptr);

    /* Check for various possible errors */
    if ((errno == ERANGE) ||
        ((errno != 0) && (val == 0)) ||
        (endptr == str)) {
        TRACE_ERROR_NUMBER("Conversion failed", EIO);
        if (error) *error = EIO;
        return def;
    }

    if (strict && (*endptr != '\0')) {
        TRACE_ERROR_NUMBER("More characters than expected", EIO);
        if (error) *error = EIO;
        val = def;
    }

    TRACE_FLOW_DOUBLE("ini_get_double_config_value returning", val);
    return val;
}

/* Get boolean value */
unsigned char ini_get_bool_config_value(struct value_obj *vo,
                                        unsigned char def, int *error)
{
    const char *str;
    uint32_t len = 0;

    TRACE_FLOW_ENTRY();

    /* Do we have the vo ? */
    if (vo == NULL) {
        TRACE_ERROR_NUMBER("Invalid argument.", EINVAL);
        if (error) *error = EINVAL;
        return def;
    }

    if (error) *error = EOK;

    /* Get value - no error checking as we checked it above
     * and there is no other reson the function could fail.
     */
    value_get_concatenated(vo, &str);
    value_get_concatenated_len(vo, &len);

    /* Try to parse the value */
    if ((strncasecmp(str, "true", len) == 0) ||
        (strncasecmp(str, "yes", len) == 0)) {
        TRACE_FLOW_STRING("Returning", "true");
        return '\1';
    }
    else if ((strncasecmp(str, "false", len) == 0) ||
             (strncasecmp(str, "no", len) == 0)) {
        TRACE_FLOW_STRING("Returning", "false");
        return '\0';
    }

    TRACE_ERROR_STRING("Returning", "error");
    if (error) *error = EIO;
    return def;
}

/* Return a string out of the value */
char *ini_get_string_config_value(struct value_obj *vo,
                              int *error)
{
    const char *str = NULL;
    char *ret_str = NULL;

    TRACE_FLOW_ENTRY();

    /* Do we have the vo ? */
    if (vo == NULL) {
        TRACE_ERROR_NUMBER("Invalid argument.", EINVAL);
        if (error) *error = EINVAL;
        return NULL;
    }

    /* Get value - no error checking as we checked it above
     * and there is no other reson the function could fail.
     */
    value_get_concatenated(vo, &str);

    ret_str = strdup(str);
    if (ret_str == NULL) {
        TRACE_ERROR_NUMBER("Failed to allocate memory.", ENOMEM);
        if (error) *error = ENOMEM;
        return NULL;
    }

    if (error) *error = EOK;

    TRACE_FLOW_STRING("ini_get_string_config_value returning", str);
    return ret_str;
}

/* Get string from the value object */
const char *ini_get_const_string_config_value(struct value_obj *vo,
                                              int *error)
{
    const char *str;

    TRACE_FLOW_ENTRY();

    /* Do we have the vo ? */
    if (vo == NULL) {
        TRACE_ERROR_NUMBER("Invalid argument.", EINVAL);
        if (error) *error = EINVAL;
        return NULL;
    }

    /* Get value - no error checking as we checked it above
     * and there is no other reson the function could fail.
     */
    value_get_concatenated(vo, &str);

    if (error) *error = EOK;

    TRACE_FLOW_STRING("ini_get_const_string_config_value returning", str);
    return str;
}

/* A special hex format is assumed.
 * The string should be taken in single quotes
 * and consist of hex encoded value two hex digits per byte.
 * Example: '0A2BFECC'
 * Case does not matter.
 */
char *ini_get_bin_config_value(struct value_obj *vo,
                               int *length, int *error)
{
    int i;
    char *value = NULL;
    const char *buff;
    int size = 0;
    uint32_t len = 0;
    const char *str;

    TRACE_FLOW_ENTRY();

    /* Do we have the vo ? */
    if (vo == NULL) {
        TRACE_ERROR_NUMBER("Invalid argument.", EINVAL);
        if (error) *error = EINVAL;
        return NULL;
    }

    if (error) *error = EOK;

    /* Get value - no error checking as we checked it above
     * and there is no other reson the function could fail.
     */
    value_get_concatenated(vo, &str);

    /* Check the length */
    value_get_concatenated_len(vo, &len);
    if ((len%2) != 0) {
        TRACE_ERROR_STRING("Invalid length for binary data", "");
        if (error) *error = EINVAL;
        return NULL;
    }

    /* Is the format correct ? */
    if ((*str != '\'') ||
        (str[len -1] != '\'')) {
        TRACE_ERROR_STRING("String is not escaped","");
        if (error) *error = EIO;
        return NULL;
    }

    /* Check that all the symbols are ok */
    buff = str + 1;
    len -= 2;
    for (i = 0; i < len; i++) {
        if (!isxdigit(buff[i])) {
            TRACE_ERROR_STRING("Invalid encoding for binary data", buff);
            if (error) *error = EIO;
            return NULL;
        }
    }

    /* The value is good so we can allocate memory for it */
    value = malloc(len / 2);
    if (value == NULL) {
        TRACE_ERROR_NUMBER("Failed to allocate memory.", ENOMEM);
        if (error) *error = ENOMEM;
        return NULL;
    }

    /* Convert the value */
    for (i = 0; i < len; i += 2) {
        value[size] = (char)(16 * HEXVAL(buff[i]) + HEXVAL(buff[i+1]));
        size++;
    }

    if (error) *error = EOK;
    if (length) *length = size;
    TRACE_FLOW_STRING("ini_get_bin_config_value", "Exit");
    return value;
}

/* Function to free binary configuration value */
void ini_free_bin_config_value(char *value)
{
    free(value);
}
