/* dirlist-recursive.m,
 *
 * This directory listing lists all the files inside a directory and
 * it's sub-directories.  Directory names are appended a '/' to the
 * end (like zziplib).  './' and '../' are ignored.
 */

#include <assert.h>
#include <stdlib.h>
#ifdef __MINGW__
# include <allegro.h>
#else
# include <dirent.h>
#endif
#include "dirlist-recursive.h"
#include "seborrhea.h"


#ifndef PATH_MAX
# define PATH_MAX		512
#endif


@implementation RecursiveDirectoryList
- (int) appendEntry:(void *)entry WithDirectory:(int)sub_dir
{
#ifdef __MINGW__
# define ENTRY_NAME		ent->name
    struct al_ffblk *ent = entry;
#else  /* Linux */
# define ENTRY_NAME		ent->d_name
    struct dirent *ent = entry;
#endif

    int index = [super appendEntry:entry];

    if (sub_dir >= 0) {
	snprintf(entries[index].d_name, sizeof entries[nentries].d_name, "%s%s",
		 entries[sub_dir].d_name, ENTRY_NAME);
    }

    return index;

#undef ENTRY_NAME
}

#ifdef __MINGW__
- (void *) openDirectory:(const char *)top_dir :(int)sub_dir
{
    struct al_ffblk *ent  = malloc(sizeof(*ent));
    char pattern[PATH_MAX];
    int attrib = (FA_RDONLY|FA_SYSTEM|FA_LABEL|FA_DIREC|FA_ARCH);
    assert(ent);
    
    /* Note: we need the '/' after sub_dir's name. */
    if (sub_dir < 0)
	snprintf(pattern, sizeof pattern, "%s*.*", top_dir);
    else
	snprintf(pattern, sizeof pattern, "%s%s/*.*", top_dir, entries[sub_dir].d_name);
    
    if (al_findfirst(pattern, ent, attrib) == 0)
	return ent;
    else
	return NULL;
}

- (void) closeDirectory:(void *)dir_
{
    struct al_ffblk *ent = dir_;
    
    if (ent) {
	al_findclose(ent);
	free(ent);
    }
}

#else  /* Linux */
- (void *) openDirectory:(const char *)top_dir :(int)sub_dir
{
    char dirname[PATH_MAX];
    assert(top_dir);
    
    if (sub_dir < 0)
	snprintf(dirname, sizeof dirname, "%s", top_dir);
    else
	snprintf(dirname, sizeof dirname, "%s%s", top_dir, entries[sub_dir].d_name);

    return opendir(dirname);
}

- (void) closeDirectory:(void *)dir_
{
    DIR *dir = dir_;

    if (dir)
	closedir(dir);
}
#endif

- (void) diveIntoSubdirectory:(const char *)top_dir :(int)sub_dir
{
    /* Is probably a directory since symlinks are DT_LNK. */
    if ([self isDirectory:sub_dir]) {
	void *new_dir;

	new_dir = [self openDirectory:top_dir :sub_dir];
	if (not new_dir)
	    return;

	/* This is a directory.  Add a / to the directory name to be
	   consistent, then read its contents. */
	strncat(entries[sub_dir].d_name, "/", sizeof entries[sub_dir].d_name);
	[self readDirectory:new_dir :top_dir :sub_dir];
	[self closeDirectory:new_dir];
    }
}

- (void) readDirectory:(void *)dir_ :(const char *)top_dir :(int)sub_dir
{
    int index;

#ifdef __MINGW__
    struct al_ffblk *ent = dir_;
    assert(ent && top_dir);

    do {
	if (filter && filter(ent) == 0)
	    continue;

	index = [self appendEntry:ent WithDirectory:sub_dir];

	if ([self isDirectory:index])
	    [self diveIntoSubdirectory:top_dir :index];
    } while (al_findnext(ent) == 0);
#else  /* Linux */
    struct dirent *ent;
    DIR *dir = dir_;
    assert(dir && top_dir);

    while ((ent = readdir(dir))) {
	if (filter && filter(ent) == 0)
	    continue;

	index = [self appendEntry:ent WithDirectory:sub_dir];

	if ([self isDirectory:index])
		[self diveIntoSubdirectory:top_dir :index];
    }
#endif
}

- (BOOL) scanDirectory:(const char *)dirname
{
    void *dir;
    assert(dirname);
    assert(not entries && nentries == 0);

    dir = [self openDirectory:dirname :-1];
    if (not dir)
	return NO;

    [self readDirectory:dir :dirname :-1];
    [self closeDirectory:dir];
    [self sort];
    return YES;
}
@end
