/*
 * Make sure that scandir function works OK.
 *
 * Copyright (C) 1998-2019 Toni Ronkko
 * This file is part of dirent.  Dirent may be freely distributed
 * under the MIT license.  For all details and documentation, see
 * https://github.com/tronkko/dirent
 */

/* Silence warning about fopen being insecure */
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <time.h>
#include <limits.h>

#undef NDEBUG
#include <assert.h>


/* Filter and sort functions */
static int only_readme (const struct dirent *entry);
static int no_directories (const struct dirent *entry);
static int reverse_alpha (const struct dirent **a, const struct dirent **b);


int
main(
    int argc, char *argv[])
{
    struct dirent **files;
    int i;
    int n;

    (void) argc;
    (void) argv;

    /* Initialize random number generator */
    srand ((unsigned) time (NULL));

    /* Basic scan with simple filter function */
    {
        /* Read directory entries */
        n = scandir ("tests/3", &files, only_readme, alphasort);
        assert (n == 1);

        /* Make sure that the filter works */
        assert (strcmp (files[0]->d_name, "README.txt") == 0);

        /* Release file names */
        for (i = 0; i < n; i++) {
            free (files[i]);
        }
        free (files);
    }

    /* Basic scan with default sorting function */
    {
        /* Read directory entries in alphabetic order */
        n = scandir ("tests/3", &files, NULL, alphasort);
        assert (n == 13);

        /* Make sure that we got all the file names in the proper order */
        assert (strcmp (files[0]->d_name, ".") == 0);
        assert (strcmp (files[1]->d_name, "..") == 0);
        assert (strcmp (files[2]->d_name, "3zero.dat") == 0);
        assert (strcmp (files[3]->d_name, "666.dat") == 0);
        assert (strcmp (files[4]->d_name, "Qwerty-my-aunt.dat") == 0);
        assert (strcmp (files[5]->d_name, "README.txt") == 0);
        assert (strcmp (files[6]->d_name, "aaa.dat") == 0);
        assert (strcmp (files[7]->d_name, "dirent.dat") == 0);
        assert (strcmp (files[8]->d_name, "empty.dat") == 0);
        assert (strcmp (files[9]->d_name, "sane-1.12.0.dat") == 0);
        assert (strcmp (files[10]->d_name, "sane-1.2.3.dat") == 0);
        assert (strcmp (files[11]->d_name, "sane-1.2.4.dat") == 0);
        assert (strcmp (files[12]->d_name, "zebra.dat") == 0);

        /* Release file names */
        for (i = 0; i < n; i++) {
            free (files[i]);
        }
        free (files);
    }

    /* Custom filter AND sort function */
    {
        /* Read directory entries in alphabetic order */
        n = scandir ("tests/3", &files, no_directories, reverse_alpha);
        assert (n == 11);

        /* Make sure that we got all the FILE names in the REVERSE order */
        assert (strcmp (files[0]->d_name, "zebra.dat") == 0);
        assert (strcmp (files[1]->d_name, "sane-1.2.4.dat") == 0);
        assert (strcmp (files[2]->d_name, "sane-1.2.3.dat") == 0);
        assert (strcmp (files[3]->d_name, "sane-1.12.0.dat") == 0);
        assert (strcmp (files[4]->d_name, "empty.dat") == 0);
        assert (strcmp (files[5]->d_name, "dirent.dat") == 0);
        assert (strcmp (files[6]->d_name, "aaa.dat") == 0);
        assert (strcmp (files[7]->d_name, "README.txt") == 0);
        assert (strcmp (files[8]->d_name, "Qwerty-my-aunt.dat") == 0);
        assert (strcmp (files[9]->d_name, "666.dat") == 0);
        assert (strcmp (files[10]->d_name, "3zero.dat") == 0);

        /* Release file names */
        for (i = 0; i < n; i++) {
            free (files[i]);
        }
        free (files);
    }

    /* Trying to read from non-existent directory leads to an error */
    {
        files = NULL;
        n = scandir ("tests/invalid", &files, NULL, alphasort);
        assert (n == -1);
        assert (files == NULL);
        assert (errno == ENOENT);
    }

    /* Trying to open file as a directory produces ENOTDIR error */
    {
        files = NULL;
        n = scandir ("tests/3/666.dat", &files, NULL, alphasort);
        assert (n == -1);
        assert (files == NULL);
        assert (errno == ENOTDIR);
    }

    /* Scan large directory */
    {
        char dirname[PATH_MAX+1];
        int i, j;
        int ok;

        /* Copy name of temporary directory to variable dirname */
#ifdef WIN32
        i = GetTempPathA (PATH_MAX, dirname);
        assert (i > 0);
#else
        strcpy (dirname, "/tmp/");
        i = strlen (dirname);
#endif

        /* Append random characters to dirname */
        for (j = 0; j < 10; j++) {
            char c;

            /* Generate random character */
            c = "abcdefghijklmnopqrstuvwxyz"[rand() % 26];

            /* Append character to dirname */
            assert (i < PATH_MAX);
            dirname[i++] = c;
        }

        /* Terminate directory name */
        assert (i < PATH_MAX);
        dirname[i] = '\0';

        /* Create directory */
#ifdef WIN32
        ok = CreateDirectoryA (dirname, NULL);
        assert (ok);
#else
        ok = mkdir (dirname, 0700);
        assert (ok == /*success*/0);
#endif

        /* Create one thousand files */
        assert (i + 5 < PATH_MAX);
        for (j = 0; j < 1000; j++) {
            FILE *fp;

            /* Construct file name */
            dirname[i] = '/';
            dirname[i+1] = 'z';
            dirname[i+2] = '0' + ((j / 100) % 10);
            dirname[i+3] = '0' + ((j / 10) % 10);
            dirname[i+4] = '0' + (j % 10);
            dirname[i+5] = '\0';

            /* Create file */
            fp = fopen (dirname, "w");
            assert (fp != NULL);
            fclose (fp);

        }

        /* Cut out the file name part */
        dirname[i] = '\0';

        /* Scan directory */
        n = scandir (dirname, &files, no_directories, alphasort);
        assert (n == 1000);

        /* Make sure that all 1000 files are read back */
        for (j = 0; j < n; j++) {
            char match[100];

            /* Construct file name */
            match[0] = 'z';
            match[1] = '0' + ((j / 100) % 10);
            match[2] = '0' + ((j / 10) % 10);
            match[3] = '0' + (j % 10);
            match[4] = '\0';

            /* Make sure that file name matches that on the disk */
            assert (strcmp (files[j]->d_name, match) == 0);

        }

        /* Release file names */
        for (j = 0; j < n; j++) {
            free (files[j]);
        }
        free (files);
    }

    printf ("OK\n");
    return EXIT_SUCCESS;
}

/* Only pass README.txt file */
static int
only_readme (const struct dirent *entry)
{
    int pass;

    if (strcmp (entry->d_name, "README.txt") == 0) {
        pass = 1;
    } else {
        pass = 0;
    }

    return pass;
}

/* Filter out directories */
static int
no_directories (const struct dirent *entry)
{
    int pass;

    if (entry->d_type != DT_DIR) {
        pass = 1;
    } else {
        pass = 0;
    }

    return pass;
}

/* Sort in reverse direction */
static int
reverse_alpha(
    const struct dirent **a, const struct dirent **b)
{
    return strcoll ((*b)->d_name, (*a)->d_name);
}
