/* container-file.m,
 *
 * A basic container that allows us to find an item by its name.
 */

#include <assert.h>
#include "container-file.h"
#include "seborrhea-debug.h"
#include "zzpk.h"


#ifndef PATH_MAX
# define PATH_MAX		512
#endif


@implementation SebFile
- init
{
    [super init];
    process_directories = YES;
    return self;
}

- (BOOL) addToList:(Sebum *)child
{
    unsigned int i;
    const char *child_name;
    assert(child);

    child_name = [child name];

    /* Check for duplicate names. */
    for (i = 0; i < nelements; i++) {
	if (streq(child_name, [element[i] name])) {
	    fprintf(stderr, "[SebFile] Warning: duplicated name (%s) in %s\n", child_name, [self name]);
	    return NO;
	}
    }

    SEBORRHEA_MESSAGE(stdout, "[SebFile] Adding: '%s' (%s)\n", child_name, [[child class] name]);
    return [super addToList:child];
}

	/* Contents file. */
- (BOOL) assertContentsOfContainer:(const char *)top_dir :(const char *)sub_dir :(DirectoryList *)dirlist
{
    char contentsfn[PATH_MAX];
    void *fp;
    assert(top_dir && sub_dir && dirlist);

    /* Check (optional) =contents file. */
    /* XXX: Should check if file exists, return YES if it doesn't, and
       NO if it couldn't open. */
    snprintf(contentsfn, sizeof contentsfn, "%s%s=contents", top_dir, sub_dir);
    fp = [dirlist openFile:contentsfn];
    if (not fp)
	return YES;

    SEBORRHEA_MESSAGE(stdout, "[SebFile] Reading %s\n", contentsfn);

    if (not [self seekInFile:fp For:"contents" WithDirList:dirlist])
	goto exit_ok;

    while (not expect_char(fp, ')')) {
	Token *tok = read_string(fp);
	const char *sebum_name;

	if (not tok)
	    goto buggy;

	sebum_name = [tok getString];

	if (not [self getSebumByName:sebum_name]) {
	    fprintf(stderr, "[SebFile] %s is missing some files (%s)!\n", sub_dir, sebum_name);
	    [tok free];
	    goto missing;
	}

	[tok free];
    }

    goto exit_ok;

 buggy:
    fprintf(stderr, "[SebFile] Buggy contents file in %s%s\n", top_dir, sub_dir);
    /* Fall through. */

 exit_ok:
    [dirlist closeFile:fp];
    return YES;

 missing:
    [dirlist closeFile:fp];
    return NO;
}

	/* Metafile. */
- (void) processCommand:(Token *)command On:(const char *)filename 
	  WithArguments:(int)argc :(Token *[])argv
		       :(unsigned int)index :(DirectoryList *)dirlist 
{
    int i;
    const char *cmd;
    assert(command);
    cmd = [command getSymbol];

    for (i = 0; i < MAX_METAFILE_COMMANDS && metafile_command_handlers[i].proc; i++) {
	if (strcaseeq(cmd, metafile_command_handlers[i].command)) {
	    metafile_command_handlers[i].proc(filename, index, argc, argv, self, dirlist);
	    return;
	}
    }

    fprintf(stderr, "[%s:%s] Unknown command (%s) in metafile\n", [[self class] name], [self name], cmd);
    assert(i < MAX_METAFILE_COMMANDS);
}

- (BOOL) readMetafileCommands:(const char *)top_dir :(const char *)sub_dir WithDirList:(DirectoryList *)dirlist
{
    assert(top_dir && sub_dir && dirlist);

    if (not metafp || not [self seekInFile:metafp For:"commands" WithDirList:dirlist])
	return YES;

    SEBORRHEA_MESSAGE(stdout, "[%s] Reading metafile (commands)\n", [[self class] name]);

    while (expect_char(metafp, '(')) {
	Token *command = nil, *filename = nil;
	Token *argv[32];
	int argc = 0;
	unsigned int index;

        if (not (command = read_symbol(metafp)))
	    continue;

	if (not (filename = read_string(metafp))) /* File to process. */
	    goto abort;

	{				/* Mark this file read. */
	    for (index = 0; (unsigned)index < [dirlist numEntries]; index++) {
		char path[PATH_MAX];
		snprintf(path, sizeof path, "%s%s", sub_dir, [filename getString]);

		if (streq(path, [dirlist getNthEntry:index]->d_name)) {
		    [dirlist markEntryLoaded:index];
		    break;
		}
	    }
	}

	if (expect_char(metafp, '(')) {	/* Begin arguments. */
	    do {
		char entire_path[PATH_MAX];

		argc = [self readTokensNonParenthesisFrom:metafp :argv :sizeof(argv)/sizeof(*argv)];
		if (argc <= 0)
		    goto abort;

		snprintf(entire_path, sizeof entire_path, "%s%s%s", top_dir, sub_dir, [filename getString]);
		[self processCommand:command On:entire_path WithArguments:argc :argv :index :dirlist];

		while (argc > 0) {	/* Free arguments. */
		    argc--;
		    argv[argc] = [argv[argc] free];
		}
	    } while (expect_char(metafp, '(')); /* More arguments. */
	}
	else {				/* No arguments. */
	    char entire_path[PATH_MAX];
	    snprintf(entire_path, sizeof entire_path, "%s%s%s", top_dir, sub_dir, [filename getString]);
	    [self processCommand:command On:entire_path WithArguments:0 :argv :index :dirlist];
	}

	/* End of command. */
	if (not expect_char(metafp, ')'))
	    goto abort;

	[filename free];
	[command free];
	continue;

    abort:
	while (argc > 0) {		/* Free the arguments. */
	    argc--;
	    argv[argc] = [argv[argc] free];
	}

	if (command)  command  = [command free];
	if (filename) filename = [filename free];
	goto bad;
    }

    if (not expect_char(metafp, ')')) /* ) to close commands list */
	goto bad;

    /* Note: Keep the metafile open for later! */
    return YES;

 bad:
    [self closeMetafile:dirlist];
    return NO;
}

