/*
 * EMS-ESP - https://github.com/emsesp/EMS-ESP
 * Copyright 2020-2025  emsesp.org
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "helpers.h"
#include "emsesp.h"

namespace emsesp {

// like itoa but for hex, and quicker
// note: only for single byte hex values
char * Helpers::hextoa(char * result, const uint8_t value) {
    char *  p    = result;
    uint8_t nib1 = (value >> 4) & 0x0F;
    uint8_t nib2 = (value >> 0) & 0x0F;
    *p++         = nib1 < 0xA ? '0' + nib1 : 'A' + nib1 - 0xA;
    *p++         = nib2 < 0xA ? '0' + nib2 : 'A' + nib2 - 0xA;
    *p           = '\0'; // null terminate just in case
    return result;
}

// same as hextoa but uses to a hex std::string
std::string Helpers::hextoa(const uint8_t value, bool prefix) {
    if (prefix) {
        char buf[5]; // "0x" + 2 hex chars + null
        buf[0] = '0';
        buf[1] = 'x';
        hextoa(&buf[2], value);
        return std::string(buf);
    } else {
        char buf[3];
        hextoa(buf, value);
        return std::string(buf);
    }
}

// same for 16 bit values
char * Helpers::hextoa(char * result, const uint16_t value) {
    if (value <= 0xFF) {
        return hextoa(result, (uint8_t)value);
    }
    hextoa(result, (uint8_t)(value >> 8));
    hextoa(&result[2], (uint8_t)(value & 0xFF));
    return result;
}

// same as above but to a hex string
std::string Helpers::hextoa(const uint16_t value, bool prefix) {
    if (prefix) {
        char buf[7]; // "0x" + 4 hex chars + null
        buf[0] = '0';
        buf[1] = 'x';
        hextoa(&buf[2], value);
        return std::string(buf);
    } else {
        char buf[5];
        hextoa(buf, value);
        return std::string(buf);
    }
}

#ifdef EMSESP_STANDALONE
// special function to work outside of ESP's libraries
char * Helpers::ultostr(char * ptr, uint32_t value, const uint8_t base) {
    if (nullptr == ptr) {
        return nullptr;
    }

    unsigned long t     = 0;
    unsigned long tmp   = value;
    int           count = 0;

    if (tmp == 0) {
        count++;
    }

    while (tmp > 0) {
        tmp = tmp / base;
        count++;
    }

    ptr += count;

    *ptr = '\0';

    do {
        unsigned long res = value - base * (t = value / base);
        if (res < 10) {
            *--ptr = '0' + res;
        } else if (res < 16) {
            *--ptr = 'A' - 10 + res;
        }
    } while ((value = t) != 0);

    return (ptr);
}
#endif

// fast itoa returning a std::string
// http://www.strudel.org.uk/itoa/
std::string Helpers::itoa(int16_t value) {
    // int16_t max: -32768 to 32767 = max 6 chars + null
    char   buf[8];
    char * p = buf + sizeof(buf) - 1;
    *p       = '\0';

    bool    negative = value < 0;
    int32_t abs_val  = negative ? -(int32_t)value : value; // cast to int32 to handle -32768

    // Build string in reverse
    do {
        *--p = '0' + (abs_val % 10);
        abs_val /= 10;
    } while (abs_val > 0);

    if (negative) {
        *--p = '-';
    }

    return std::string(p);
}

/*
  * fast itoa
  * written by Lukás Chmela, Released under GPLv3. http://www.strudel.org.uk/itoa/ version 0.4
  * optimized for ESP32 for EMS-ESP
  */
char * Helpers::itoa(int32_t value, char * result, const uint8_t base) {
    // check that the base if valid
    if (base < 2 || base > 36) {
        *result = '\0';
        return result;
    }

    char *  ptr = result, *ptr1 = result;
    int32_t tmp_value;

    do {
        tmp_value = value;
        value /= base;
        *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + (tmp_value - value * base)];
    } while (value);

    // Apply negative sign
    if (tmp_value < 0) {
        *ptr++ = '-';
    }

    *ptr-- = '\0';
    while (ptr1 < ptr) {
        char tmp_char = *ptr;
        *ptr--        = *ptr1;
        *ptr1++       = tmp_char;
    }

    return result;
}

