/* File: HashFile.m - db(3) to HashTable bridge
 *
 * By: Christopher Lane <lane@camis.stanford.edu>
 * Symbolic Systems Resources Group
 * Knowledge Systems Laboratory
 * Stanford University
 *
 * Date: 12 August 1994
 *
 * Copyright: 1990, 1991, 1992 & 1994 by The Leland Stanford Junior University.
 * This program may be distributed without restriction for non-commercial use.
 */

#import <c.h>
#import <libc.h>
#import <string.h>
#import <assert.h>

#import "HashFile.h"

#define OBJECT @encode(id)

#define NXRewind(stream) NXSeek(stream, 0L, NX_FROMSTART)

@implementation HashFile

+ (BOOL) isHashFile:(const char *) name
{
	return dbExists((char *) name);
}

- initFromFile:(const char *) name
{
	return [self initFromFile:name keyDesc:OBJECT];
}

- initFromFile:(const char *) name keyDesc:(const char *) aKeyDesc
{
	return [self initFromFile:name keyDesc:aKeyDesc valueDesc:OBJECT];
}

- initFromFile:(const char *) name keyDesc:(const char *) aKeyDesc valueDesc:(const char *) aValueDesc
{
	return [self initFromFile:name keyDesc:aKeyDesc valueDesc:aValueDesc capacity:0];
}

- initFromFile:(const char *) name keyDesc:(const char *) aKeyDesc valueDesc:(const char *) aValueDesc capacity:(unsigned) aCapacity;
{
	NXTypedStream *typedStream;

	if ((db = dbOpen(filename = (char *) name)) == NULL) return nil;

	readOnly = (BOOL) ((db->flag & (dbFlagReadOnly | dbFlagCompressed)) != 0);
	
	cached = (aCapacity != HASHFILE_UNCACHED);
	
	keyDesc = aKeyDesc;
	valueDesc = aValueDesc;
	
	if (cached) table = [[HashTable alloc] initKeyDesc:keyDesc valueDesc:valueDesc capacity:aCapacity];
		
	assert((d.k.s = (char *) malloc((size_t) LEAFSIZE)) != NULL);
	assert((d.c.s = (char *) malloc((size_t) LEAFSIZE)) != NULL);

	d.k.n = malloc_size(d.k.s);
	d.c.n = malloc_size(d.c.s);

	assert((stream = NXOpenMemory(NULL, 0, NX_READWRITE)) != NULL);

	assert((typedStream = NXOpenTypedStream(stream, NX_WRITEONLY)) != NULL); {
		offset = NXTell(stream);
		NXCloseTypedStream(typedStream);
		}

	return self;
}

- free
{
	free(d.k.s);
	free(d.c.s);
	NXCloseMemory(stream, NX_FREEBUFFER);
	assert(dbClose(db));
	if (cached) [table free];

	return [super free];
}

- empty
{
	if (readOnly || !dbClose(db) || !dbCreate(filename) || (db = dbInit(filename)) == NULL) return nil;
	else if (cached) [table empty];

	return self;
}

- (unsigned) count
{
	unsigned i = 0;

	if (dbFirst(db, &d) && d.k.n > 0) do { ++i; } while (dbNext(db, &d));

	if (cached) assert(i >= [table count]);

	return i;
}

- (BOOL) isKey:(const void *) aKey
{
	if (cached && [table isKey:aKey]) return YES;

	[self _keyCvtIn:aKey];

	return((BOOL) dbFind(db, &d));
}

- (void *) valueForKey:(const void *) aKey
{
	void *value = (void *) nil;

	if (cached && [table isKey:aKey]) return [table valueForKey:aKey];

	[self _keyCvtIn:aKey];

	if (dbFetch(db, &d)) {
		value = [self _valueCvtOut];
		if (cached) (void) [table insertKey:[self _keyCvtOut] value:value];
		}

	return value;
}