- (BOOL) readMetafilePropertiesWithDirList:(DirectoryList *)dirlist
{
    if (not metafp || not [self seekInFile:metafp For:"properties" WithDirList:dirlist])
	return YES;

    SEBORRHEA_MESSAGE(stdout, "[%s] Reading metafile (properties)\n", [[self class] name]);

    while (expect_char(metafp, '(')) {
	Token *command = nil;
	int i;

        if (not (command = read_symbol(metafp)))
	    continue;

	while (expect_char(metafp, '(')) { /* Begin arguments. */
	    Token *argv[8] = {nil};
	    int argc = [self readTokensNonParenthesisFrom:metafp :argv :sizeof(argv)/sizeof(*argv)];
	    if (argc <= 0)
		goto abort;

	    {				/* Process command. */
		const char *cmd = [command getSymbol];

		for (i = 0; metafile_property_handlers[i].proc; i++) {
		    if (streq(cmd, metafile_property_handlers[i].prop)) {
			metafile_property_handlers[i].proc([self getSebumByName:[argv[0] getString]], argc, argv);
			break;
		    }
		}

		if (not metafile_property_handlers[i].proc) {
		    fprintf(stderr, "[%s] %s is not a recognised metafile property!\n",
			    [[self class] name], cmd);
		}
	    }

	    while (argc > 0) {		/* Free arguments. */
		argc--;
		argv[argc] = [argv[argc] free];
	    }
	}

	/* End of command. */
	if (not expect_char(metafp, ')'))
	    goto abort;

	command = [command free];
	continue;

    abort:
	command = [command free];
	return NO;
    }

    if (not expect_char(metafp, ')'))	/* ) to close properties list */
	return NO;

    return YES;
}

- (BOOL) startLoadingSebumDirectory:(const char *)top_dir :(const char *)sub_dir WithDirList:(DirectoryList *)dirlist
{
    [super startLoadingSebumDirectory:top_dir :sub_dir WithDirList:dirlist];

    /* Open the metafile, and process the commands section. */
    [self openMetafile:top_dir :sub_dir WithDirList:dirlist];
    if (not [self readMetafileCommands:top_dir :sub_dir WithDirList:dirlist])
	goto buggy_metafile;

    /* Load everything else not yet loaded by the metafile. */
    if (not [self loadContentsOfDirectory:top_dir :sub_dir WithDirList:dirlist])
	goto error;

    /* Check the contents. */
    if (not [self assertContentsOfContainer:top_dir :sub_dir :dirlist])
	goto error;

    /* Process the properties section of the metafile. */
    if (not [self readMetafilePropertiesWithDirList:dirlist])
	goto buggy_metafile;

    /* Done */
    [self closeMetafile:dirlist];
    return YES;

 buggy_metafile:
    fprintf(stderr, "[%s] Buggy metafile in %s%s.\n", [[self class] name], top_dir, sub_dir);
    /* Fall through. */

 error:
    [self closeMetafile:dirlist];
    return NO;

}

- (unsigned int) numElements { return nelements; }

- (Sebum *) getNthSebum:(unsigned int)nth
{
    assert(nth < nelements);
    return element[nth];
}

- (Sebum *) getSebumByName:(const char *)name_
{
    const char *delim;
    unsigned int i;
    assert(name_);

    if ((delim = strchr(name_, '/'))) {
	char head[1024];
	delimited_strdup(head, name_, delim, sizeof head);

	for (i = 0; i < nelements; i++) {
	    /* Warning: no error checking! */
	    if (streq([element[i] name], head))
		return [(SebFile *)element[i] getSebumByName:delim+1];
	}
    }
    for (i = 0; i < nelements; i++) {
	if (streq([element[i] name], name_))
	    return element[i];
    }

    fprintf(stderr, "[SebFile] Could not find %s.\n", name_);
    return nil;
}
@end
