/********************** String Operations Utility *****************************
This file is part of the PDI stack.

This is free software. You can redistribute it and/or modify it but without any
warranty.

Author          : Suraj I.
Created Date    : 1st June 2019
******************************************************************************/

#include "DataTypeConversions.h"
#include "StringOperations.h"

/**
 * @brief Finds the first occurrence of a substring in a string.
 * 
 * This function searches for the first occurrence of a substring within a given string.
 * If the substring is found, it returns the index of the first occurrence; otherwise, it returns -1.
 *
 * @param str The main string to search in.
 * @param substr The substring to search for.
 * @param _len The maximum length to search (default is 300).
 * @return The index of the first occurrence of the substring, or -1 if not found.
 */
int __strstr(const char *str, const char *substr, int _len)
{
    if (nullptr == str || nullptr == substr || 0 == strlen(str) || 0 == strlen(substr))
    {
        return -1;
    }

    int n = 0;

    while (*(str+n) && n < _len)
    {
        char *pattern = (char *)substr;

        int p = 0;
        while (*(str+n+p) && *pattern && *(str+n+p) == *pattern)
        {
            p++;
            pattern++;
        }

        if (!*pattern)
            return n;

        n++;
    }

    return -1;
}

/**
 * @brief Trims a specific character from both ends of a string.
 * 
 * This function removes occurrences of a specified character from the beginning and end of a string.
 *
 * @param str The string to trim.
 * @param _val The character to trim.
 * @param _overflow_limit The maximum length of the string (default is 300).
 * @return A pointer to the trimmed string.
 */
char *__strtrim_val(char *str, char _val, uint16_t _overflow_limit)
{
    if (nullptr == str)
    {
        return nullptr;
    }

    uint16_t n = 0;
    uint16_t len = strlen(str);

    if (0 == len)
    {
        return nullptr;
    }

    while (*str && n < _overflow_limit)
    {
        char *_begin = str;

        while (*_begin && *_begin == _val && n < len && n < _overflow_limit)
        {
            _begin++;
            n++;
        }
        while (*(str + len - 1) && len > 0 && n < _overflow_limit)
        {
            if (*(str + len - 1) == _val)
            {
                *(str + len - 1) = 0;
                n++;
                len--;
            }
            else
            {
                break;
            }
        }

        return _begin;
    }

    return nullptr;
}

/**
 * @brief Trims whitespace from both ends of a string.
 * 
 * This function removes leading and trailing whitespace from a string.
 *
 * @param str The string to trim.
 * @param _overflow_limit The maximum length of the string (default is 300).
 * @return A pointer to the trimmed string.
 */
char *__strtrim(char *str, uint16_t _overflow_limit)
{
    return __strtrim_val(str, ' ', _overflow_limit);
}

/**
 * @brief Compares two strings for equality.
 * 
 * This function checks whether two strings are equal.
 *
 * @param str1 The first string to compare.
 * @param str2 The second string to compare.
 * @param _overflow_limit The maximum length of the strings (default is 300).
 * @return True if the strings are equal, false otherwise.
 */
bool __are_str_equals(const char *str1, const char *str2, uint16_t _overflow_limit)
{
    if (nullptr == str1 || nullptr == str2)
    {
        return false;
    }

    uint16_t len = strlen(str1);
    if (len != strlen(str2))
    {
        return false;
    }

    for (uint16_t i = 0; i < len && i < _overflow_limit; i++)
    {
        if (str1[i] != str2[i])
        {
            return false;
        }
    }
    return true;
}

/**
 * @brief Compares two character arrays for equality.
 * 
 * This function checks whether two character arrays are equal.
 *
 * @param array1 The first array to compare.
 * @param array2 The second array to compare.
 * @param len The length of the arrays to compare.
 * @return True if the arrays are equal, false otherwise.
 */
bool __are_arrays_equal(const char *array1, const char *array2, uint16_t len)
{
    if (nullptr == array1 || nullptr == array2)
    {
        return false;
    }

    for (uint16_t i = 0; i < len; i++)
    {
        if (array1[i] != array2[i])
        {
            return false;
        }
    }
    return true;
}

/**
 * @brief Appends an unsigned integer to a string buffer using a specified format.
 * 
 * This function appends a formatted unsigned integer to a string buffer.
 *
 * @param _str The string buffer to append to.
 * @param _format The format string (e.g., "%u").
 * @param _value The unsigned integer value to append.
 * @param _len The maximum length of the string buffer.
 */