// for decimals 0 to 99, printed as a 2 char string
char * Helpers::smallitoa(char * result, const uint8_t value) {
    result[0] = ((value / 10) == 0) ? '0' : (value / 10) + '0';
    result[1] = (value % 10) + '0';
    result[2] = '\0';
    return result;
}

// for decimals 0 to 999, printed as a string
char * Helpers::smallitoa(char * result, const uint16_t value) {
    result[0] = ((value / 100) == 0) ? '0' : (value / 100) + '0';
    result[1] = (((value % 100) / 10) == 0) ? '0' : ((value % 100) / 10) + '0';
    result[2] = (value % 10) + '0';
    result[3] = '\0';
    return result;
}

// work out how to display booleans
// for strings only
char * Helpers::render_boolean(char * result, const bool value, const bool dashboard) {
    uint8_t bool_format_ = dashboard ? EMSESP::system_.bool_dashboard() : EMSESP::system_.bool_format();

    if (bool_format_ == BOOL_FORMAT_ONOFF_STR) {
        strlcpy(result, value ? translated_word(FL_(on)) : translated_word(FL_(off)), 12);
    } else if (bool_format_ == BOOL_FORMAT_ONOFF_STR_CAP) {
        strlcpy(result, value ? translated_word(FL_(ON)) : translated_word(FL_(OFF)), 12);
    } else if ((bool_format_ == BOOL_FORMAT_10) || (bool_format_ == BOOL_FORMAT_10_STR)) {
        strlcpy(result, value ? "1" : "0", 2);
    } else {
        strlcpy(result, value ? "true" : "false", 7); // default
    }
    return result;
}

char * Helpers::remove_trailing_zero(char * result) {
    if (strchr(result, '.')) {
        while (result[strlen(result) - 1] == '0') {
            result[strlen(result) - 1] = '\0';
        }
        if (result[strlen(result) - 1] == '.') {
            result[strlen(result) - 1] = '\0';
        }
    }
    return result;
}

// convert unsigned int (single byte) to text value and returns it
// format: 255(0xFF)=boolean, 0=no formatting, otherwise divide by format
char * Helpers::render_value(char * result, uint8_t value, int8_t format, const uint8_t fahrenheit) {
    // special check if its a boolean
    if ((uint8_t)format == EMS_VALUE_BOOL) {
        if (value == EMS_VALUE_BOOL_OFF) {
            render_boolean(result, false);
        } else if (value == EMS_VALUE_BOOL_NOTSET) {
            return nullptr;
        } else {
            render_boolean(result, true); // assume on. could have value 0x01 or 0xFF
        }
        return result;
    }

    if (!hasValue(value)) {
        return nullptr;
    }

    int16_t new_value = fahrenheit ? format ? value * 1.8 + 32 * format * (fahrenheit - 1) : value * 1.8 + 32 * (fahrenheit - 1) : value;

    if (!format) {
        itoa(new_value, result, 10); // format = 0
        return result;
    }

    char s2[10];

    // special case for / 2
    if (format == 2) {
        strlcpy(result, itoa(new_value >> 1, s2, 10), 5);
        strlcat(result, ".", 5);
        strlcat(result, ((new_value & 0x01) ? "5" : "0"), 7);
    } else if (format == 4) {
        strlcpy(result, itoa(new_value >> 2, s2, 10), 5);
        strlcat(result, ".", 5);
        new_value = (new_value & 0x03) * 25;
        strlcat(result, itoa(new_value, s2, 10), 7);
    } else if (format > 0) {
        strlcpy(result, itoa(new_value / format, s2, 10), 5);
        strlcat(result, ".", 5);
        strlcat(result, itoa(new_value % format, s2, 10), 7);
    } else {
        strlcpy(result, itoa(new_value * format * -1, s2, 10), 5);
    }

    return remove_trailing_zero(result);
}

// float: convert float to char
// format is the precision, 0 to 8
char * Helpers::render_value(char * result, const double value, const int8_t format) {
    if (format > 8) {
        return nullptr;
    }

    uint32_t p[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};

    char * ret   = result;
    double v     = value < 0 ? value - 1.0 / (2 * p[format]) : value + 1.0 / (2 * p[format]);
    auto   whole = (long long)v;

    if (whole <= 0 && v < 0) {
        result[0] = '-';
        result++;
        whole = -whole;
        v     = -v;
    }
#ifndef EMSESP_STANDALONE
    lltoa(whole, result, 10);
#else
    ultostr(result, whole, 10);
#endif

    while (*result != '\0') {
        result++;
    }

    *result++    = '.';
    auto decimal = abs((int32_t)((v - whole) * p[format]));
    for (int8_t i = 1; i < format; i++) {
        if (decimal < p[i]) {
            *result++ = '0'; // add leading zeros
        }
    }
    itoa(decimal, result, 10);

    return remove_trailing_zero(ret);
}

