/* Controller.m:
 * You may freely copy, distribute, and reuse the code in this example.
 * NeXT disclaims any warranty of any kind, expressed or  implied, as to its
 * fitness for any particular use.
 *
 *
 */

#import <pwd.h>
#import <sys/param.h>
#import <indexing/IXRecordManager.h>
#import <indexing/IXPostingList.h>
#import <indexing/protocols.h>
#import <btree/protocols.h>
#import <btree/IXPostingSet.h>
#import <store/IXStoreFile.h>
#import <architecture/byte_order.h>

#import "Controller.h"
#import "DataRecord.h"

#define STRINGLEN(A) (strlen((A)) + 1)

@implementation Controller

- free
{
    [recMan free];
    if (postingList != nil)
	postingList = [[postingList freeObjects] free];

    return [super free];
}

/*
 * Commits any pending transactions to disk.
 */
- commit
{
    [[recMan store] commitTransaction];
    return self;
}

/* TARGET ACTION METHODS */

- clear:sender
{
    [idField setStringValue:""];
    [dataForm setStringValue:"" at:0];
    [dataForm setStringValue:"" at:1];
    return self;
}

/*
 * Adds a new record to the database.  A DataRecord is created and initialized
 * from the fields in the dataForm.
 */
- addARecord:sender
{
    id newRec;
	const char *stringInput;
	const char *numberInput;
    
    /* create and setup the new record */
    /* We don't allow empty input, since those records can't be retrieved
     * later on
     */
    stringInput = (const char *)[dataForm stringValueAt:0];
    numberInput = (const char *)[dataForm stringValueAt:1];
    if ( (strcmp(stringInput, "") == 0) || (strcmp(numberInput, "") == 0) ){
	    NXRunAlertPanel("Error", "You must supply a string.", NULL, NULL, NULL);
	    return self;
    }
    newRec = [[DataRecord alloc] init];
    [newRec setAString:[dataForm stringValueAt:0]];
    [newRec setAnInt:[dataForm intValueAt:1]];
    /* add it in */
    [idField setIntValue:[recMan addRecord:newRec]];
    [newRec free];
    [self commit];
    [dataForm selectTextAt:0];
    return self;
}

/*
 * Delete the current record from the database.
 */
- deleteRecord:sender
{
    id retval;
    unsigned int aHandle;
    
    aHandle = (unsigned)[idField intValue];
    retval = [recMan removeRecord:aHandle];
    if(retval != nil)
    {
	[self clear:self];
	[self commit];
    }

    return self;
}

/*
 * Changes the data fields of the current record.  This is done by creating a
 * new record and replacing the existing one with the new one.
 */
- editRecord:sender
{
    id newRec;
    
    /* create and setup the replacement record */
    newRec = [[DataRecord alloc] init];
    [newRec setAString:[dataForm stringValueAt:0]];
    [newRec setAnInt:[dataForm intValueAt:1]];
    
    /* replace it */
    [recMan replaceRecord:[idField intValue] with:newRec];
    [newRec free];
    [self commit];
    return self;
}


/*
 * Searches for records in the database.
 * If the allSwitch is set, we simply pull all records from the record manager.
 * If the allSwitch is not set, we search on each attribute 'anding' the 
 * results as we go along.
 */
