/*
 * (C) Copyright 2005- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 *
 * In applying this licence, ECMWF does not waive the privileges and immunities granted to it by
 * virtue of its status as an intergovernmental organisation nor does it submit to any jurisdiction.
 */

#include "grib_api_internal.h"

// Compare two strings ignoring case.
// strcasecmp is not in the C standard. However, it's defined by
// 4.4BSD, POSIX.1-2001. So we use our own
int strcmp_nocase(const char* s1, const char* s2)
{
    const unsigned char *us1 = (const unsigned char*)s1,
                        *us2 = (const unsigned char*)s2;

    while (tolower(*us1) == tolower(*us2++)) {
        if (*us1++ == '\0')
            return (0);
    }
    return (tolower(*us1) - tolower(*--us2));
}

// Strip whitespace from the end of a string
void string_rtrim(char* s)
{
    size_t len = 0;
    if (!s)
        return;
    len = strlen(s);
    while (len > 0 && isspace((unsigned char)s[len - 1]))
        len--;
    s[len] = '\0';
}

void string_lrtrim(char** x, int do_left, int do_right)
{
    DEBUG_ASSERT(do_left || do_right);
    if (do_left) {
        while (isspace(**x) && **x != '\0')
            (*x)++;
    }
    if (**x == '\0')
        return;
    if (do_right) {
        char* p = (*x) + strlen(*x) - 1;
        while (isspace(*p)) {
            *p = '\0';
            p--;
        }
        if (isspace(*p))
            *p = '\0';
    }
}

// Return the component after final slash
//  "/tmp/x"  -> "x"
//  "/tmp/"   -> ""
const char* extract_filename(const char* filepath)
{
    // Note: Windows users could pass in fwd slashes!
    // so have to check both separators
    const char* s = strrchr(filepath, '/');
    if (!s)
        s = strrchr(filepath, '\\');
    if (!s)
        return filepath;
    else
        return s + 1;
}

// Returns an array of strings the last of which is NULL.
// Note: The delimiter here is a 'string' but must be ONE character!
//       Splitting with several delimiters is not supported.
char** string_split(char* inputString, const char* delimiter)
{
    char** result       = NULL;
    char* p             = inputString;
    char* lastDelimiter = NULL;
    char* aToken        = NULL;
    char* lasts         = NULL;
    size_t numTokens    = 0;
    size_t strLength    = 0;
    size_t index        = 0;
    char delimiterChar  = 0;

    DEBUG_ASSERT(inputString);
    DEBUG_ASSERT(delimiter && (strlen(delimiter) == 1));
    delimiterChar = delimiter[0];
    while (*p) {
        const char ctmp = *p;
        if (ctmp == delimiterChar) {
            ++numTokens;
            lastDelimiter = p;
        }
        p++;
    }
    strLength = strlen(inputString);
    if (lastDelimiter < (inputString + strLength - 1)) {
        ++numTokens; // there is a trailing token
    }
    ++numTokens; // terminating NULL string to mark the end

    result = (char**)malloc(numTokens * sizeof(char*));
    ECCODES_ASSERT(result);

    // Start tokenizing
    aToken = strtok_r(inputString, delimiter, &lasts);
    while (aToken) {
        ECCODES_ASSERT(index < numTokens);
        *(result + index++) = strdup(aToken);
        aToken              = strtok_r(NULL, delimiter, &lasts);
    }
    ECCODES_ASSERT(index == numTokens - 1);
    *(result + index) = NULL;

    return result;
}

// Return GRIB_SUCCESS if we can convert 'input' to an integer, GRIB_INVALID_ARGUMENT otherwise.
// If 'strict' is 1 then disallow characters at the end which are not valid digits.
// E.g., in strict mode, "4i" will be rejected. Otherwise it will convert it to 4
int string_to_long(const char* input, long* output, int strict)
{
    const int base = 10;
    char* endptr;
    long val = 0;

    if (!input)
        return GRIB_INVALID_ARGUMENT;

    errno = 0;
    val   = strtol(input, &endptr, base);
    if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) ||
        (errno != 0 && val == 0)) {
        // perror("strtol");
        return GRIB_INVALID_ARGUMENT;
    }
    if (endptr == input) {
        // fprintf(stderr, "No digits were found\n");
        return GRIB_INVALID_ARGUMENT;
    }
    if (strict && *endptr != 0) {
        // fprintf(stderr, "Left over characters at the end; not a pure number\n");
        return GRIB_INVALID_ARGUMENT;
    }
    *output = val;
    return GRIB_SUCCESS;
}

// Return 1 if 's' ends with 'suffix', 0 otherwise
int string_ends_with(const char* s, const char* suffix)
{
    const size_t len1 = strlen(s);
    const size_t len2 = strlen(suffix);
    if (len2 > len1)
        return 0;

    if (strcmp(&s[len1 - len2], suffix) == 0)
        return 1;
    return 0;
}

int string_count_char(const char* str, char c)
{
    int i = 0, count = 0;
    DEBUG_ASSERT(str);
    for (i=0; str[i]; i++) {
        if (str[i] == c) count++;
    }
    return count;
}

const char* codes_get_product_name(ProductKind product)
{
    switch (product) {
        case PRODUCT_GRIB:
            return "GRIB";
        case PRODUCT_BUFR:
            return "BUFR";
        case PRODUCT_METAR:
            return "METAR";
        case PRODUCT_GTS:
            return "GTS";
        case PRODUCT_TAF:
            return "TAF";
        case PRODUCT_ANY:
            return "ANY";
    }
    return "unknown";
}

const char* grib_get_type_name(int type)
{
    switch (type) {
        case GRIB_TYPE_LONG:
            return "long";
        case GRIB_TYPE_STRING:
            return "string";
        case GRIB_TYPE_BYTES:
            return "bytes";
        case GRIB_TYPE_DOUBLE:
            return "double";
        case GRIB_TYPE_LABEL:
            return "label";
        case GRIB_TYPE_SECTION:
            return "section";
    }
    return "unknown";
}

// Replace all occurrences of character in string.
// Returns pointer to the NUL byte at the end of 's'
char* string_replace_char(char *s, char oldc, char newc)
{
    for (; *s; ++s)
        if (*s == oldc)
            *s = newc;
    return s;
}

// Remove all instances of character 'c' from 'str'
void string_remove_char(char* str, char c)
{
    size_t i, j;
    DEBUG_ASSERT(str);
    size_t len = strlen(str);
    for(i=0; i<len; i++) {
        if(str[i] == c) {
            for(j=i; j<len; j++) {
                str[j] = str[j+1];
            }
            len--;
            i--;
        }
    }
}


size_t levenshteinDistance(const char* a, const char* b)
{
    const size_t m = strlen(a);
    const size_t n = strlen(b);

    // Use a single-row approach: prev holds the previous row, curr the current
    std::vector<size_t> prev(n + 1);
    std::vector<size_t> curr(n + 1);

    for (size_t j = 0; j <= n; ++j)
        prev[j] = j;

    for (size_t i = 1; i <= m; ++i) {
        curr[0] = i;
        for (size_t j = 1; j <= n; ++j) {
            size_t cost = (a[i - 1] == b[j - 1]) ? 0 : 1;
            curr[j] = std::min({prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost});
        }
        std::swap(prev, curr);
    }
    return prev[n];
}


