/* DAP String Functions */
#ifdef _WIN32
//#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#endif
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include "dap_common.h"
#include "dap_strfuncs.h"

#ifdef _WIN32
static inline char *strndup(char *str, int len) {
    char *buf = (char*)malloc(len + 1);
    memcpy(buf, str, len);
    buf[len] = 0;
    return buf;
}
#endif

#ifdef _WIN32

char *strptime( char *buff, const char *fmt, struct tm *tm )
{
  uint32_t len = strlen( buff );

  dap_sscanf( buff,"%u.%u.%u_%u.%u.%u",&tm->tm_year, &tm->tm_mon, &tm->tm_mday, &tm->tm_hour, &tm->tm_min, &tm->tm_sec );

  tm->tm_year += 2000;

  return buff + len;
}

#endif


/**
 * dap_strlen:
 * @a_str: (nullable): the string
 *
 * If @a_str is %NULL it returns 0
 *
 * Returns: length of the string
 */
size_t dap_strlen(const char *a_str)
{
    size_t l_length = 0;

    if(a_str) {
        l_length = strlen(a_str);
    }
    return l_length;
}

/**
 * dap_strcmp:
 * @a_str1: (nullable): the string
 * @a_str2: (nullable): the string
 *
 * Compare a_str1 and a_str2
 */
int dap_strcmp(const char *a_str1, const char *a_str2)
{
    if(a_str1 && a_str2) {
        return strcmp(a_str1, a_str2);
    }
    return -1;
}

/**
 * dap_strcmp:
 * @a_str1: (nullable): the string
 * @a_str2: (nullable): the string
 *
 * Compare a_n characters of a_str1 and a_str2
 */
int dap_strncmp(const char *a_str1, const char *a_str2, size_t a_n)
{
    if(a_str1 && a_str2) {
        return strncmp(a_str1, a_str2, a_n);
    }
    return -1;
}

/**
 * dap_strdup:
 * @a_str: (nullable): the string to duplicate
 *
 * Duplicates a string. If @a_str is %NULL it returns %NULL.
 * The returned string should be freed
 * when no longer needed.
 *
 * Returns: a newly-allocated copy of @a_str
 */
char* dap_strdup(const char *a_str)
{
    char *l_new_str;
    size_t l_length;

    if(a_str)
    {
        l_length = (size_t) (strlen(a_str) + 1);
        l_new_str = DAP_NEW_SIZE(char, l_length);
        memcpy(l_new_str, a_str, l_length);
    }
    else
        l_new_str = NULL;

    return l_new_str;
}

/**
 * dap_strdup_vprintf:
 * @a_format: a standard printf() format string, but notice
 *     [string precision pitfalls][string-precision]
 * @a_args: the list of parameters to insert into the format string
 *
 * Similar to the standard C vsprintf() function but safer, since it
 * calculates the maximum space required and allocates memory to hold
 * the result. The returned string should be freed with DAP_DELETE()
 * when no longer needed.
 *
 * Returns: a newly-allocated string holding the result
 */
char* dap_strdup_vprintf(const char *a_format, va_list a_args)
{
    char *l_string = NULL;
    int len = dap_vasprintf(&l_string, a_format, a_args);
    if(len < 0)
        l_string = NULL;
    return l_string;
}

/**
 * dap_strdup_printf:
 * @a_format: a standard printf() format string
 *
 * Similar to the standard C sprintf() function but safer, since it
 * calculates the maximum space required and allocates memory to hold
 * the result. The returned string should be freed with DAP_DELETE()
 * when no longer needed.
 *
 * Returns: a newly-allocated string holding the result
 */
char* dap_strdup_printf(const char *a_format, ...)
{
    char *l_buffer;
    va_list l_args;

    va_start(l_args, a_format);
    l_buffer = dap_strdup_vprintf(a_format, l_args);
    va_end(l_args);

    return l_buffer;
}

