// SPDX-License-Identifier: GPL-3.0-or-later

#include "../libnetdata.h"

struct simple_pattern {
    const char *match;
    uint32_t len;

    SIMPLE_PREFIX_MODE mode;
    bool negative;
    bool case_sensitive;

    struct simple_pattern *child;
    struct simple_pattern *next;
};

static struct simple_pattern *parse_pattern(char *str, SIMPLE_PREFIX_MODE default_mode, size_t count) {
    if(unlikely(count >= 1000))
        return NULL;

    // fprintf(stderr, "PARSING PATTERN: '%s'\n", str);

    SIMPLE_PREFIX_MODE mode;
    struct simple_pattern *child = NULL;

    char *s = str, *c = str;

    // skip asterisks in front
    while(*c == '*') c++;

    // find the next asterisk
    while(*c && *c != '*') c++;

    // do we have an asterisk in the middle?
    if(*c == '*' && c[1] != '\0') {
        // yes, we have
        child = parse_pattern(c, default_mode, count + 1);
        c[1] = '\0';
    }

    // check what this one matches

    size_t len = strlen(s);
    if(len >= 2 && *s == '*' && s[len - 1] == '*') {
        s[len - 1] = '\0';
        s++;
        mode = SIMPLE_PATTERN_SUBSTRING;
    }
    else if(len >= 1 && *s == '*') {
        s++;
        mode = SIMPLE_PATTERN_SUFFIX;
    }
    else if(len >= 1 && s[len - 1] == '*') {
        s[len - 1] = '\0';
        mode = SIMPLE_PATTERN_PREFIX;
    }
    else
        mode = default_mode;

    // allocate the structure
    struct simple_pattern *m = callocz(1, sizeof(struct simple_pattern));
    if(*s) {
        m->match = strdupz(s);
        m->len = strlen(m->match);
        m->mode = mode;
    }
    else {
        m->mode = SIMPLE_PATTERN_SUBSTRING;
    }

    m->child = child;

    return m;
}

SIMPLE_PATTERN *simple_pattern_create(const char *list, const char *separators, SIMPLE_PREFIX_MODE default_mode, bool case_sensitive) {
    struct simple_pattern *root = NULL, *last = NULL;

    if(unlikely(!list || !*list)) return root;

    bool isseparator[256] = {
              [' ']  = true    // space
            , ['\t'] = true    // tab
            , ['\r'] = true    // carriage return
            , ['\n'] = true    // new line
            , ['\f'] = true    // form feed
            , ['\v'] = true    // vertical tab
    };

    if (unlikely(separators == SIMPLE_PATTERN_NO_SEPARATORS))
        memset(isseparator, false, sizeof(isseparator));

    else if (unlikely(separators && *separators)) {
        memset(isseparator, false, sizeof(isseparator));
        while(*separators) isseparator[(unsigned char)*separators++] = true;
    }

    char *buf = mallocz(strlen(list) + 1);
    const char *s = list;

    while(s && *s) {
        buf[0] = '\0';
        char *c = buf;

        bool negative = false;

        // skip all spaces
        while(isseparator[(unsigned char)*s])
            s++;

        if(*s == '!') {
            negative = true;
            s++;
        }

        // empty string
        if(unlikely(!*s))
            break;

        // find the next space
        char escape = 0;
        while(*s) {
            if(*s == '\\' && !escape) {
                escape = 1;
                s++;
            }
            else {
                if (isseparator[(unsigned char)*s] && !escape) {
                    s++;
                    break;
                }

                *c++ = *s++;
                escape = 0;
            }
        }

        // terminate our string
        *c = '\0';

        // if we matched the empty string, skip it
        if(unlikely(!*buf))
            continue;

        // fprintf(stderr, "FOUND PATTERN: '%s'\n", buf);
        struct simple_pattern *m = parse_pattern(buf, default_mode, 0);
        m->negative = negative;
        m->case_sensitive = case_sensitive;

        if(default_mode == SIMPLE_PATTERN_SUBSTRING) {
            m->mode = SIMPLE_PATTERN_SUBSTRING;

            struct simple_pattern *tm;
            for(tm = m; tm->child ; tm = tm->child) ;
            tm->mode = SIMPLE_PATTERN_SUBSTRING;
        }

        // link it at the end
        if(unlikely(!root))
            root = last = m;
        else {
            last->next = m;
            last = m;
        }
    }

    freez(buf);
    return (SIMPLE_PATTERN *)root;
}

ALWAYS_INLINE
static char *add_wildcarded(const char *matched, size_t matched_size, char *wildcarded, size_t *wildcarded_size) {
    //if(matched_size) {
    //    char buf[matched_size + 1];
    //    strncpyz(buf, matched, matched_size);
    //    fprintf(stderr, "ADD WILDCARDED '%s' of length %zu\n", buf, matched_size);
    //}

    if(unlikely(wildcarded && *wildcarded_size && matched && *matched && matched_size)) {
        size_t wss = *wildcarded_size - 1;
        size_t len = (matched_size < wss)?matched_size:wss;
        if(likely(len)) {
            strncpyz(wildcarded, matched, len);

            *wildcarded_size -= len;
            return &wildcarded[len];
        }
    }

    return wildcarded;
}