// int32: convert signed 32bit to text string and returns string
// format: 0=no division, other divide by the value given and render with a decimal point
char * Helpers::render_value(char * result, const int32_t value, const int8_t format, const uint8_t fahrenheit) {
    int32_t new_value = fahrenheit ? format ? value * 1.8 + 32 * format * (fahrenheit - 1) : value * 1.8 + 32 * (fahrenheit - 1) : value;
    char    s[13]     = {0};
    // just print it if no conversion required (format = 0)
    if (!format) {
        strlcpy(result, itoa(new_value, s, 10), sizeof(s)); // format is 0
        return result;
    }

    result[0] = '\0';

    // check for negative values
    if (new_value < 0) {
        strlcpy(result, "-", sizeof(s));
        new_value *= -1; // convert to positive
    } else {
        strlcpy(result, "", sizeof(s));
    }

    // do floating point
    if (format == 2) {
        // divide by 2
        strlcat(result, itoa(new_value / 2, s, 10), sizeof(s));
        strlcat(result, ".", sizeof(s));
        strlcat(result, ((new_value & 0x01) ? "5" : "0"), sizeof(s));
    } else if (format > 0) {
        strlcat(result, itoa(new_value / format, s, 10), sizeof(s));
        strlcat(result, ".", sizeof(s));
        strlcat(result, itoa(((new_value % format) * 10) / format, s, 10), sizeof(s));
    } else {
        strlcat(result, itoa(new_value * format * -1, s, 10), sizeof(s));
    }

    return remove_trailing_zero(result);
}

// int16: convert short (two bytes) to text string and prints it
char * Helpers::render_value(char * result, const int16_t value, const int8_t format, const uint8_t fahrenheit) {
    if (!hasValue(value)) {
        return nullptr;
    }

    return (render_value(result, (int32_t)value, format, fahrenheit)); // use same code, force it to a signed int
}

// uint16: convert unsigned short (two bytes) to text string and prints it
char * Helpers::render_value(char * result, const uint16_t value, const int8_t format, const uint8_t fahrenheit) {
    if (!hasValue(value)) {
        return nullptr;
    }

    return (render_value(result, (int32_t)value, format, fahrenheit)); // use same code, force it to a signed int
}

// int8: convert signed byte to text string and prints it
char * Helpers::render_value(char * result, const int8_t value, const int8_t format, const uint8_t fahrenheit) {
    if (!hasValue(value)) {
        return nullptr;
    }

    return (render_value(result, (int32_t)value, format, fahrenheit)); // use same code, force it to a signed int
}

// uint32: render long (4 byte) unsigned values
char * Helpers::render_value(char * result, const uint32_t value, const int8_t format, const uint8_t fahrenheit) {
    if (!hasValue(value)) {
        return nullptr;
    }
    result[0]          = '\0';
    uint32_t new_value = fahrenheit ? format ? value * 1.8 + 32 * format * (fahrenheit - 1) : value * 1.8 + 32 * (fahrenheit - 1) : value;
    char     s[14]     = {0};

#ifndef EMSESP_STANDALONE
    if (!format) {
        strlcpy(result, lltoa(new_value, s, 10), sizeof(s)); // format is 0
    } else if (format > 0) {
        strlcpy(result, lltoa(new_value / format, s, 10), sizeof(s));
        strlcat(result, ".", sizeof(s));
        strlcat(result, itoa(((new_value % format) * 10) / format, s, 10), sizeof(s));
        if (format == 100) {
            strlcat(result, itoa(new_value % 10, s, 10), sizeof(s));
        }
    } else {
        strlcpy(result, lltoa(new_value * format * -1, s, 10), sizeof(s));
    }
#else
    if (!format) {
        strlcpy(result, ultostr(s, new_value, 10), sizeof(s)); // format is 0
    } else {
        strlcpy(result, ultostr(s, new_value / format, 10), sizeof(s));
        strlcat(result, ".", sizeof(s));
        strncat(result, ultostr(s, new_value % format, 10), sizeof(s));
    }
#endif

    return remove_trailing_zero(result);
}