void __appendUintToBuff(char *_str, const char *_format, uint32_t _value, int _len)
{
    if (nullptr == _str || nullptr == _format) return;

    char value[20];
    memset(value, 0, sizeof(value));
    int pos = 0;
    const char *p = _format;

    // Copy literal prefix up to the '%' specifier.
    while (*p && *p != '%' && pos < (int)sizeof(value) - 1) {
        value[pos++] = *p++;
    }

    // Substitute the %d / %u specifier with the decimal form of _value.
    // (Callers of this helper only ever use a single integer placeholder.)
    if (*p == '%') {
        p++;
        if (*p == 'd' || *p == 'u') {
            p++;
            char num[15];
            Uint32ToString(_value, num, sizeof(num));
            int numlen = (int)strlen(num);
            if (pos + numlen > (int)sizeof(value) - 1) numlen = (int)sizeof(value) - 1 - pos;
            if (numlen > 0) {
                memcpy(value + pos, num, numlen);
                pos += numlen;
            }
        }
    }

    // Copy any literal suffix after the specifier.
    while (*p && pos < (int)sizeof(value) - 1) {
        value[pos++] = *p++;
    }
    value[pos] = '\0';

    strncat(_str, value, _len);
}

/**
 * @brief Converts an IP address from integer format to string format.
 * 
 * This function converts an IP address represented as an array of 4 bytes into a string.
 *
 * @param _str The string buffer to store the IP address.
 * @param _ip The IP address in integer format (4 bytes).
 * @param _len The maximum length of the string buffer (default is 15).
 */
void __int_ip_to_str(char *_str, uint8_t *_ip, int _len)
{
    if (nullptr == _str || nullptr == _ip || _len <= 0) return;
    memset(_str, 0, _len);

    int pos = 0;
    for (int i = 0; i < 4; i++) {
        char num[4]; // max "255" + NUL
        Uint32ToString((uint32_t)_ip[i], num, sizeof(num));
        int n = (int)strlen(num);
        if (pos + n > _len - 1) n = _len - 1 - pos;
        if (n > 0) { memcpy(_str + pos, num, n); pos += n; }
        if (i < 3 && pos < _len - 1) _str[pos++] = '.';
    }
    _str[pos] = '\0';
}

/**
 * Helper: copy `src_len` bytes from `src` into `str` at `pos`, bounded by
 * (size - 1) so a trailing NUL slot is always preserved. Returns the new pos.
 */
static int __snprintf_copy(char *str, int size, int pos, const char *src, int src_len)
{
    if (pos >= size - 1 || src_len <= 0) return pos;
    int room = size - 1 - pos;
    int n = (src_len < room) ? src_len : room;
    memcpy(str + pos, src, n);
    return pos + n;
}