ALWAYS_INLINE
static int sp_strcmp(const char *s1, const char *s2, bool case_sensitive) {
    if(case_sensitive)
        return strcmp(s1, s2);

    return strcasecmp(s1, s2);
}

ALWAYS_INLINE
static int sp_strncmp(const char *s1, const char *s2, size_t n, bool case_sensitive) {
    if(case_sensitive)
        return strncmp(s1, s2, n);

    return strncasecmp(s1, s2, n);
}

ALWAYS_INLINE
static char *sp_strstr(const char *haystack, const char *needle, bool case_sensitive) {
    if(case_sensitive)
        return strstr(haystack, needle);

    return strcasestr(haystack, needle);
}

ALWAYS_INLINE
static bool match_pattern(struct simple_pattern *m, const char *str, size_t len, char *wildcarded, size_t *wildcarded_size) {
    char *s;

    bool loop = true;
    while(loop && m->len <= len) {
        loop = false;

        switch(m->mode) {
            default:
            case SIMPLE_PATTERN_EXACT:
                if(unlikely(sp_strcmp(str, m->match, m->case_sensitive) == 0)) {
                    if(!m->child) return true;
                    return false;
                }
                break;

            case SIMPLE_PATTERN_SUBSTRING:
                if(!m->len) return true;
                if((s = sp_strstr(str, m->match, m->case_sensitive))) {
                    wildcarded = add_wildcarded(str, s - str, wildcarded, wildcarded_size);
                    if(!m->child) {
                        add_wildcarded(&s[m->len], len - (&s[m->len] - str), wildcarded, wildcarded_size);
                        return true;
                    }

                    // instead of recursion
                    {
                        len = len - (s - str) - m->len;
                        str = &s[m->len];
                        m = m->child;
                        loop = true;
                        // return match_pattern(m->child, &s[m->len], len - (s - str) - m->len, wildcarded, wildcarded_size);
                    }
                }
                break;

            case SIMPLE_PATTERN_PREFIX:
                if(unlikely(sp_strncmp(str, m->match, m->len, m->case_sensitive) == 0)) {
                    if(!m->child) {
                        add_wildcarded(&str[m->len], len - m->len, wildcarded, wildcarded_size);
                        return true;
                    }
                    // instead of recursion
                    {
                        len = len - m->len;
                        str = &str[m->len];
                        m = m->child;
                        loop = true;
                        // return match_pattern(m->child, &str[m->len], len - m->len, wildcarded, wildcarded_size);
                    }
                }
                break;

            case SIMPLE_PATTERN_SUFFIX:
                if(unlikely(sp_strcmp(&str[len - m->len], m->match, m->case_sensitive) == 0)) {
                    add_wildcarded(str, len - m->len, wildcarded, wildcarded_size);
                    if(!m->child) return true;
                    return false;
                }
                break;
        }
    }

    return false;
}

ALWAYS_INLINE
static SIMPLE_PATTERN_RESULT simple_pattern_matches_extract_with_length(SIMPLE_PATTERN *list, const char *str, size_t len, char *wildcarded, size_t wildcarded_size) {
    struct simple_pattern *m, *root = (struct simple_pattern *)list;

    for(m = root; m ; m = m->next) {
        char *ws = wildcarded;
        size_t wss = wildcarded_size;
        if(unlikely(ws)) *ws = '\0';

        if (match_pattern(m, str, len, ws, &wss)) {
            if (m->negative) return SP_MATCHED_NEGATIVE;
            return SP_MATCHED_POSITIVE;
        }
    }

    return SP_NOT_MATCHED;
}

SIMPLE_PATTERN_RESULT simple_pattern_matches_buffer_extract(SIMPLE_PATTERN *list, BUFFER *str, char *wildcarded, size_t wildcarded_size) {
    if(!list || !str || buffer_strlen(str)) return SP_NOT_MATCHED;
    return simple_pattern_matches_extract_with_length(list, buffer_tostring(str), buffer_strlen(str), wildcarded, wildcarded_size);
}

SIMPLE_PATTERN_RESULT simple_pattern_matches_string_extract(SIMPLE_PATTERN *list, STRING *str, char *wildcarded, size_t wildcarded_size) {
    if(!list || !str) return SP_NOT_MATCHED;
    return simple_pattern_matches_extract_with_length(list, string2str(str), string_strlen(str), wildcarded, wildcarded_size);
}

SIMPLE_PATTERN_RESULT simple_pattern_matches_extract(SIMPLE_PATTERN *list, const char *str, char *wildcarded, size_t wildcarded_size) {
    if(!list || !str || !*str) return SP_NOT_MATCHED;
    return simple_pattern_matches_extract_with_length(list, str, strlen(str), wildcarded, wildcarded_size);
}