// convert special Latin1 characters to UTF8
char * Helpers::render_string(char * result, const char * c, const uint8_t len) {
    char * p = result;
    while (*c != '\0' && (p - result < len)) {
        switch ((uint8_t)*c) {
        case 0xC4: // Ä
            *p     = 0xC3;
            *(++p) = 0x84;
            break;
        case 0xD6: // Ö
            *p     = 0xC3;
            *(++p) = 0x96;
            break;
        case 0xDC: // Ü
            *p     = 0xC3;
            *(++p) = 0x9C;
            break;
        case 0xDF: // ß
            *p     = 0xC3;
            *(++p) = 0x9F;
            break;
        case 0xE4: // ä
            *p     = 0xC3;
            *(++p) = 0xA4;
            break;
        case 0xF6: // ö
            *p     = 0xC3;
            *(++p) = 0xB6;
            break;
        case 0xFC: // ü
            *p     = 0xC3;
            *(++p) = 0xBC;
            break;
        case 0xF0: // greek capital Xi, used for boiler servicecode dhw+heat
            *p     = 0xCE;
            *(++p) = 0x9E;
            break;
        default:
            *p = (*c & 0x80) ? '?' : *c;
            break;
        }
        c++;
        p++;
    }
    *p = '\0'; // terminate result
    return result;
}

char * Helpers::utf8tolatin1(char * result, const char * c, const uint8_t len) {
    char * p = result;
    while (*c != '\0' && (p - result < len)) {
        if ((uint8_t)*c == 0xC3) {
            c++;
            switch ((uint8_t)*c) {
            case 0x84: // Ä
                *p = 0xC4;
                break;
            case 0x96: // Ö
                *p = 0xD6;
                break;
            case 0x9C: // Ü
                *p = 0xDC;
                break;
            case 0x9F: // ß
                *p = 0xDF;
                break;
            case 0xA4: // ä
                *p = 0xE4;
                break;
            case 0xB6: // ö
                *p = 0xF6;
                break;
            case 0xBC: // ü
                *p = 0xFC;
                break;
            default:
                break;
            }
        } else if ((uint8_t)*c > 127) {
            *p = '?';
        } else {
            *p = *c;
        }
        c++;
        p++;
    }
    *p = '\0'; // terminate result
    return result;
}
// creates string of hex values from an array of bytes
std::string Helpers::data_to_hex(const uint8_t * data, const uint8_t length) {
    if (length == 0) {
        return "<empty>";
    }

    std::vector<char> str(length * 3);
    memset(str.data(), 0, str.size());

    char   buffer[4];
    char * p = str.data();
    for (uint8_t i = 0; i < length; i++) {
        Helpers::hextoa(buffer, data[i]);
        *p++ = buffer[0];
        *p++ = buffer[1];
        *p++ = ' '; // space
    }
    *--p = '\0'; // null terminate just in case, loosing the trailing space

    return std::string(str.data());
}

// takes a hex string and convert it to an unsigned 32bit number (max 8 hex digits)
// works with only positive numbers
uint32_t Helpers::hextoint(const char * hex) {
    if (hex == nullptr) {
        return 0;
    }

    uint32_t val = 0;

    // skip leading '0x'
    if (hex[0] == '0' && hex[1] == 'x') {
        hex += 2;
    }

    while (*hex) {
        // get current character then increment
        char byte = *hex++;
        // transform hex character to the 4bit equivalent number, using the ascii table indexes
        if (byte == ' ')
            byte = *hex++; // skip spaces
        if (byte >= '0' && byte <= '9')
            byte = byte - '0';
        else if (byte >= 'a' && byte <= 'f')
            byte = byte - 'a' + 10;
        else if (byte >= 'A' && byte <= 'F')
            byte = byte - 'A' + 10;
        else
            return 0; // error
        // shift 4 to make space for new digit, and add the 4 bits of the new digit
        val = (val << 4) | (byte & 0xF);
    }

    return val;
}

// quick char to long
int Helpers::atoint(const char * value) {
    int  x = 0;
    char s = value[0];
    if (s == '-') {
        ++value;
    }
    while (*value >= '0' && *value <= '9') {
        x = (x * 10) + (*value - '0');
        ++value;
    }
    if (s == '-') {
        return (-x);
    }
    return x;
}