int __vsnprintf(char *str, int size, const char *format, va_list args)
{
    if (nullptr == str || size <= 0 || nullptr == format) return 0;

    int pos = 0;
    const char *p = format;

    while (*p && pos < size - 1) {
        if (*p != '%') {
            str[pos++] = *p++;
            continue;
        }

        // Consume '%'
        p++;

        // Flags: '0' (zero-pad) and '-' (left-justify)
        bool zero_pad = false;
        bool left_justify = false;
        while (*p == '0' || *p == '-') {
            if (*p == '0') zero_pad = true;
            else left_justify = true;
            p++;
        }

        // Width (decimal digits)
        int width = 0;
        while (*p >= '0' && *p <= '9') {
            width = width * 10 + (*p - '0');
            p++;
        }

        // Length modifier 'l'
        bool is_long = false;
        if (*p == 'l') { is_long = true; p++; }

        char tmp[32];
        int tmplen = 0;
        bool valid_spec = true;

        switch (*p) {
            case 'd':
            case 'i': {
                int32_t v = is_long ? (int32_t)va_arg(args, long)
                                    : (int32_t)va_arg(args, int);
                Int32ToString(v, tmp, sizeof(tmp));
                tmplen = (int)strlen(tmp);
                p++;
                break;
            }
            case 'u': {
                uint32_t v = is_long ? (uint32_t)va_arg(args, unsigned long)
                                     : (uint32_t)va_arg(args, unsigned int);
                Uint32ToString(v, tmp, sizeof(tmp));
                tmplen = (int)strlen(tmp);
                p++;
                break;
            }
            case 'x':
            case 'X': {
                bool cap = (*p == 'X');
                uint32_t v = is_long ? (uint32_t)va_arg(args, unsigned long)
                                     : (uint32_t)va_arg(args, unsigned int);
                Uint32ToHexString(v, tmp, sizeof(tmp), cap);
                tmplen = (int)strlen(tmp);
                // Zero-pad makes no sense for negative sign here; hex is unsigned.
                p++;
                break;
            }
            case 'f': {
                double v = va_arg(args, double);
                FloatToString(v, tmp, sizeof(tmp));
                tmplen = (int)strlen(tmp);
                p++;
                break;
            }
            case 's': {
                const char *s = va_arg(args, const char *);
                int slen = 0;
                if (nullptr != s) {
                    while (s[slen]) slen++;
                }
                // Apply width directly into the output for strings (no tmp copy).
                if (!left_justify && width > slen) {
                    for (int i = 0; i < width - slen && pos < size - 1; i++) {
                        str[pos++] = ' ';
                    }
                }
                for (int i = 0; i < slen && pos < size - 1; i++) {
                    str[pos++] = s[i];
                }
                if (left_justify && width > slen) {
                    for (int i = 0; i < width - slen && pos < size - 1; i++) {
                        str[pos++] = ' ';
                    }
                }
                p++;
                valid_spec = false; // handled inline, skip the tmp-based copy below
                break;
            }
            case 'c': {
                char c = (char)va_arg(args, int);
                tmp[0] = c;
                tmp[1] = '\0';
                tmplen = 1;
                p++;
                break;
            }
            case '%': {
                tmp[0] = '%';
                tmp[1] = '\0';
                tmplen = 1;
                p++;
                break;
            }
            default: {
                // Unrecognised specifier — echo it so it's visible.
                if (pos < size - 1) str[pos++] = '%';
                if (is_long && pos < size - 1) str[pos++] = 'l';
                if (*p && pos < size - 1) { str[pos++] = *p; p++; }
                valid_spec = false;
                break;
            }
        }

        if (!valid_spec) continue;

        // Apply width padding to numeric/char/literal-% conversions.
        if (width > tmplen && tmplen < (int)sizeof(tmp) - 1) {
            int shift = width - tmplen;
            if (tmplen + shift > (int)sizeof(tmp) - 1) shift = (int)sizeof(tmp) - 1 - tmplen;

            if (left_justify) {
                // Append spaces after the value (zero-pad ignored when left-justifying).
                for (int i = 0; i < shift; i++) tmp[tmplen + i] = ' ';
                tmplen += shift;
                tmp[tmplen] = '\0';
            } else {
                char pad = zero_pad ? '0' : ' ';
                if (zero_pad && tmplen > 0 && tmp[0] == '-') {
                    // Keep the '-' first, pad zeros between it and the digits.
                    memmove(tmp + 1 + shift, tmp + 1, tmplen - 1);
                    for (int i = 0; i < shift; i++) tmp[1 + i] = '0';
                } else {
                    memmove(tmp + shift, tmp, tmplen);
                    for (int i = 0; i < shift; i++) tmp[i] = pad;
                }
                tmplen += shift;
                tmp[tmplen] = '\0';
            }
        }

        pos = __snprintf_copy(str, size, pos, tmp, tmplen);
    }

    str[pos] = '\0';
    return pos;
}

int __snprintf(char *str, int size, const char *format, ...)
{
    va_list args;
    va_start(args, format);
    int r = __vsnprintf(str, size, format, args);
    va_end(args);
    return r;
}

int __sprintf(char *str, const char *format, ...)
{
    va_list args;
    va_start(args, format);
    // No size bound — callers are responsible for buffer sizing. Pass a large
    // value so __vsnprintf's internal "size - 1" bookkeeping still works.
    int r = __vsnprintf(str, INT32_MAX, format, args);
    va_end(args);
    return r;
}

/**
 * @brief Converts an IP address from string format to integer format.
 * 
 * This function converts an IP address represented as a string into an array of 4 bytes.
 *
 * @param _str The IP address in string format.
 * @param _ip The buffer to store the IP address in integer format (4 bytes).
 * @param _len The maximum length of the string (default is 15).
 * @param _clear_str_after_done If true, clears the string after conversion (default is true).
 */