/*
 // alternative version
 char* dap_strdup_printf2(const char *a_format, ...)
 {
 size_t l_buffer_size = 0;
 char *l_buffer = NULL;
 va_list l_args;

 va_start(l_args, a_format);
 l_buffer_size += vsnprintf(l_buffer, 0, a_format, l_args);
 va_end(l_args);

 if(!l_buffer_size)
 return NULL;
 l_buffer = DAP_NEW_SIZE(char, l_buffer_size + 1);

 va_start(l_args, a_format);
 vsnprintf(l_buffer, l_buffer_size + 1, a_format, l_args);
 va_end(l_args);

 return l_buffer;
 }*/

/**
 * dap_stpcpy:
 * @a_dest: destination buffer.
 * @a_src: source string.
 *
 * Copies a null-terminated string into the dest buffer, include the
 * trailing null, and return a pointer to the trailing null byte.
 * This is useful for concatenating multiple strings together
 * without having to repeatedly scan for the end.
 *
 * Returns: a pointer to trailing null byte.
 **/
char* dap_stpcpy(char *a_dest, const char *a_src)
{
    char *l_d = a_dest;
    const char *l_s = a_src;

    dap_return_val_if_fail(a_dest != NULL, NULL);
    dap_return_val_if_fail(a_src != NULL, NULL);
    do
        *l_d++ = *l_s;
    while(*l_s++ != '\0');

    return l_d - 1;
}

/**
 * dap_strstr_len:
 * @a_haystack: a string
 * @a_haystack_len: the maximum length of @a_haystack. Note that -1 is
 *     a valid length, if @a_haystack is null-terminated, meaning it will
 *     search through the whole string.
 * @a_needle: the string to search for
 *
 * Searches the string @a_haystack for the first occurrence
 * of the string @a_needle, limiting the length of the search
 * to @a_haystack_len.
 *
 * Returns: a pointer to the found occurrence, or
 *    %NULL if not found.
 */
char* dap_strstr_len(const char *a_haystack, ssize_t a_haystack_len, const char *a_needle)
{
    dap_return_val_if_fail(a_haystack != NULL, NULL);
    dap_return_val_if_fail(a_needle != NULL, NULL);

    if(a_haystack_len < 0)
        return strstr(a_haystack, a_needle);
    else
    {
        const char *l_p = a_haystack;
        ssize_t l_needle_len = (ssize_t) strlen(a_needle);
        const char *l_end;
        ssize_t l_i;

        if(l_needle_len == 0)
            return (char *) a_haystack;

        if(a_haystack_len < l_needle_len)
            return NULL;

        l_end = a_haystack + a_haystack_len - l_needle_len;

        while(l_p <= l_end && *l_p)
        {
            for(l_i = 0; l_i < l_needle_len; l_i++)
                if(l_p[l_i] != a_needle[l_i])
                    goto next;

            return (char *) l_p;

            next:
            l_p++;
        }

        return NULL;
    }
}

/**
 * dap_strjoinv:
 * @a_separator: (allow-none): a string to insert between each of the
 *     strings, or %NULL
 * @a_str_array: a %NULL-terminated array of strings to join
 *
 * Joins a number of strings together to form one long string, with the
 * optional @separator inserted between each of them. The returned string
 * should be freed.
 *
 * If @str_array has no items, the return value will be an
 * empty string. If @a_str_array contains a single item, @a_separator will not
 * appear in the resulting string.
 *
 * Returns: a newly-allocated string containing all of the strings joined
 *     together, with @a_separator between them
 */
char* dap_strjoinv(const char *a_separator, char **a_str_array)
{
    char *l_string;
    char *l_ptr;

    dap_return_val_if_fail(a_str_array != NULL, NULL);

    if(a_separator == NULL)
        a_separator = "";

    if(*a_str_array)
    {
        int l_i;
        size_t l_len;
        size_t l_separator_len;

        l_separator_len = strlen(a_separator);
        /* First part, getting length */
        l_len = 1 + strlen(a_str_array[0]);
        for(l_i = 1; a_str_array[l_i] != NULL; l_i++)
            l_len += strlen(a_str_array[l_i]);
        l_len += l_separator_len * (l_i - 1);

        /* Second part, building string */
        l_string = DAP_NEW_SIZE(char, l_len);
        l_ptr = dap_stpcpy(l_string, *a_str_array);
        for(l_i = 1; a_str_array[l_i] != NULL; l_i++)
                {
            l_ptr = dap_stpcpy(l_ptr, a_separator);
            l_ptr = dap_stpcpy(l_ptr, a_str_array[l_i]);
        }
    }
    else
        l_string = dap_strdup("");

    return l_string;
}