// rounds a number to 2 decimal places
// example: round2(3.14159) -> 3.14
//  The conversion to Fahrenheit is different for absolute temperatures and relative temperatures like hysteresis.
//  fahrenheit=0 - off, no conversion
//  fahrenheit=1 - relative, 1.8t
//  fahrenheit=2 - absolute, 1.8t + 32(fahrenheit-1)
double Helpers::transformNumFloat(double value, const int8_t numeric_operator, const uint8_t fahrenheit) {
    double val;

    if (numeric_operator == 0) { // DV_NUMOP_NONE
        val = value * 100;
    } else if (numeric_operator > 0) { // DV_NUMOP_DIVxx
        val = value * 100 / numeric_operator;
    } else { // DV_NUMOP_MULxx
        val = value * -100 * numeric_operator;
    }

    if (fahrenheit) {
        val = val * 1.8 + 3200 * (fahrenheit - 1);
    }

    return (round(val)) / 100.0;
}

// abs of a signed 32-bit integer
uint32_t Helpers::abs(const int32_t i) {
    return (i < 0 ? -i : i);
}

// for booleans, use isBool true (EMS_VALUE_BOOL)
bool Helpers::hasValue(const uint8_t & value, const uint8_t isBool) {
    if (isBool == EMS_VALUE_BOOL) {
        return (value != EMS_VALUE_BOOL_NOTSET);
    }
    return (value != EMS_VALUE_UINT8_NOTSET);
}

bool Helpers::hasValue(const int8_t & value) {
    return (value != EMS_VALUE_INT8_NOTSET);
}

bool Helpers::hasValue(const char * value) {
    if ((value == nullptr) || (strlen(value) == 0)) {
        return false;
    }

    return (value[0] != '\0');
}

// for short these are typically 0x8300, 0x7D00 and sometimes 0x8000
bool Helpers::hasValue(const int16_t & value) {
    return (abs(value) < EMS_VALUE_UINT16_NOTSET);
}

bool Helpers::hasValue(const uint16_t & value) {
    return (value < EMS_VALUE_UINT16_NOTSET);
}

bool Helpers::hasValue(const uint32_t & value) {
    return (value != EMS_VALUE_UINT24_NOTSET && value != EMS_VALUE_UINT32_NOTSET);
}

// checks if we can convert a char string to an int value
bool Helpers::value2number(const char * value, int & value_i, const int min, const int max) {
    if ((value == nullptr) || (strlen(value) == 0)) {
        value_i = 0;
        return false;
    }

    if (strlen(value) > 2 && value[0] == '0' && value[1] == 'x') {
        value_i = hextoint(value);
    } else {
        value_i = atoi(value);
    }

    if (value_i >= min && value_i <= max) {
        return true;
    }
    return false;
}

// checks if we can convert a char string to a float value
bool Helpers::value2float(const char * value, float & value_f) {
    value_f = 0;
    if ((value == nullptr) || (strlen(value) == 0)) {
        return false;
    }

    if (value[0] == '-' || value[0] == '.' || (value[0] >= '0' && value[0] <= '9')) {
        value_f = atof(value);
        return true;
    }

    if (value[0] == '+' && (value[1] == '.' || (value[1] >= '0' && value[1] <= '9'))) {
        value_f = atof(value + 1);
        return true;
    }
    return false;
}

bool Helpers::value2temperature(const char * value, float & value_f, bool relative) {
    if (value2float(value, value_f)) {
        if (EMSESP::system_.fahrenheit()) {
            value_f = relative ? (value_f / 1.8) : (value_f - 32) / 1.8;
        }
        return true;
    }
    return false;
}

bool Helpers::value2temperature(const char * value, int & value_i, const bool relative, const int min, const int max) {
    if (value2number(value, value_i, min, max)) {
        if (EMSESP::system_.fahrenheit()) {
            value_i = relative ? (value_i / 1.8) : (value_i - 32) / 1.8;
        }
        return true;
    }
    return false;
}

// checks if we can convert a char string to a lowercase string
bool Helpers::value2string(const char * value, std::string & value_s) {
    if ((value == nullptr) || (strlen(value) == 0)) {
        value_s = std::string{};
        return false;
    }

    value_s = toLower(value);
    return true;
}