void __str_ip_to_int(char *_str, uint8_t *_ip, int _len, bool _clear_str_after_done)
{
    _ip[0] = StringToUint8(_str, 3);
    for (uint8_t i = 0, _ip_index = 1; i < strlen(_str) && i < _len; i++)
    {
        if (_str[i] == '.' && _ip_index < 4)
        {
            _ip[_ip_index++] = StringToUint8(&_str[i + 1], 3);
        }
    }
    if (_clear_str_after_done)
    {
        memset(_str, 0, _len);
    }
}

/**
 * @brief Finds and replaces a substring in a string.
 * 
 * This function searches for a substring in a string and replaces it with another substring.
 *
 * @param _str The main string to modify.
 * @param _find_str The substring to find.
 * @param _replace_with The substring to replace with.
 * @param _occurence The number of occurrences to replace.
 */
void __find_and_replace(char *_str, const char *_find_str, const char *_replace_with, int _occurence)
{
    if (nullptr == _str || nullptr == _find_str || nullptr == _replace_with)
    {
        return;
    }

    int _str_len = strlen(_str);
    int _find_str_len = strlen(_find_str);
    int _replace_str_len = strlen(_replace_with);

    int _total_len = _str_len + (_replace_str_len * _occurence) - (_occurence * _find_str_len) + 1;
    _total_len = pdistd::max(_str_len, _total_len) + 1;
    char *_buf = new char[_total_len];

    if (nullptr == _buf)
    {
        return;
    }

    memset(_buf, 0, _total_len);

    int j = 0, o = 0;
    for (; j < _str_len && o < _occurence;)
    {
        int _occur_index = __strstr(&_str[j], _find_str, _str_len-j);
        if (_occur_index >= 0)
        {
            strncat(_buf, &_str[j], _occur_index);
            strncat(_buf, _replace_with, _replace_str_len);
            j += _occur_index + _find_str_len;
            o++;
        }
        else
        {
            break;
        }
    }

    if (j < _str_len)
        strncat(_buf, &_str[j], (_str_len - j));
    int _fin_len = _str_len - (o * _find_str_len) + (o * _replace_str_len);

    if (strlen(_buf) > 0 && o > 0 && _str_len > _fin_len)
    {
        memset(_str, 0, _str_len);
        memcpy(_str, _buf, _fin_len + 1);
    }
    delete[] _buf;
}

/**
 * @brief Extracts a value from a JSON string based on a key.
 * 
 * This function parses a JSON string and retrieves the value associated with a specified key.
 *
 * @param _str The JSON string to parse.
 * @param _key The key to search for.
 * @param _value The buffer to store the extracted value.
 * @param _max_value_len The maximum length of the value buffer.
 * @return True if the key-value pair was found, false otherwise.
 */
bool __get_from_json(const char *_str, const char *_key, char *_value, int _max_value_len)
{
    if (nullptr == _str || nullptr == _key || nullptr == _value || _max_value_len <= 0)
    {
        return false;
    }

    int _str_len = strlen(_str);

    int _key_index = __strstr(_str, _key, _str_len);
    if (_key_index < 0 || _key_index >= _str_len)
        return false;

    int _colon_index = __strstr(_str + _key_index, ":", (_str_len - _key_index));
    if (_colon_index < 0)
        return false;

    const char* pos = _str + _key_index + _colon_index + 1; // skip colon
    while ((*pos == ' ' || *pos == '\t' || *pos == '\n') && (pos - _str) < _str_len ) pos++; // skip whitespace

    int quotes = 0, braces = 0, brackets = 0;
    const char* start = pos;
    const char* end = pos;

    while (*end) {
        char c = *end;

        if (c == '"') {

            // Count consecutive backslashes before this quote
            int backslashes = 0;
            const char* tmp = end - 1;
            while (tmp >= start && *tmp == '\\') {
                backslashes++;
                tmp--;
            }
            // If even number of backslashes → quote is not escaped
            if (backslashes % 2 == 0) {
                quotes ^= 1; // toggle inside/outside string
            }
        }else if (!quotes) {

            if (c == '{') braces++;
            else if (c == '}') braces--;
            else if (c == '[') brackets++;
            else if (c == ']') brackets--;
            else if (c == ',' && braces == 0 && brackets == 0) break;
        }

        if (braces < 0 || brackets < 0) break; // safety
        end++;
    }

    int len = end - start;
    if (len >= _max_value_len) len = _max_value_len - 1;

    memset(_value, 0, _max_value_len);
    memcpy(_value, start, len);

    char* _trimmedstr = __strtrim_val(_value, ',', _max_value_len);
    if( nullptr != _trimmedstr )
    memcpy(_value, _trimmedstr, strlen(_value));

    _trimmedstr = __strtrim(_value, _max_value_len);
    if( nullptr != _trimmedstr )
    memcpy(_value, _trimmedstr, strlen(_value));

    _trimmedstr = __strtrim_val(_value, '"', _max_value_len);
    if( nullptr != _trimmedstr )
    memcpy(_value, _trimmedstr, strlen(_value));

    return true;
}

