/** \file globbing.c */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

#include "globbing.h"

/**
 * Please see globbing.h for details.
 *
 * License: this code is public domain. I make no warranty that it is useful,
 *  correct, harmless, or environmentally safe.
 *
 * This particular file may be used however you like, including copying it
 *  verbatim into a closed-source project, exploiting it commercially, and
 *  removing any trace of my name from the source (although I hope you won't
 *  do that). I welcome enhancements and corrections to this file, but I do
 *  not require you to send me patches if you make changes. This code has
 *  NO WARRANTY.
 *
 * Unless otherwise stated, the rest of PhysicsFS falls under the zlib license.
 *  Please see the file LICENSE.txt in the source's root directory.
 *
 *  \author Ryan C. Gordon.
 */


static int matchesPattern(const char *fname, const char *wildcard,
                          int caseSensitive)
{
    char x, y;
    const char *fnameptr = fname;
    const char *wildptr = wildcard;

    while ((*wildptr) && (*fnameptr))
    {
        y = *wildptr;
        if (y == '*')
        {
            do
            {
                wildptr++;  /* skip multiple '*' in a row... */
            } while (*wildptr == '*');

            y = (caseSensitive) ? *wildptr : (char) tolower(*wildptr);

            while (1)
            {
                x = (caseSensitive) ? *fnameptr : (char) tolower(*fnameptr);
                if ((!x) || (x == y))
                    break;
                else
                    fnameptr++;
            } /* while */
        } /* if */

        else if (y == '?')
        {
            wildptr++;
            fnameptr++;
        } /* else if */

        else
        {
            if (caseSensitive)
                x = *fnameptr;
            else
            {
                x = tolower(*fnameptr);
                y = tolower(y);
            } /* if */

            wildptr++;
            fnameptr++;

            if (x != y)
                return 0;
        } /* else */
    } /* while */

    while (*wildptr == '*')
        wildptr++;

    return (*fnameptr == *wildptr);
} /* matchesPattern */

typedef struct
{
    const PHYSFS_Allocator *allocator;
    const char *wildcard;
    int caseSensitive;
    PHYSFS_EnumFilesCallback callback;
    void *origData;
} WildcardCallbackData;


/*
 * This callback sits between the enumerator and the enduser callback,
 *  filtering out files that don't match the wildcard pattern.
 */
static void wildcardCallback(void *_d, const char *origdir, const char *fname)
{
    const WildcardCallbackData *data = (const WildcardCallbackData *) _d;
    if (matchesPattern(fname, data->wildcard, data->caseSensitive))
        data->callback(data->origData, origdir, fname);
} /* wildcardCallback */


void PHYSFSEXT_enumerateFilesCallbackWildcard(const char *dir,
                                              const char *wildcard,
                                              int caseSensitive,
                                              PHYSFS_EnumFilesCallback c,
                                              void *d)
{
    WildcardCallbackData data;
    data.allocator = PHYSFS_getAllocator();
    data.wildcard = wildcard;
    data.caseSensitive = caseSensitive;
    data.callback = c;
    data.origData = d;
    PHYSFS_enumerateFilesCallback(dir, wildcardCallback, &data);
} /* PHYSFSEXT_enumerateFilesCallbackWildcard */


void PHYSFSEXT_freeEnumeration(char **list)
{
    const PHYSFS_Allocator *allocator = PHYSFS_getAllocator();
    int i;
    if (list != NULL)
    {
        for (i = 0; list[i] != NULL; i++)
            allocator->Free(list[i]);
        allocator->Free(list);
    } /* if */
} /* PHYSFSEXT_freeEnumeration */


char **PHYSFSEXT_enumerateFilesWildcard(const char *dir, const char *wildcard,
                                        int caseSensitive)
{
    const PHYSFS_Allocator *allocator = PHYSFS_getAllocator();
    char **list = PHYSFS_enumerateFiles(dir);
    char **retval = NULL;
    int totalmatches = 0;
    int matches = 0;
    char **i;

    for (i = list; *i != NULL; i++)
    {
        #if 0
        printf("matchesPattern: '%s' vs '%s' (%s) ... %s\n", *i, wildcard,
               caseSensitive ? "case" : "nocase",
               matchesPattern(*i, wildcard, caseSensitive) ? "true" : "false");
        #endif
        if (matchesPattern(*i, wildcard, caseSensitive))
            totalmatches++;
    } /* for */

    retval = (char **) allocator->Malloc(sizeof (char *) * (totalmatches+1));
    if (retval != NULL)
    {
        for (i = list; ((matches < totalmatches) && (*i != NULL)); i++)
        {
            if (matchesPattern(*i, wildcard, caseSensitive))
            {
                retval[matches] = (char *) allocator->Malloc(strlen(*i) + 1);
                if (retval[matches] == NULL)
                {
                    while (matches--)
                        allocator->Free(retval[matches]);
                    allocator->Free(retval);
                    retval = NULL;
                    break;
                } /* if */
                strcpy(retval[matches], *i);
                matches++;
            } /* if */
        } /* for */

        if (retval != NULL)
        {
            assert(totalmatches == matches);
            retval[matches] = NULL;
        } /* if */
    } /* if */

    PHYSFS_freeList(list);
    return retval;
} /* PHYSFSEXT_enumerateFilesWildcard */


#ifdef TEST_PHYSFSEXT_ENUMERATEFILESWILDCARD
int main(int argc, char **argv)
{
    int rc;
    char **flist;
    char **i;

    if (argc != 3)
    {
        printf("USAGE: %s <pattern> <caseSen>\n"
               "   where <caseSen> is 1 or 0.\n", argv[0]);
        return 1;
    } /* if */

    if (!PHYSFS_init(argv[0]))
    {
        fprintf(stderr, "PHYSFS_init(): %s\n", PHYSFS_getLastError());
        return 1;
    } /* if */

    if (!PHYSFS_addToSearchPath(".", 1))
    {
        fprintf(stderr, "PHYSFS_addToSearchPath(): %s\n", PHYSFS_getLastError());
        PHYSFS_deinit();
        return 1;
    } /* if */

    flist = PHYSFSEXT_enumerateFilesWildcard("/", argv[1], atoi(argv[2]));
    rc = 0;
    for (i = flist; *i; i++)
    {
        printf("%s\n", *i);
        rc++;
    } /* for */
    printf("\n  total %d files.\n\n", rc);

    PHYSFSEXT_freeEnumeration(flist);
    PHYSFS_deinit();

    return 0;
} /* main */
#endif

/* end of globbing.c ... */