/*
 * xstr.c - Extra String functions used by SiriDB.
 */
#include <assert.h>
#include <ctype.h>
#include <inttypes.h>
#include <logger/logger.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

void xstr_lower_case(char * sptr)
{
   for (; *sptr != '\0'; sptr++)
   {
       *sptr = tolower( (unsigned char) * sptr);
   }
}

void xstr_upper_case(char * sptr)
{
   for (; *sptr != '\0'; sptr++)
   {
       *sptr = toupper( (unsigned char) * sptr);
   }
}

void xstr_replace_char(char * sptr, char orig, char repl)
{
    for (; *sptr != '\0'; sptr++)
    {
        if (*sptr == orig)
        {
            *sptr = repl;
        }
    }
}

/*
 * Replace all occurrences of 'o' with 'r' in 'str'. We restrict the new size
 * to 'n'.
 *
 * Returns 0 if successful or -1 if the replaced string does not fit. In this
 * case the original string is untouched. The new string is terminated.
 */
int xstr_replace_str(char * str, char * o, char * r, size_t n)
{
    char buffer[n];
    char * pos, * s;
    size_t l, size = 0, olen = strlen(o), rlen = strlen(r);

    for (s = str; (pos = strstr(s, o)) != NULL; s = pos + olen)
    {
        l = pos - s;

        if (size + l + rlen >= n)
        {
            return -1;
        }

        memcpy(buffer + size, s, l);
        size += l;

        memcpy(buffer + size, r, rlen);
        size += rlen;

    }

    if (s != str)
    {
        memcpy(str, buffer, size);
        str[size] = '\0';
    }

    return 0;
}

/*
 * Split and then join a given string.
 *
 * For example:
 *      string: "  this  is a   test  "
 *      split: ' ' and join with '_'
 *      result: "this_is_a_test"
 */
void xstr_split_join(char * pt, char split_chr, char join_chr)
{
    int join = -1;
    char * dest = pt;

    for (; *pt != '\0'; pt++)
    {
        if (*pt != split_chr)
        {
            if (join > 0)
            {
                *dest = join_chr;
                dest++;
            }
            join = 0;
            *dest = *pt;
            dest++;
        }
        else if (!join)
        {
            join = 1;
        }
    }

    *dest = '\0';
}

void xstr_trim(char ** str, char chr)
{
    /*
     * trim: when chr is 0 we will trim whitespace,
     * otherwise only the given char.
     */
    char * end;

    /* trim leading chars  */
    while ((chr && **str == chr) || (!chr && isspace(**str)))
    {
        (*str)++;
    }

    /* check all chars?  */
    if(**str == 0)
    {
        return;
    }

    /* trim trailing chars  */
    end = *str + strlen(*str) - 1;
    while (end > *str &&
            ((chr && *end == chr) || (!chr && isspace(*end))))
    {
        end--;
    }

    /* write new null terminator  */
    *(end + 1) = 0;
}

/*
 * returns true or false
 */
bool xstr_is_empty(const char * str)
{
    const char * test = str;
    for (; *test; test++)
    {
        if (!isspace(*test))
        {
            return false;
        }
    }
    return true;
}

bool xstr_is_int(const char * str)
{
   /* Handle negative numbers.  */
   if (*str == '-')
   {
       ++str;
   }

   /* Handle empty string or just "-".  */
   if (!*str)
   {
       return false;
   }

   /* Check for non-digit chars in the rest of the string.  */
   while (*str)
   {
       if (!isdigit(*str))
       {
           return false;
       }
       else
       {
           ++str;
       }
   }

   return true;
}

bool xstr_is_float(const char * str)
{
   /* Handle negative numbers.   */
   if (*str == '-' || *str == '+')
   {
       ++str;
   }

   size_t dots = 0;

   /* Handle empty string or just "-".  */
   if (!*str)
   {
       return false;
   }

   /* Check for non-digit chars in the rest of the string.  */
   while (*str)
   {
       if (*str == '.')
       {
           ++dots;
       }
       else if (!isdigit(*str))
       {
           return false;
       }

       ++str;
   }

   return dots == 1;
}

bool xstr_is_graph(const char * str)
{
    for (; *str; str++)
    {
        if (!isgraph(*str))
        {
            return false;
        }
    }
    return true;
}

/*
 * This function is used to extra a SiriDB string. These strings start
 * with " or with ' and we should replace double this character in the
 * string with a single one.
 *
 * 'dest' string will be terminated and the return value is the new
 * length of 'dest'.
 */
size_t xstr_extract_string(char * dest, const char * source, size_t len)
{
    size_t i = 0;

    /* take the first character, this is " or ' */
    char chr = *source;

    /* we need to loop till len-2 so take 2 */
    for (len -= 2; i < len; i++)
    {
        source++;
        if (*source == chr)
        {
            /* this is the character, skip one and take the next */
            source++;
            len--;
        }
        dest[i] = *source;
    }

    /* set final 0 */
    dest[i] = 0;

    return i;
}

/*
 * Returns a string to double.
 * No error checking is done, we make the following assumptions:
 *      - len > 0
 *      - string is allowed to have one dot (.) at most but not required
 *      - string can start with a plus (+) or minus (-) sign.
 */
double xstr_to_double(const char * str)
{
    double d;
    double negative = 0;

    switch (*str)
    {
    case '-':
        negative = -1.0;
        ++str;
        break;
    case '+':
        ++str;
        break;
    }

    if (*str == 'i')
        return negative ? negative * INFINITY : INFINITY;

    if (*str == 'n')
        return NAN;

    if (errno == ERANGE)
        errno = 0;

    d = strtod(str, NULL);

    if (errno == ERANGE)
    {
        assert (d == HUGE_VAL || d == -HUGE_VAL);

        d = d == HUGE_VAL ? INFINITY : -INFINITY;
        errno = 0;
    }

    return negative ? negative * d : d;
}

/*
 * Returns a string to uint64_t.
 * No error checking is done, we make the following assumptions:
 *      - len > 0
 *      - string can only contain characters [0..9] and no signs
 */
uint64_t xstr_to_uint64(const char * src, size_t len)
{
    char * pt = (char *) src;

    uint64_t i = *pt - '0';

    while (--len && isdigit(*(++pt)))
    {
        i = 10 * i + *pt - '0';
    }

    return i;
}

/*
 * Returns a string duplicate like strdup() and set the strlen() to n;
 */
char * xstr_dup(const char * src, size_t * n)
{
    *n = strlen(src);
    char * nstr = malloc(*n + 1);
    if (nstr)
    {
        memcpy(nstr, src, *n + 1);
    }
    return nstr;
}