/**
 * @brief Convert to lowercase if any uppercase char.
 * @param _str The string to convert to lower.
 * @param _strlen The length of the string.
 */
void __tolowercase(char *_str, int _strlen){

    for (uint16_t i = 0; i < strlen(_str) && i < _strlen; i++)
    {
        if (_str[i] >= 'A' && _str[i] <= 'Z')
        {
            _str[i] = _str[i] + 32;
        }
    }
}

/**
 * @brief Convert to uppercase if any lowercase char.
 * @param _str The string to convert to upper.
 * @param _strlen The length of the string.
 */
void __touppercase(char *_str, int _strlen){

    for (uint16_t i = 0; (nullptr != _str) && (i < strlen(_str)) && (i < _strlen); i++)
    {
        if (_str[i] >= 'a' && _str[i] <= 'z')
        {
            _str[i] = _str[i] - 32;
        }
    }
}

/**
 * @brief Get the provided interface in uppercase and lowercase format.
 * @param _iface The interface perfix.
 * @param _ifaceport The interface port.
 * @param _ifaceuppercase The pointer to the buffer to store interface format in uppercase.
 * @param _ifaceuppercase The pointer to the buffer to store interface format in lowercase.
 * @param _maxlen The max length of the formatted out.
 */
void __get_iface_key_informat( const char* _iface, uint16_t _ifaceport, char* _ifaceuppercase, char* _ifacelowercase, int _maxlen ){

    if( nullptr != _ifaceuppercase ){

        memset(_ifaceuppercase, 0, _maxlen);
        memcpy(_ifaceuppercase, _iface, strlen(_iface));
        __touppercase(_ifaceuppercase, _maxlen);
        __appendUintToBuff(_ifaceuppercase, "%d", _ifaceport, _maxlen - 1);
    }

    if( nullptr != _ifacelowercase ){

        memset(_ifacelowercase, 0, _maxlen);
        memcpy(_ifacelowercase, _iface, strlen(_iface));
        __tolowercase(_ifacelowercase, _maxlen);
        __appendUintToBuff(_ifacelowercase, "%d", _ifaceport, _maxlen - 1);
    }
}

/**
 * @brief Get the data for provided interface key.
 * @param _iface The interface perfix.
 * @param _ifaceport The interface port.
 * @param _jsonpayload The pointer to the json payload.
 * @param _ifacejsondata The pointer to the buffer to store interface json data.
 * @param _maxlen The max length of the iface json data.
 * @return True if the key-value pair was found, false otherwise.
 */
bool __get_iface_data_fromjson( const char* _iface, uint16_t _ifaceport, char* _jsonpayload, int _jsonpayloadlen, char* _ifacejsondata, int _maxjsondatalen ){

    int _iface_key_max_len = strlen(_iface) + 6;
    char _iface_label_uppercase[_iface_key_max_len];
    char _iface_label_lowercase[_iface_key_max_len];

    __get_iface_key_informat(_iface, _ifaceport, _iface_label_uppercase, _iface_label_lowercase, _iface_key_max_len);

    if (
        0 <= __strstr(_jsonpayload, _iface_label_uppercase, _jsonpayloadlen - strlen(_iface_label_uppercase)) ||
        0 <= __strstr(_jsonpayload, _iface_label_lowercase, _jsonpayloadlen - strlen(_iface_label_lowercase)) 
    ){

        if (
            __get_from_json(_jsonpayload, _iface_label_uppercase, _ifacejsondata, _maxjsondatalen) || 
            __get_from_json(_jsonpayload, _iface_label_lowercase, _ifacejsondata, _maxjsondatalen))
        {
            return true;
        }
    }

    return false;
}