/**
 * dap_strjoin:
 * @a_separator: (allow-none): a string to insert between each of the
 *     strings, or %NULL
 * @...: a %NULL-terminated list of strings to join
 *
 * Joins a number of strings together to form one long string, with the
 * optional @a_separator inserted between each of them. The returned string
 * should be freed.
 *
 * Returns: a newly-allocated string containing all of the strings joined
 *     together, with @a_separator between them
 */
char* dap_strjoin(const char *a_separator, ...)
{
    char *string, *l_s;
    va_list l_args;
    size_t l_len;
    size_t l_separator_len;
    char *l_ptr;

    if(a_separator == NULL)
        a_separator = "";

    l_separator_len = strlen(a_separator);

    va_start(l_args, a_separator);

    l_s = va_arg(l_args, char*);

    if(l_s)
    {
        /* First part, getting length */
        l_len = 1 + strlen(l_s);

        l_s = va_arg(l_args, char*);
        while(l_s)
        {
            l_len += l_separator_len + strlen(l_s);
            l_s = va_arg(l_args, char*);
        }
        va_end(l_args);

        /* Second part, building string */
        string = DAP_NEW_SIZE(char, l_len);

        va_start(l_args, a_separator);

        l_s = va_arg(l_args, char*);
        l_ptr = dap_stpcpy(string, l_s);

        l_s = va_arg(l_args, char*);
        while(l_s)
        {
            l_ptr = dap_stpcpy(l_ptr, a_separator);
            l_ptr = dap_stpcpy(l_ptr, l_s);
            l_s = va_arg(l_args, char*);
        }
    }
    else
        string = dap_strdup("");

    va_end(l_args);

    return string;
}

typedef struct _dap_slist dap_slist;

struct _dap_slist
{
    void* data;
    dap_slist *next;
};

static dap_slist* dap_slist_prepend(dap_slist *a_list, void* a_data)
{
    dap_slist *l_new_list;

    l_new_list = DAP_NEW_Z(dap_slist);
    l_new_list->data = a_data;
    l_new_list->next = a_list;

    return l_new_list;
}

static void dap_slist_free(dap_slist *a_list)
{
    if(a_list)
    {
        dap_slist *l_cur_node;
        dap_slist *l_last_node = a_list;
        while(l_last_node)
        {
            l_cur_node = l_last_node;
            l_last_node = l_last_node->next;
            DAP_DELETE(l_cur_node);
        }
    }
}

/**
 * dap_strsplit:
 * @a_string: a string to split
 * @a_delimiter: a string which specifies the places at which to split
 *     the string. The delimiter is not included in any of the resulting
 *     strings, unless @a_max_tokens is reached.
 * @a_max_tokens: the maximum number of pieces to split @a_string into.
 *     If this is less than 1, the string is split completely.
 *
 * Splits a string into a maximum of @a_max_tokens pieces, using the given
 * @a_delimiter. If @a_max_tokens is reached, the remainder of @a_string is
 * appended to the last token.
 *
 * As an example, the result of dap_strsplit (":a:bc::d:", ":", -1) is a
 * %NULL-terminated vector containing the six strings "", "a", "bc", "", "d"
 * and "".
 *
 * As a special case, the result of splitting the empty string "" is an empty
 * vector, not a vector containing a single string. The reason for this
 * special case is that being able to represent a empty vector is typically
 * more useful than consistent handling of empty elements. If you do need
 * to represent empty elements, you'll need to check for the empty string
 * before calling dap_strsplit().
 *
 * Returns: a newly-allocated %NULL-terminated array of strings.
 * Use dap_strfreev() to free it.
 */
