/* seborrhea-dirlist.m,
 */

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


@implementation DirectoryList
static int directory_list_filter_hidden(const FILTER_TYPE *entry)
{
#ifdef __MINGW__
# define ENTRY_NAME		ent->name
        const struct al_ffblk *ent = entry;
#else  /* Linux. */
# define ENTRY_NAME		entry->d_name
#endif
    assert(entry);

    /* Filter out ./, ../, hidden files. */
    if (ENTRY_NAME[0] == '.')
        return 0;
    else
        return 1;

#undef ENTRY_NAME
}

static int directory_list_alpha_sort(const void *a, const void *b)
{
    const dirent_t *da = a;
    const dirent_t *db = b;
    return strcmp(da->d_name, db->d_name);
}

- init
{
    [super init];
    filter = directory_list_filter_hidden;
    sorter = directory_list_alpha_sort;
    return self;
}

- (int) appendEntry:(void *)entry
{
#ifdef __MINGW__
# define ENTRY_NAME(ent)	ent->name
# define ENTRY_TYPE(ent)	(ent->attrib & FA_DIREC) ? DT_DIR : DT_REG;
    struct al_ffblk *ent = entry;
#else  /* Linux */
# define ENTRY_NAME(ent)	ent->d_name
# define ENTRY_TYPE(ent)	ent->d_type
    struct dirent *ent = entry;
#endif
    assert(ent);
    
    if (nentries_allocated <= nentries) {
	entries = realloc(entries, (nentries+1)*sizeof(*entries));
	assert(entries && "[Dirlist] Error reallocating space for entries!");
	nentries_allocated = nentries+1;
    }
    
    strncpy(entries[nentries].d_name, ENTRY_NAME(ent), sizeof entries[nentries].d_name);
    entries[nentries].d_type = ENTRY_TYPE(ent);
    entries[nentries].loaded = NO;
    entries[nentries].st_size = -1;
    
    nentries++;
    
    /* Returns the index the entry was stuck into. */
    return nentries-1;

#undef ENTRY_TYPE
#undef ENTRY_NAME
}

- freeEntries
{
    if (entries) {
	free(entries);
	entries = NULL;
    }

    if (working_directory) {
	free(working_directory);
	working_directory = NULL;
    }

    nentries = 0;
    nentries_allocated = 0;
    return self;
}

- free
{
    [self freeEntries];
    return [super free];
}

- (void) sort
{
    if (not entries)
	return;

    if (sorter && nentries > 1)
	qsort(entries, nentries, sizeof(*entries), sorter);
}

- (BOOL) scanDirectory:(const char *)dirname
{
#ifdef __MINGW__
    struct al_ffblk *ent = malloc(sizeof(*ent));
    char pattern[1024];
    int attrib = (FA_RDONLY|FA_SYSTEM|FA_LABEL|FA_DIREC|FA_ARCH);
    assert(dirname && ent);
    assert(not entries && nentries == 0);

    snprintf(pattern, sizeof pattern, "%s/*.*", dirname);
    if (al_findfirst(pattern, ent, attrib) != 0)
	return NO;

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

	[self appendEntry:ent];
    } while (al_findnext(ent) == 0);

    al_findclose(ent);
    free(ent);
#else  /* Linux */
    struct dirent **ents;
    int n;
    assert(dirname);
    assert(not entries && nentries == 0);

    n = scandir(dirname, &ents, filter, NULL);
    if (n == -1)
	return NO;

    /* Do manual entries allocation because we know how many elements
       we need. */
    nentries_allocated = n;
    entries = malloc(nentries_allocated*sizeof(*entries));
    assert(entries);

    for (n--; n >= 0; n--)
	[self appendEntry:ents[n]];

    free(ents);
#endif

    [self sort];
    working_directory = strdup(dirname);
    return YES;
}

- (BOOL) isFile:(unsigned int)nth
{
    assert(nth < nentries);

#ifdef __LINUX__
    /* Extra checks for symlinks. */
    if ([self isLink:nth]) {
	char link_src[PATH_MAX];
	struct stat status;

	snprintf(link_src, sizeof link_src, "%s/%s", working_directory, entries[nth].d_name);
	if ((lstat(link_src, &status) == 0) &&
	    (S_ISREG(status.st_mode)))
	    return YES;
	else
	    return NO;
    }
#endif

    return ((entries[nth].d_type == DT_REG) ||
	    (entries[nth].d_type == DT_UNKNOWN));
}

- (BOOL) isDirectory:(unsigned int)nth
{
    assert(nth < nentries);

#ifdef __LINUX__
    /* Extra checks for symlinks. */
    if ([self isLink:nth]) {
	char link_src[PATH_MAX];
	struct stat status;

	snprintf(link_src, sizeof link_src, "%s/%s", working_directory, entries[nth].d_name);
	if ((stat(link_src, &status) == 0) &&
	    (S_ISDIR(status.st_mode)))
	    return YES;
	return NO;
    }
#endif

    return ((entries[nth].d_type == DT_DIR) || 
	    (entries[nth].d_type == DT_UNKNOWN));
}

- (BOOL) isLink:(unsigned int)nth 
{
    assert(nth < nentries);
    return (entries[nth].d_type == DT_LNK);
}

+ (BOOL) isReal { return YES; }

- (void *) openFile:(const char *)filename
{
    assert(filename);
    return fopen(filename, "rt");
}

- (void) closeFile:(void *)file
{
    FILE *fp = file;
    if (fp)
	fclose(fp);
}

- (unsigned int) numEntries { return nentries; }

- (dirent_t *) getNthEntry:(unsigned int)nth
{
    assert(nth < nentries);
    return &entries[nth];
}

- (BOOL) entryIsLoaded:(unsigned int)nth
{
    assert(nth < nentries);
    return entries[nth].loaded;
}

- (void) markEntryLoaded:(unsigned int)nth
{
    assert(nth < nentries);
    entries[nth].loaded = YES;
}
@end