- search:sender
{
    id cur;
    char *searchString;
    IXPostingSet *postingSet;
    unsigned int intfld;

    if (postingList != nil)
	postingList = [[postingList freeObjects] free];

    if(![allSwitch state])
    {
	postingSet = [[IXPostingSet alloc] init];

	/* first search the string attribute */
	searchString = (char *)[searchForm stringValueAt:0];
	if(searchString && *searchString != '\0')
	{
	    cur = [recMan cursorForAttributeNamed:DRASTRINGNAME];
	    if([cur setKey:searchString andLength:STRINGLEN(searchString)])
			[postingSet formUnionWithPostingsIn:cur];

	    [cur free]; // must free cursor to avoid leaking.
	}

	/* now search the integer attribute */
	searchString = (char *)[searchForm stringValueAt:1];
	if(searchString && *searchString != '\0')
	{
	    cur = [recMan cursorForAttributeNamed:DRANINTNAME];
	    intfld = atoi(searchString);

		/* accumulate results in the IXPostingSet.
		   Do a logical AND if there were string matches above,
		   otherwise, just do a logical OR.
		 */
		

/* Swap if this is a little-endian machine. */
		if ( NXHostByteOrder() == NX_LittleEndian )
			intfld = NXSwapInt(intfld);



	    if([cur setKey:(void *)&intfld andLength:sizeof(unsigned int)])
	    {
		if([postingSet count] > 0)
		    [postingSet formIntersectionWithPostingsIn:cur];
		else
		    [postingSet formUnionWithPostingsIn:cur];
	    }
	
	    [cur free]; // must free cursor to avoid leaking.
	}

	[searchForm selectTextAt:0];
	postingList = [[IXPostingList alloc] initWithSource:recMan andPostingsIn:postingSet];
	[postingSet free];
    } else
	postingList = [recMan recordsForClass:[DataRecord class]];
   
    [recBrowser loadColumnZero];
    return self;
}

- browserHit:sender
/*
 * Responds to the user selecting a handle in the browser by retrieving
 * the corresponding record and displaying its values in the dataForm.
 */
{
    int col = [recBrowser selectedColumn];
    id dr, matrix = [recBrowser matrixInColumn:col];
    unsigned int handle;
    
    handle = [[matrix selectedCell] intValue];
    [idField setIntValue:handle];
    if((dr = [recMan readRecord:handle fromZone:[self zone]]) == nil)
    {
	NXRunAlertPanel("Error", "Cannot read record", NULL, NULL, NULL);
	[self clear:self];
    } else
    {
	[dataForm setStringValue:[dr aString] at:0];
	[dataForm setIntValue:[dr anInt] at:1];
	[dr free];
    }

    return self;
}

/* NXBROWSER DELEGATE METHODS */

- (int)browser:sender fillMatrix:matrix inColumn:(int)col
{
    id cell;
    int i, rows = 0;

    if (postingList != nil)
	rows = [postingList count];

    for(i = 0; i < rows; i++)
    {
	[matrix insertRowAt:i];
	cell = [matrix cellAt:i :0];
	[cell setIntValue:[postingList handleOfObjectAt:i]];
	[cell setLoaded:YES];
	[cell setLeaf:YES];
    }

    return rows;
}

/* APPLICATION DELEGATE METHODS */

- appDidInit:sender
{
    struct passwd *pw;
    char path[MAXPATHLEN];

    pw = getpwuid(getuid());
    sprintf(path, "%s/%s", pw->pw_dir, DBFILENAME);
	fprintf(stderr, "path %s\n", path);
    [recBrowser acceptArrowKeys:YES andSendActionMessages:YES];
    if(access(path, F_OK) == -1)
    {
	/* create new database */
	recMan = [[IXRecordManager alloc] initWithName:DBNAME inFile:path];
	if (recMan == nil)
	{
	    NXRunAlertPanel("Error", "Cannot create database.", NULL, NULL, NULL);
	    [NXApp terminate:self];
	}

	[recMan addAttributeNamed:DRASTRINGNAME forSelector:@selector(aString)];
	[recMan setComparator:&IXCompareMonocaseStrings andContext:NULL 
	    forAttributeNamed:DRASTRINGNAME];
	
	[recMan addAttributeNamed:DRANINTNAME forSelector:@selector(anInt)];
	[recMan setComparator:&IXCompareLongs andContext:NULL 
	    forAttributeNamed:DRANINTNAME];
    } else
    {
	/* open existing database */
	recMan = [[IXRecordManager alloc] initFromName:DBNAME inFile:path forWriting:YES];
	if (recMan == nil)
	{
	    NXRunAlertPanel("Error", "Cannot open database.", NULL, NULL, NULL);
	    [NXApp terminate:self];
	}
    }

    [[recMan store] startTransaction];
    [[recMan store] commitTransaction]; // don't want to leave a transaction pending
    return self;
}

-appWillTerminate:sender
{
    [self commit];
    return self;
}

@end