// checks to see if a string (usually a command or payload cmd) looks like a boolean
// on, off, true, false, 1, 0
// uses translated words for on/off
bool Helpers::value2bool(const char * value, bool & value_b) {
    if ((value == nullptr) || (strlen(value) == 0)) {
        return false;
    }

    std::string bool_str = toLower(value);

    if ((bool_str == std::string(Helpers::translated_word(FL_(on)))) || (bool_str == toLower(Helpers::translated_word(FL_(ON)))) || (bool_str == "on")
        || (bool_str == "1") || (bool_str == "true")) {
        value_b = true;
        return true; // is a bool
    }

    if ((bool_str == std::string(Helpers::translated_word(FL_(off)))) || (bool_str == toLower(Helpers::translated_word(FL_(OFF)))) || (bool_str == "off")
        || (bool_str == "0") || (bool_str == "false")) {
        value_b = false;
        return true; // is a bool
    }

#ifdef EMSESP_STANDALONE
    EMSESP::logger().debug("Error. value2bool: %s is not a boolean", value);
#endif

    return false; // not a bool
}

// checks to see if a string is member of a vector and return the index, also allow true/false for on/off
// this for a list of lists, when using translated strings
bool Helpers::value2enum(const char * value, uint8_t & value_ui, const char * const ** strs) {
    if ((value == nullptr) || (strlen(value) == 0)) {
        return false;
    }
    std::string str = toLower(value);

    for (value_ui = 0; strs[value_ui]; value_ui++) {
        std::string str1 = toLower(std::string(Helpers::translated_word(strs[value_ui])));
        std::string str2 = toLower((strs[value_ui][0])); // also check for default language
        if ((str1 != "")
            && ((str2 == "off" && str == "false") || (str2 == "on" && str == "true") || (str == str1) || (str == str2)
                || (value[0] == ('0' + value_ui) && value[1] == '\0'))) {
            return true;
        }
    }
    value_ui = 0;
    return false;
}

bool Helpers::value2enum(const char * value, uint8_t & value_ui, const char * const ** strs, const std::vector<uint8_t> & mask) {
    uint8_t v = value_ui;
    if (!value2enum(value, v, strs) || v >= mask.size()) {
        return false;
    }
    value_ui = mask[v];
    return true;
}

// finds the string (value) of a list vector (strs)
// returns true if found, and sets the value_ui to the index, else false
// also allow true/false for on/off
bool Helpers::value2enum(const char * value, uint8_t & value_ui, const char * const * strs) {
    if ((value == nullptr) || (strlen(value) == 0)) {
        return false;
    }
    std::string str = toLower(value);

    std::string s_on  = Helpers::translated_word(FL_(on));
    std::string s_off = Helpers::translated_word(FL_(off));

    // stops when a nullptr is found, which is the end delimeter of a MAKE_TRANSLATION()
    // could use count_items() to avoid buffer over-run but this works
    for (value_ui = 0; strs[value_ui]; value_ui++) {
        std::string enum_str = toLower((strs[value_ui]));

        if ((enum_str != "")
            && ((enum_str == "off" && (str == s_off || str == "false")) || (enum_str == "on" && (str == s_on || str == "true")) || (str == enum_str)
                || (value[0] == ('0' + value_ui) && value[1] == '\0'))) {
            return true;
        }
    }

    return false;
}

bool Helpers::value2enum(const char * value, uint8_t & value_ui, const char * const * strs, const std::vector<uint8_t> & mask) {
    uint8_t v = value_ui;
    if (!value2enum(value, v, strs) || v >= mask.size()) {
        return false;
    }
    value_ui = mask[v];
    return true;
}

// https://stackoverflow.com/questions/313970/how-to-convert-stdstring-to-lower-case
std::string Helpers::toLower(std::string const & s) {
    std::string lc = s;
    std::transform(lc.begin(), lc.end(), lc.begin(), [](unsigned char c) { return std::tolower(c); });
    return lc;
}

std::string Helpers::toLower(const char * s) {
    return toLower(std::string(s));
}

std::string Helpers::toUpper(std::string const & s) {
    std::string lc = s;
    std::transform(lc.begin(), lc.end(), lc.begin(), [](unsigned char c) { return std::toupper(c); });
    return lc;
}

