/***********************************************************************
 * Copyright (c) 2009, Secure Endpoints Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 **********************************************************************/

#include<config.h>

#include <stdlib.h>
#include <io.h>
#include <string.h>
#include <errno.h>
#include "dirent.h"

#ifndef _WIN32
#error Only implemented for Win32
#endif

struct _dirent_dirinfo {
    int             magic;
    long            n_entries;
    long            nc_entries;
    long            cursor;
    struct dirent **entries;
};
#define DIRINFO_MAGIC 0xf8c0639d
#define IS_DP(p) ((p) && ((DIR *)(p))->magic == DIRINFO_MAGIC)

#define INITIAL_ENTRIES 16

/**
 * Create a filespec for use with _findfirst() using a path spec
 *
 * If the last component of the path spec contains wildcards, we let
 * it be.  If the last component doesn't end with a slash, we add one.
 */
static const char *
filespec_from_dir_path(const char * path, char * buffer, size_t cch_buffer)
{
    char *comp, *t;
    size_t pos;
    int found_sep = 0;

    if (strcpy_s(buffer, cch_buffer, path) != 0)
        return NULL;

    comp = strrchr(buffer, '\\');
    if (comp == NULL)
        comp = buffer;
    else
        found_sep = 1;

    t = strrchr(comp, '/');
    if (t != NULL) {
        comp = t;
        found_sep = 1;
    }

    if (found_sep)
        comp++;

    pos = strcspn(comp, "*?");
    if (comp[pos] != '\0')
        return buffer;

    /* We don't append a slash if pos == 0 because that changes the
     * meaning:
     *
     * "*.*" is all files in the current directory.
     * "\*.*" is all files in the root directory of the current drive.
     */
    if (pos > 0 && comp[pos - 1] != '\\' &&
        comp[pos - 1] != '/') {
        strcat_s(comp, cch_buffer - (comp - buffer), "\\");
    }

    strcat_s(comp, cch_buffer - (comp - buffer), "*.*");

    return buffer;
}

ROKEN_LIB_FUNCTION DIR * ROKEN_LIB_CALL
opendir(const char * path)
{
    DIR *              dp;
    struct _finddata_t fd;
    intptr_t           fd_handle;
    const char         *filespec;
    char               path_buffer[1024]="";

    memset(&fd, 0, sizeof(fd));

    filespec = filespec_from_dir_path(path, path_buffer, sizeof(path_buffer)/sizeof(char));
    if (filespec == NULL)
        return NULL;

    fd_handle = _findfirst(filespec, &fd);

    if (fd_handle == -1)
        return NULL;

    dp = malloc(sizeof(*dp));
    if (dp == NULL)
        goto done;

    memset(dp, 0, sizeof(*dp));
    dp->magic      = DIRINFO_MAGIC;
    dp->cursor     = 0;
    dp->n_entries  = 0;
    dp->nc_entries = INITIAL_ENTRIES;
    dp->entries    = calloc(dp->nc_entries, sizeof(dp->entries[0]));

    if (dp->entries == NULL) {
        closedir(dp);
        dp = NULL;
        goto done;
    }

    do {
        size_t len = strlen(fd.name);
        struct dirent * e;

        if (dp->n_entries == dp->nc_entries) {
	    struct dirent ** ne;

            dp->nc_entries *= 2;
            ne = realloc(dp->entries, sizeof(dp->entries[0]) * dp->nc_entries);

            if (ne == NULL) {
                closedir(dp);
                dp = NULL;
                goto done;
            }

	    dp->entries = ne;
        }

        e = malloc(sizeof(*e) + len * sizeof(char));
        if (e == NULL) {
            closedir(dp);
            dp = NULL;
            goto done;
        }

        e->d_ino = 0;           /* no inodes :( */
        strcpy_s(e->d_name, len + 1, fd.name);

        dp->entries[dp->n_entries++] = e;

    } while (_findnext(fd_handle, &fd) == 0);

 done:
    if (fd_handle != -1)
        _findclose(fd_handle);

    return dp;
}

ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL
closedir(DIR * dp)
{
    if (!IS_DP(dp))
        return EINVAL;

    if (dp->entries) {
        long i;

        for (i=0; i < dp->n_entries; i++) {
            free(dp->entries[i]);
        }

        free(dp->entries);
    }

    free(dp);

    return 0;
}

ROKEN_LIB_FUNCTION struct dirent * ROKEN_LIB_CALL
readdir(DIR * dp)
{
    if (!IS_DP(dp) ||
        dp->cursor < 0 ||
        dp->cursor >= dp->n_entries)

        return NULL;

    return dp->entries[dp->cursor++];
}

ROKEN_LIB_FUNCTION void ROKEN_LIB_CALL
rewinddir(DIR * dp)
{
    if (IS_DP(dp))
        dp->cursor = 0;
}

ROKEN_LIB_FUNCTION void ROKEN_LIB_CALL
seekdir(DIR * dp, long offset)
{
    if (IS_DP(dp) && offset >= 0 && offset < dp->n_entries)
        dp->cursor = offset;
}

ROKEN_LIB_FUNCTION long ROKEN_LIB_CALL
telldir(DIR * dp)
{
    return dp->cursor;
}
