#include <stdint.h>
#include <string.h>

#include "fs_private.h"

enum {
    FS_UNIQUE_STRING_LIST_STACK_CAPACITY = 4099,
};

char **FS_ListFilteredFiles(const char *path, const char *extension,
                            const char *filter, int *numfiles)
{
    char osPath[FS_OSPATH_SIZE];
    char strippedName[CODUO_MAX_QPATH];
    int listedCount;
    const char *pakName;
    qboolean listFolders;
    char directory[FS_OSPATH_SIZE];
    pack_t *pack;
    int pathDepth;
    int filenameDepth;
    int filenameBaseLength;
    int pakNameLength;
    int extensionLength;
    int pathLength;
    size_t relativeOffset;
    char **sysFiles;
    char *list[FS_UNIQUE_STRING_LIST_STACK_CAPACITY];
    const fileInPack_t *packFiles;
    int count;
    int index;
    searchpath_t *search;

    FS_CheckFileSystemStarted();

    if (path == NULL) {
        *numfiles = 0;
        return NULL;
    }

    if (extension == NULL) {
        extension = "";
    }

    if (*path == '/' || *path == '\\') {
        path++;
    }

    /* ORIGINAL_BINARY_BUG: stock strips at most one leading separator and
     * does not reject parent components before the host-directory branch;
     * see original-bugs/coduomp-file-list-path-traversal.md. */
    listFolders = (Q_stricmp(extension, "/") == 0);
    pathLength = (int)strlen(path);
    if (pathLength != 0 &&
        (path[pathLength - 1] == '\\' || path[pathLength - 1] == '/')) {
        pathLength--;
    }
    extensionLength = (int)strlen(extension);
    count = 0;

    FS_SplitPathDirectory(path, directory, &pathDepth);
    if (*path != '\0') {
        pathDepth++;
    }

    for (search = FS_SearchpathHead(); search != NULL;
         search = FS_SearchpathNext(search)) {
        if (FS_UseSearchPath(search) == 0) {
            continue;
        }

        if (search->pack != NULL) {
            if (search->localized == 0 &&
                FS_PakIsPure(search->pack) == 0) {
                continue;
            }

            pack = search->pack;
            packFiles = pack->fileList;
            for (index = 0; index < pack->numFiles; index++) {
                pakName = packFiles[index].name;

                if (filter != NULL) {
                    if (Com_FilterPath(filter, pakName, 0) != 0) {
                        count = FS_AddFileToList(pakName, list, count);
                    }
                    continue;
                }

                filenameBaseLength =
                    FS_SplitPathDirectory(pakName, directory, &filenameDepth);
                if (filenameDepth != pathDepth ||
                    pathLength > filenameBaseLength ||
                    (pathLength > 0 && pakName[pathLength] != '/') ||
                    Q_stricmpn(pakName, path, pathLength) != 0) {
                    continue;
                }

                pakNameLength = (int)strlen(pakName);
                if (pakNameLength < extensionLength ||
                    Q_stricmp(pakName + (pakNameLength - extensionLength),
                              extension) != 0) {
                    continue;
                }

                relativeOffset = pathLength;
                if (pathLength != 0) {
                    relativeOffset++;
                }

                if (listFolders == 0) {
                    count =
                        FS_AddFileToList(pakName + relativeOffset, list, count);
                } else {
                    /* ORIGINAL_BINARY_BUG: a long relative pak name is copied
                     * without a bound into strippedName; see
                     * original-bugs/coduomp-file-list-path-stack-overflow.md. */
                    strcpy(strippedName, pakName + relativeOffset);
                    strippedName[strlen(strippedName) - 1] = '\0';
                    count = FS_AddFileToList(strippedName, list, count);
                }
            }
        } else if (search->dir != NULL &&
                   ((fs_restrict->integer == 0 &&
                     fs_numServerPaks == 0) ||
                    Q_stricmp(extension, "svg") == 0)) {
            directory_t *dir;

            dir = search->dir;
            FS_BuildOSPath(dir->path, dir->gamedir, path, osPath);
            sysFiles = Sys_ListFiles(osPath, extension, filter, &listedCount,
                                     listFolders);
            for (index = 0; index < listedCount; index++) {
                count = FS_AddFileToList(sysFiles[index], list, count);
            }
            Sys_FreeFileList(sysFiles);
        }
    }

    *numfiles = count;
    if (count == 0) {
        return NULL;
    }

    sysFiles = Z_Malloc(((size_t)count + 1U) * sizeof(*sysFiles));
    for (index = 0; index < count; index++) {
        sysFiles[index] = list[index];
    }
    sysFiles[index] = NULL;

    return sysFiles;
}