char** dap_strsplit(const char *a_string, const char *a_delimiter, int a_max_tokens)
{
    dap_slist *l_string_list = NULL, *l_slist;
    char **l_str_array, *l_s;
    uint32_t l_n = 1;

    dap_return_val_if_fail(a_string != NULL, NULL);
    dap_return_val_if_fail(a_delimiter != NULL, NULL);

    if(a_max_tokens < 1)
        a_max_tokens = INT_MAX;

    l_s = strstr(a_string, a_delimiter);
    if(l_s)
    {
        uint32_t delimiter_len = (uint32_t) strlen(a_delimiter);

        do
        {
            uint32_t len;
            char *new_string;

            len = (uint32_t) (l_s - a_string);
            new_string = DAP_NEW_SIZE(char, len + 1);
            strncpy(new_string, a_string, len);
            new_string[len] = 0;
            l_string_list = dap_slist_prepend(l_string_list, new_string);
            l_n++;
            a_string = l_s + delimiter_len;
            l_s = strstr(a_string, a_delimiter);
        }
        while(--a_max_tokens && l_s);
    }
    l_string_list = dap_slist_prepend(l_string_list, dap_strdup(a_string));

    l_str_array = DAP_NEW_SIZE(char*, (l_n + 1) * sizeof(char*));

    l_str_array[l_n--] = NULL;
    for(l_slist = l_string_list; l_slist; l_slist = l_slist->next)
        l_str_array[l_n--] = l_slist->data;

    dap_slist_free(l_string_list);

    return l_str_array;
}

size_t dap_str_countv(char **a_str_array)
{
    size_t l_i = 0;
    if(a_str_array)
    {
        for(l_i = 0; a_str_array[l_i] != NULL; l_i++)
            ;
    }
    return l_i;
}

/**
 * dap_strdupv:
 * @a_str_array: (nullable): a %NULL-terminated array of strings
 *
 * Copies %NULL-terminated array of strings. The copy is a deep copy;
 * the new array should be freed by first freeing each string, then
 * the array itself. g_strfreev() does this for you. If called
 * on a %NULL value, g_strdupv() simply returns %NULL.
 *
 * Returns: (nullable): a new %NULL-terminated array of strings.
 */
char** dap_strdupv(const char **a_str_array)
{
    if(a_str_array)
    {
        int l_i;
        char **l_retval;

        l_i = 0;
        while(a_str_array[l_i])
            ++l_i;

        l_retval = DAP_NEW_SIZE(char*, (l_i + 1) * sizeof(char*));

        l_i = 0;
        while(a_str_array[l_i])
        {
            l_retval[l_i] = dap_strdup(a_str_array[l_i]);
            ++l_i;
        }
        l_retval[l_i] = NULL;

        return l_retval;
    }
    else
        return NULL;
}

/**
 * dap_strfreev:
 * @a_str_array: (nullable): a %NULL-terminated array of strings to free
 *
 * Frees a %NULL-terminated array of strings, as well as each
 * string it contains.
 *
 * If @a_str_array is %NULL, this function simply returns.
 */
void dap_strfreev(char **a_str_array)
{
    if(a_str_array)
    {
        int l_i;
        for(l_i = 0; a_str_array[l_i] != NULL; l_i++)
            DAP_DELETE(a_str_array[l_i]);

        DAP_DELETE(a_str_array);
    }
}

/**
 * dap_strchug:
 * @a_string: a string to remove the leading whitespace from
 *
 * Removes leading whitespace from a string, by moving the rest
 * of the characters forward.
 *
 * This function doesn't allocate or reallocate any memory;
 * it modifies @a_string in place. Therefore, it cannot be used on
 * statically allocated strings.
 *
 * The pointer to @a_string is returned to allow the nesting of functions.
 * Returns: @a_string
 */
