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

#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <glob.h>
#include <string.h>
#include <stdlib.h>

#include "util.h"
#include "glob_portable.h"

pattern_list expand_brace_pattern(const char *pattern) {
    pattern_list list = {NULL, 0};
    const char *open = strchr(pattern, '{');
    const char *close = open ? strchr(pattern, '}') : NULL;

    if (!open || !close) {
        list.patterns = malloc(sizeof(char*));
        list.patterns[0] = xstrdup(pattern);
        list.count = 1;
        return list;
    }

    char *brace = xstrndup(open+1, close-open-1);
    char *prefix = xstrndup(pattern, open-pattern);
    char *suffix = xstrdup(close+1);

    size_t comma_count = 1;
    for (char *p = brace; *p; p++) {
        if (*p == ',')
            comma_count++;
    }

    list.patterns = malloc(comma_count*sizeof(char*));
    list.count = comma_count;

    char *saveptr;
    char *token = strtok_r(brace, ",", &saveptr);
    size_t i = 0;
    while (token && i < comma_count) {
        size_t len = strlen(prefix) + strlen(token) + strlen(suffix) + 1;
        list.patterns[i] = malloc(len);
        snprintf(list.patterns[i], len, "%s%s%s", prefix, token, suffix);
        i++;
        token = strtok_r(NULL, ",", &saveptr);
    }

    free(brace);
    free(prefix);
    free(suffix);
    return list;
}

int glob_brace(const char *pattern, glob_t *pglob, int flags,
               int (*errfunc)(const char *epath, int eerrno)) {
    pattern_list patterns = expand_brace_pattern(pattern);

    int result = GLOB_NOMATCH;

    for (size_t i = 0; i < patterns.count; i++) {
        if (i == 0) {
            result = glob(patterns.patterns[i], flags, errfunc, pglob);
        } else {
            result = glob(patterns.patterns[i], flags | GLOB_APPEND, errfunc, pglob);
        }
        free(patterns.patterns[i]);
    }
    free(patterns.patterns);

    return result;
}

static bool root_is_real(const char *root) {
    if (!root || !*root)
        return true;
    if (root[0] == '/' && root[1] == '\0')
        return true;
    return false;
}

int glob_in_root(const char *pattern, const char *root,
                 int (*cb)(const char *, void *), void *userdata) {
    if (!pattern || !*pattern || !cb)
        return -EINVAL;

    bool have_root = !root_is_real(root);

    _cleanup_free_ char *root_norm = NULL;
    size_t root_len = 0;
    if (have_root) {
        root_norm = xstrdup(root);
        path_simplify(root_norm);
        if (root_norm[0] == '/' && root_norm[1] == '\0') {
            free(root_norm);
            root_norm = NULL;
            have_root = false;
        } else {
            root_len = strlen(root_norm);
        }
    }

    _cleanup_free_ char *rooted = NULL;
    const char *eff_pattern;
    if (have_root) {
        rooted = path_join(root_norm, pattern);
        eff_pattern = rooted;
    } else {
        eff_pattern = pattern;
    }

    glob_t g = {0};
    int gr = glob_brace(eff_pattern, &g, GLOB_NOSORT, NULL);
    if (gr == GLOB_NOMATCH) {
        globfree(&g);
        return 0;
    }
    if (gr != 0) {
        globfree(&g);
        return -EINVAL;
    }

    int ret = 0;
    for (size_t j = 0; j < g.gl_pathc; j++) {
        const char *match = g.gl_pathv[j];
        const char *logical;

        if (have_root) {
            if (strncmp(match, root_norm, root_len) == 0) {
                logical = match + root_len;
                if (*logical == '\0')
                    logical = "/";
            } else {
                logical = match;
            }
        } else {
            logical = match;
        }

        int q = cb(logical, userdata);
        if (q < 0 && ret == 0)
            ret = q;
    }
    globfree(&g);
    return ret;
}
