/* Icecast
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
 * Copyright 2018,      Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <stdlib.h>
#include <errno.h>

#include "resourcematch.h"

static size_t count_groups(const char *pattern)
{
    size_t ret = 0;

    while (*pattern) {
        for (; *pattern && *pattern != '%'; pattern++);

        if (!*pattern) {
            return ret;
        }

        pattern++;

        if (!*pattern)
            return ret;

        if (*pattern != '%')
            ret++;

        pattern++;
    }

    return ret;
}

static resourcematch_extract_t * allocate_extract(const char *pattern)
{
    size_t groups = count_groups(pattern);
    resourcematch_extract_t *ret;

    ret = calloc(1, sizeof(*ret));
    if (!ret)
        return NULL;

    ret->groups = groups;
    ret->group = calloc(groups, sizeof(*ret->group));

    return ret;
}

static void strip_common_prefix(const char **pattern, const char **string)
{
    const char *p = *pattern;
    const char *s = *string;

    for (; *p && *p != '%' && *p == *s; p++, s++);

    *pattern = p;
    *string = s;
}

static inline void setup_group(resourcematch_extract_t *extract, size_t idx, char type)
{
    if (!extract)
        return;

    extract->group[idx].type = type;
    extract->group[idx].raw = NULL;
}

static inline resourcematch_result_t match_lli(const char **string, resourcematch_extract_t *extract, size_t idx, int base)
{
    long long int ret;
    char *endptr;

    errno = 0;
    ret = strtoll(*string, &endptr, base);
    if (endptr == *string)
        return RESOURCEMATCH_NOMATCH;
    if (errno != 0)
        return RESOURCEMATCH_ERROR;

    if (extract) {
        extract->group[idx].result.lli = ret;
    }

    *string = endptr;

    return RESOURCEMATCH_MATCH;
}

static inline void match_s(const char **string, resourcematch_extract_t *extract, size_t idx)
{
    if (extract) {
        extract->group[idx].raw = strdup(*string);
        extract->group[idx].result.string = extract->group[idx].raw;
    }
    *string += strlen(*string);
}

resourcematch_result_t resourcematch_match(const char *pattern, const char *string, resourcematch_extract_t **extract)
{
    resourcematch_result_t ret;
    resourcematch_extract_t *matches = NULL;
    size_t idx = 0;

    if (!pattern || !string)
        return RESOURCEMATCH_ERROR;

    if (extract) {
        matches = allocate_extract(pattern);
        if (!matches)
            return RESOURCEMATCH_NOMATCH;
    }

    while (1) {
        strip_common_prefix(&pattern, &string);

        if (!*pattern && !*string) {
            if (extract)
                *extract = matches;

            return RESOURCEMATCH_MATCH;
        } else if (!*pattern || !*string) {
            if (extract)
                resourcematch_extract_free(matches);

            return RESOURCEMATCH_NOMATCH;
        }

        if (*pattern != '%') {
            if (extract)
                resourcematch_extract_free(matches);

            return RESOURCEMATCH_NOMATCH;
        }

        pattern++;

        switch (*pattern) {
            case '%':
                if (*string == '%') {
                    string++;
                } else {
                    if (extract)
                        resourcematch_extract_free(matches);

                    return RESOURCEMATCH_NOMATCH;
                }
            break;
#define _test_int(type,base) \
            case (type): \
                setup_group(matches, idx, *pattern); \
\
                ret = match_lli(&string, matches, idx, (base)); \
                if (ret != RESOURCEMATCH_MATCH) { \
                    if (extract) \
                        resourcematch_extract_free(matches); \
\
                    return ret; \
                } \
                idx++; \
            break;

            _test_int('i', 0);
            _test_int('d', 10);
            _test_int('x', 16);
            _test_int('o', 8);

            case 's':
                setup_group(matches, idx, *pattern);
                match_s(&string, matches, idx);
                idx++;
            break;
            default:
                if (extract)
                    resourcematch_extract_free(matches);

                return RESOURCEMATCH_ERROR;
            break;
        }

        pattern++;
    }
}

void resourcematch_extract_free(resourcematch_extract_t *extract)
{
    size_t i;

    if (!extract)
        return;

    for (i = 0; i < extract->groups; i++) {
        free(extract->group[i].raw);
    }

    free(extract->group);
    free(extract);
}