// capitalizes one UTF-8 character in char array
// works with Latin1 (1 byte), Polish and other (2 bytes) characters
// supports special characters for all 11 supported languages: EN, DE, NL, SV, PL, NO, FR, TR, IT, SK, CZ
#if defined(EMSESP_STANDALONE)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtype-limits"
#endif
void Helpers::CharToUpperUTF8(char * c) {
    auto p   = (c + 1); // pointer to 2nd char of 2-byte unicode char
    char p_v = *p;      // value of 2nd char in 2-byte unicode char

    switch (*c) {
    case (char)0xC3:
        // grave, acute, circumflex, diaeresis, etc.
        if ((p_v >= (char)0xA0) && (p_v <= (char)0xBE)) {
            *p -= 0x20;
        }
        // Additional special characters for supported languages
        switch (p_v) {
        case (char)0xA0: // à -> À
        case (char)0xA1: // á -> Á
        case (char)0xA2: // â -> Â
        case (char)0xA3: // ã -> Ã
        case (char)0xA4: // ä -> Ä (German, Swedish)
        case (char)0xA5: // å -> Å (Swedish, Norwegian)
        case (char)0xA6: // æ -> Æ (Norwegian)
        case (char)0xA7: // ç -> Ç (French, Turkish)
        case (char)0xA8: // è -> È (French, Italian)
        case (char)0xA9: // é -> É (French, Italian)
        case (char)0xAA: // ê -> Ê (French)
        case (char)0xAB: // ë -> Ë (French)
        case (char)0xAC: // ì -> Ì (Italian)
        case (char)0xAD: // í -> Í (Slovak, Czech)
        case (char)0xAE: // î -> Î (French)
        case (char)0xAF: // ï -> Ï (French)
        case (char)0xB0: // ð -> Ð (Icelandic)
        case (char)0xB1: // ñ -> Ñ (Spanish)
        case (char)0xB2: // ò -> Ò (Italian)
        case (char)0xB3: // ó -> Ó (Slovak, Czech)
        case (char)0xB4: // ô -> Ô (French, Slovak)
        case (char)0xB5: // õ -> Õ (Portuguese)
        case (char)0xB6: // ö -> Ö (German, Swedish, Turkish)
        case (char)0xB8: // ø -> Ø (Norwegian)
        case (char)0xB9: // ù -> Ù (French, Italian)
        case (char)0xBA: // ú -> Ú (Slovak, Czech)
        case (char)0xBB: // û -> Û (French)
        case (char)0xBC: // ü -> Ü (German, French, Turkish)
        case (char)0xBD: // ý -> Ý (Slovak, Czech)
        case (char)0xBE: // þ -> Þ (Icelandic)
        case (char)0xBF: // ÿ -> Ÿ (French)
            *p -= 0x20;
            break;
        }
        break;
    case (char)0xC4:
        switch (p_v) {
        case (char)0x85: //ą (0xC4,0x85) -> Ą (0xC4,0x84) (Polish)
        case (char)0x87: //ć (0xC4,0x87) -> Ć (0xC4,0x86) (Polish)
        case (char)0x8D: //č (0xC4,0x8D) -> Č (0xC4,0x8C) (Slovak, Czech)
        case (char)0x8F: //ď (0xC4,0x8F) -> Ď (0xC4,0x8E) (Slovak, Czech)
        case (char)0x9F: //ğ (0xC4,0x9F) -> Ğ (0xC4,0x9E) (Turkish)
        case (char)0x99: //ę (0xC4,0x99) -> Ę (0xC4,0x98) (Polish)
        case (char)0x9B: //ě (0xC4,0x9B) -> Ě (0xC4,0x9A) (Czech)
        case (char)0xAF: //ı (0xC4,0xAF) -> I (0xC4,0xAE) (Turkish)
        case (char)0xB1: //ı (0xC4,0xB1) -> I (0xC4,0xB0) (Turkish)
        case (char)0xB3: //ĳ (0xC4,0xB3) -> Ĳ (0xC4,0xB2) (Dutch)
            *p -= 1;
            break;
        }
        break;
    case (char)0xC5:
        switch (p_v) {
        case (char)0x81: //ł (0xC5,0x81) -> Ł (0xC5,0x80) (Polish)
        case (char)0x82: //ł (0xC5,0x82) -> Ł (0xC5,0x81) (Polish)
        case (char)0x83: //ń (0xC5,0x83) -> Ń (0xC5,0x82) (Polish)
        case (char)0x84: //ń (0xC5,0x84) -> Ń (0xC5,0x83) (Polish)
        case (char)0x88: //ň (0xC5,0x88) -> Ň (0xC5,0x87) (Slovak, Czech)
        case (char)0x95: //ŕ (0xC5,0x95) -> Ŕ (0xC5,0x94) (Slovak)
        case (char)0x99: //ř (0xC5,0x99) -> Ř (0xC5,0x98) (Czech)
        case (char)0x9A: //ś (0xC5,0x9A) -> Ś (0xC5,0x99) (Polish)
        case (char)0x9B: //ś (0xC5,0x9B) -> Ś (0xC5,0x9A) (Polish)
        case (char)0x9F: //ş (0xC5,0x9F) -> Ş (0xC5,0x9E) (Turkish)
        case (char)0xA1: //š (0xC5,0xA1) -> Š (0xC5,0xA0) (Slovak, Czech)
        case (char)0xA5: //ť (0xC5,0xA5) -> Ť (0xC5,0xA4) (Slovak, Czech)
        case (char)0xAF: //ů (0xC5,0xAF) -> Ů (0xC5,0xAE) (Czech)
        case (char)0xBA: //ź (0xC5,0xBA) -> Ź (0xC5,0xB9) (Polish)
        case (char)0xBC: //ż (0xC5,0xBC) -> Ż (0xC5,0xBB) (Polish)
        case (char)0xBE: //ž (0xC5,0xBE) -> Ž (0xC5,0xBD) (Slovak, Czech)
            *p -= 1;
            break;
        }
        break;
    default:
        *c = toupper(*c); // works on Latin1 letters
        break;
    }
}
#if defined(EMSESP_STANDALONE)
#pragma GCC diagnostic pop
#endif