- (void *) insertKey:(const void *) aKey value:(void *) aValue
{
	int flag;
	void *value;

	if (readOnly) return (void *) nil;

	value = [self valueForKey:aKey]; /* allows [table insertKey:...] to return previous value */

	[self _keyCvtIn:aKey];
	[self _valueCvtIn:aValue];

	assert(dbLock(db)); {
		flag = dbStore(db, &d);
		assert(dbUnlock(db));
		}

	if (!flag) return (void *) nil;
	
	if (cached) return [table insertKey:aKey value:aValue];

	return (value);
}

- (void *) removeKey:(const void *) aKey;
{
	int flag;

	if (readOnly) return (void *) nil;

	[self _keyCvtIn:aKey];

	assert(dbLock(db)); {
		flag = dbDelete(db, &d);
		assert(dbUnlock(db));
		}

	if (!flag) return (void *) nil;

	return((cached) ? [table removeKey:aKey] : (void *) nil);
}

- (NXHashState) initState
{
	NXHashState state;

	state.i = state.j = 0;

	return state;
}

- (BOOL) nextState:(NXHashState *) aState key:(const void **) aKey value:(void **) aValue
{
	if (aState->j == 0) {
		if (aState->i = dbFirst(db, &d)) {
			assert((aState->i = d.k.n) != 0);
			assert((aState->j = d.c.n) != 0);
			}
		}
	else [[self _conditionalFree:aKey desc:keyDesc] _conditionalFree:aValue desc:valueDesc];

	*aKey = *aValue = NULL;
	
	if (aState->i == 0) return NO;

	d.k.n = aState->i;
	d.c.n = aState->j;

	if (dbGet(db, &d)) {
		*aKey = [self _keyCvtOut];
		*aValue = [self _valueCvtOut];
		}
	else return NO;

	if (aState->i = dbNext(db, &d)) {
		assert((aState->i = d.k.n) != 0);
		assert((aState->j = d.c.n) != 0);
		}

	return YES;
}

- (void) _keyCvtIn:(const void *) aKey
{
	[self _datumCvtIn:&d.k from:aKey using:keyDesc];
}

- (void) _valueCvtIn:(const void *) aValue
{
	[self _datumCvtIn:&d.c from:aValue using:valueDesc];
}

- (void) _datumCvtIn:(Datum *) aDatum from:(const void *) aBuffer using:(const char *) aDesc
{
	NXTypedStream *typedStream;

	NXRewind(stream);

	assert((typedStream = NXOpenTypedStream(stream, NX_WRITEONLY)) != NULL); {
		offset = NXTell(stream);
		NXWriteType(typedStream, aDesc, &aBuffer);
		aDatum->n = NXTell(stream) - offset;
		NXCloseTypedStream(typedStream);
		}

	NXSeek(stream, offset, NX_FROMSTART);

	NXRead(stream, (void *) aDatum->s, aDatum->n);
}

- (void *) _keyCvtOut
{
	return [self _datumCvtOut:&d.k using:keyDesc];
}

- (void *) _valueCvtOut
{
	return [self _datumCvtOut:&d.c using:valueDesc];
}

- (void *) _datumCvtOut:(Datum *) aDatum using:(const char *) aDesc
{
	void *pointer;
	NXTypedStream *typedStream;

	NXSeek(stream, offset, NX_FROMSTART);

	NXWrite(stream, (void *) aDatum->s, aDatum->n);

	NXRewind(stream);

	assert((typedStream = NXOpenTypedStream(stream, NX_READONLY)) != NULL); {
		NXReadType(typedStream, aDesc, &pointer);
		NXCloseTypedStream(typedStream);
		}

	return pointer;
}

- _conditionalFree:(const void **) aPointer desc:(const char *) aDesc
{
	switch(*aDesc) {
		case '*' : free((void *) *aPointer); break;
		case '@' : [(id) (*aPointer) free]; break;
		default: break;
		}

	return self;
}

@end