SIMPLE_PATTERN_RESULT simple_pattern_matches_length_extract(SIMPLE_PATTERN *list, const char *str, size_t len, char *wildcarded, size_t wildcarded_size) {
    if(!list || !str || !*str || !len) return SP_NOT_MATCHED;
    return simple_pattern_matches_extract_with_length(list, str, len, wildcarded, wildcarded_size);
}

static inline void free_pattern(struct simple_pattern *m) {
    if(!m) return;

    free_pattern(m->child);
    free_pattern(m->next);
    freez((void *)m->match);
    freez(m);
}

void simple_pattern_free(SIMPLE_PATTERN *list) {
    if(!list) return;

    free_pattern(((struct simple_pattern *)list));
}

/* Debugging patterns

   This code should be dead - it is useful for debugging but should not be called by production code.
   Feel free to comment it out, but please leave it in the file.
*/
extern void simple_pattern_dump(uint64_t debug_type, SIMPLE_PATTERN *p)
{
    struct simple_pattern *root = (struct simple_pattern *)p;
    if(root==NULL) {
        netdata_log_debug(debug_type,"dump_pattern(NULL)");
        return;
    }
    netdata_log_debug(debug_type,"dump_pattern(%p) child=%p next=%p mode=%u match=%s", root, root->child, root->next, root->mode,
          root->match);
    if(root->child!=NULL)
        simple_pattern_dump(debug_type, (SIMPLE_PATTERN*)root->child);
    if(root->next!=NULL)
        simple_pattern_dump(debug_type, (SIMPLE_PATTERN*)root->next);
}

/* Heuristic: decide if the pattern could match a DNS name.

   Although this functionality is used directly by socket.c:connection_allowed() it must be in this file
   because of the SIMPLE_PATTERN/simple_pattern structure hiding.
   Based on RFC952 / RFC1123. We need to decide if the pattern may match a DNS name, or not. For the negative
   cases we need to be sure that it can only match an ipv4 or ipv6 address:
     * IPv6 addresses contain ':', which are illegal characters in DNS.
     * IPv4 addresses cannot contain alpha- characters.
     * DNS TLDs must be alphanumeric to distinguish from IPv4.
   Some patterns (e.g. "*a*" ) could match multiple cases (i.e. DNS or IPv6).
   Some patterns will be awkward (e.g. "192.168.*") as they look like they are intended to match IPv4-only
   but could match DNS (i.e. "192.168.com" is a valid name).
*/
static void scan_is_potential_name(struct simple_pattern *p, int *alpha, int *colon, int *wildcards)
{
    while (p) {
        if (p->match) {
            if(p->mode == SIMPLE_PATTERN_EXACT && !strcmp("localhost", p->match)) {
                p = p->child;
                continue;
            }
            char const *scan = p->match;
            while (*scan != 0) {
                if ((*scan >= 'a' && *scan <= 'z') || (*scan >= 'A' && *scan <= 'Z'))
                    *alpha = 1;
                if (*scan == ':')
                    *colon = 1;
                scan++;
            }
            if (p->mode != SIMPLE_PATTERN_EXACT)
                *wildcards = 1;
            p = p->child;
        }
    }
}

extern int simple_pattern_is_potential_name(SIMPLE_PATTERN *p)
{
    int alpha=0, colon=0, wildcards=0;
    struct simple_pattern *root = (struct simple_pattern*)p;
    while (root != NULL) {
        if (root->match != NULL) {
            scan_is_potential_name(root, &alpha, &colon, &wildcards);
        }
        if (root->mode != SIMPLE_PATTERN_EXACT)
            wildcards = 1;
        root = root->next;
    }
    return (alpha || wildcards) && !colon;
}

char *simple_pattern_iterate(SIMPLE_PATTERN **p)
{
    struct simple_pattern *root = (struct simple_pattern *) *p;
    struct simple_pattern **Proot = (struct simple_pattern **)p;

    (*Proot) = (*Proot)->next;
    return (char *) root->match;
}

bool simple_pattern_contains_wildcards(const char *str, const char *separators) {
    if(unlikely(!str || !*str)) return false;

    // Check if it starts with exclamation mark (and not escaped)
    if(*str == '!')
        return true;

    bool isseparator[256] = {
              [' ']  = true    // space
            , ['\t'] = true    // tab
            , ['\r'] = true    // carriage return
            , ['\n'] = true    // new line
            , ['\f'] = true    // form feed
            , ['\v'] = true    // vertical tab
    };

    if (unlikely(separators == SIMPLE_PATTERN_NO_SEPARATORS))
        memset(isseparator, false, sizeof(isseparator));

    else if (unlikely(separators && *separators)) {
        memset(isseparator, false, sizeof(isseparator));
        while(*separators) isseparator[(unsigned char)*separators++] = true;
    }

    // Check for separators or asterisks in the string, respecting escaping
    const char *s = str;
    char escape = 0;
    while(*s) {
        if(*s == '\\' && !escape) {
            escape = 1;
            s++;
        }
        else {
            if (!escape && (isseparator[(unsigned char)*s] || *s == '*'))
                return true;
            
            s++;
            escape = 0;
        }
    }

    return false;
}