// replace char in char string
void Helpers::replace_char(char * str, char find, char replace) {
    if (str == nullptr) {
        return;
    }

    int i = 0;

    while (str[i] != '\0') {
        // Replace the matched character...
        if (str[i] == find)
            str[i] = replace;

        i++;
    }
}

// count number of items in a list
// the end of a list has a nullptr
uint8_t Helpers::count_items(const char * const * list) {
    uint8_t list_size = 0;
    if (list != nullptr) {
        while (list[list_size]) {
            list_size++;
        }
    }
    return list_size;
}

// count number of items in a list of lists
// the end of a list has a nullptr
uint8_t Helpers::count_items(const char * const ** list) {
    uint8_t list_size = 0;
    if (list != nullptr) {
        while (list[list_size]) {
            list_size++;
        }
    }
    return list_size;
}

// returns char pointer to translated description or fullname
// if force_en is true always take the EN non-translated word
const char * Helpers::translated_word(const char * const * strings, const bool force_en) {
    uint8_t language_index = EMSESP::system_.language_index();
    uint8_t index          = 0; // default en

    if (!strings) {
        return ""; // no translations
    }

    // see how many translations we have for this entity. if there is no translation for this, revert to EN
    if (!force_en && (Helpers::count_items(strings) >= language_index + 1 && strlen(strings[language_index]))) {
        index = language_index;
    }

    return strings[index];
}

uint16_t Helpers::string2minutes(const std::string & str) {
    uint8_t  i     = 0;
    uint16_t res   = 0;
    uint16_t tmp   = 0;
    uint8_t  state = 0;

    while (str[i] != '\0') {
        // If we got a digit
        if (str[i] >= '0' && str[i] <= '9') {
            tmp = tmp * 10 + str[i] - '0';
        }
        // Or if we got a colon
        else if (str[i] == ':') {
            // If we were reading the hours
            if (state == 0) {
                res = 60 * tmp;
            }
            // Or if we were reading the minutes
            else if (state == 1) {
                if (tmp > 60) {
                    return 0;
                }
                res += tmp;
            }
            // Or we got an extra colon
            else {
                return 0;
            }

            state++;
            tmp = 0;
        }

        // Or we got something wrong
        else {
            return 0;
        }
        i++;
    }

    if (state == 1 && tmp < 60) {
        return res + tmp;
    } else if (state == 0) { // without : it's only minutes
        return tmp;
    } else {
        return 0; // Or if we were not, something is wrong in the given string
    }
}

float Helpers::numericoperator2scalefactor(int8_t numeric_operator) {
    if (numeric_operator == 0)
        return 1.0f;
    else if (numeric_operator > 0)
        return 1.0f / numeric_operator;
    else
        return -numeric_operator;
}

} // namespace emsesp