char* dap_strchug(char *a_string)
{
    unsigned char *l_start;

    dap_return_val_if_fail(a_string != NULL, NULL);

    for(l_start = (unsigned char*) a_string; *l_start && dap_ascii_isspace(*l_start); l_start++)
        ;

    memmove(a_string, l_start, strlen((char *) l_start) + 1);

    return a_string;
}

/**
 * dap_strchomp:
 * @a_string: a string to remove the trailing whitespace from
 *
 * Removes trailing whitespace from a string.
 *
 * This function doesn't allocate or reallocate any memory;
 * it modifies @a_string in place. Therefore, it cannot be used
 * on statically allocated strings.
 *
 * The pointer to @a_string is returned to allow the nesting of functions.
 *
 * Returns: @a_string
 */
char* dap_strchomp(char *a_string)
{
    size_t l_len;

    dap_return_val_if_fail(a_string != NULL, NULL);

    l_len = (size_t) strlen(a_string);
    while(l_len--)
    {
        if(dap_ascii_isspace((unsigned char ) a_string[l_len]))
            a_string[l_len] = '\0';
        else
            break;
    }
    return a_string;
}

/**
 * dap_strup:
 * @a_str: a string
 * @a_len: length of @a_str in bytes, or -1 if @a_str is nul-terminated
 *
 * Converts all lower case ASCII letters to upper case ASCII letters.
 *
 * Returns: a newly allocated string, with all the lower case
 *     characters in @a_str converted to upper case
 */
char* dap_strup(const char *a_str, ssize_t a_len)
{
    char *l_result, *l_s;

    dap_return_val_if_fail(a_str != NULL, NULL);

    if(a_len < 0)
        a_len = strlen(a_str);

    l_result = strndup(a_str, a_len);
    for(l_s = l_result; *l_s; l_s++)
        *l_s = toupper(*l_s);

    return l_result;
}

/**
 * dap_strdown:
 * @a_str: a string
 * @a_len: length of @a_str in bytes, or -1 if @a_str is nul-terminated
 *
 * Converts all upper case ASCII letters to lower case ASCII letters.
 *
 * Returns: a newly-allocated string, with all the upper case
 *     characters in @a_str converted to lower case
 */
char* dap_strdown(const char *a_str, ssize_t a_len)
{
    char *l_result, *l_s;

    dap_return_val_if_fail(a_str != NULL, NULL);

    if(a_len < 0)
        a_len = strlen(a_str);

    l_result = strndup(a_str, a_len);
    for(l_s = l_result; *l_s; l_s++)
        *l_s = tolower(*l_s);

    return l_result;
}

/**
 * dap_strreverse:
 * @a_string: the string to reverse
 *
 * Reverses all of the bytes in a string. For example,
 * `dap_strreverse("abcdef")` will result in "fedcba".
 *
 * Note that g_strreverse() doesn't work on UTF-8 strings
 * containing multibyte characters.
 *
 * Returns: the same pointer passed in as @a_string
 */
char* dap_strreverse(char *a_string)
{
    dap_return_val_if_fail(a_string != NULL, NULL);

    if(*a_string)
    {
        register char *l_h, *l_t;

        l_h = a_string;
        l_t = a_string + strlen(a_string) - 1;

        while(l_h < l_t)
        {
            register char l_c;

            l_c = *l_h;
            *l_h = *l_t;
            l_h++;
            *l_t = l_c;
            l_t--;
        }
    }

    return a_string;
}

/**
 * dap_usleep:
 * @a_microseconds: number of microseconds to pause
 *
 * Pauses the current thread for the given number of microseconds.
 */
void dap_usleep(time_t a_microseconds)
{
#ifdef _WIN32
    Sleep (a_microseconds / 1000);
#else
    struct timespec l_request, l_remaining;
    l_request.tv_sec = a_microseconds / DAP_USEC_PER_SEC;
    l_request.tv_nsec = 1000 * (a_microseconds % DAP_USEC_PER_SEC);
    while(nanosleep(&l_request, &l_remaining) == -1 && errno == EINTR)
        l_request = l_remaining;
#endif
}