#import "FaceFileCache.h"

#import <sys/param.h>
#import <sys/dir.h>
#import <stdlib.h>
#import <stdio.h>
#import <string.h>
#import <streams/streams.h>
#import <appkit/NXCType.h>
#import <appkit/Application.h>

@interface FaceFileCache (Private)
- (void)_freeDirectory;
- (void)_freeHashTable;
- (const char *)_entryForLogin:(const char *)login;
- (BOOL)_loadDirectory;
- (BOOL)_loadAliases;
@end


@implementation FaceFileCache

- initWithDirectory:(const char *)aDirectory
{
	if ((self = [super init]))
	{
		[self setDirectory:aDirectory];
	}
	return self;
}

- free
{
	[self _freeDirectory];
	[self _freeHashTable];
	return [super free];
}

- (const char *)directory	{ return directory; }

- (BOOL)setDirectory:(const char *)aDirectory
{
	[self _freeDirectory];
	[self _freeHashTable];

	if (aDirectory)
	{
		if (aDirectory[0] == '~' && aDirectory[1] == '/')
		{
			const char *home = NXHomeDirectory();

			directory = NXZoneMalloc([self zone], strlen(home)+strlen(aDirectory)-1+1);
			strcpy(directory, home);
			strcat(directory, aDirectory+1);
		}
		else
		{
			directory = NXCopyStringBufferFromZone(aDirectory, [self zone]);
		}
	}
	return [self shouldLoad];
}

- (const char *)filenameForLogin:(const char *)login
{
	const char *entry;

	if ([self shouldLoad]) [self load];

	/* first try full login name; if that fails, try `@domain' part only */
	if (login && [hashTable count] > 0 &&
	    ((entry = [self _entryForLogin:login]) ||
	     (entry = [self _entryForLogin:strchr(login, '@')])))
	{
		char buf[MAXPATHLEN+1];

		sprintf(buf, "%s/%s", directory, entry);
		return NXCopyStringBuffer(buf);
	}
	return NULL;
}

#define STAT_EQ(s1, s2)	((s1)->st_ino == (s2)->st_ino && \
			 (s1)->st_dev == (s2)->st_dev && \
			 (s1)->st_mtime == (s2)->st_mtime && \
			 (s1)->st_size == (s2)->st_size)

- (BOOL)shouldLoad
{
	struct stat st;

	if (directory == NULL) return NO;
	if (stat(directory, &st) < 0) return NO;

	if (hashTable == nil) return YES;

	if (STAT_EQ(&st, &dirstat)) return NO;
	memcpy(&dirstat, &st, sizeof(dirstat));
	return YES;
}

- (BOOL)load
{
	BOOL result;

	[self _freeHashTable];

	if (directory == NULL) return NO;

	hashTable = [[HashTable allocFromZone:[self zone]] initKeyDesc:"*" valueDesc:"*"];

	result = [self _loadDirectory];
	result |= [self _loadAliases];
	return result;
}

@end // FaceFileCache


@implementation FaceFileCache (Private)

- (void)_freeDirectory
{
	if (directory) NXZoneFree([self zone], directory), directory = NULL;
	memset(&dirstat, 0, sizeof(dirstat));
}

- (void)_freeHashTable
{
	[hashTable freeKeys:free values:free];
	hashTable = [hashTable free];
}

- (const char *)_entryForLogin:(const char *)login
{
	// Lookup login name in hash table.
	const char *result = NULL;

	if (login && (result = [hashTable valueForKey:login]) == NULL)
	{
		/* shorten login name by repeatedly removing first element
		   of domain name, and try again... */
		char buf[MAXPATHLEN+1];
		char *at, *dot;

		strcpy(buf, login);
		if ((at = strchr(buf, '@')) != NULL)
		{
			while ((dot = strchr(at + 1, '.')) != NULL)
			{
				strcpy(at + 1, dot + 1);
				result = [hashTable valueForKey:buf];
				if (result != NULL) break;
			}

		}
		    
	}
	return result;
}

- (void)_loginFromFilename:(char *)buf
{
	char *s = buf;

	// convert to lower case.
	while (*s)
	{
		*s = NXToLower(*s);
		++s;
	}

	// Backward compatibility: convert aap.noot.mies.org -> aap@noot.mies.org
	if (strchr(buf, '@') == NULL)
	{
		char *dot;

		// Heuristic: skip W.E. in W.E.Coyote.death.valley.org
		while ((dot = strchr(buf, '.')) != NULL && dot <= buf + 1)
		{
			buf = dot + 1;
		}
		if (dot) *dot = '@';
	}
}

static const char _tiff[] = ".tiff";

- (BOOL)_loadDirectory
{
	DIR *dir = opendir(directory);
	struct direct *d;

	if (dir == NULL) return NO;

	while ((d = readdir(dir)) != NULL)
	{
		char *key, *value;

		if (d->d_namlen <= sizeof(_tiff) -1 ||
		    strcmp(d->d_name + d->d_namlen - (sizeof(_tiff) -1),
			   _tiff) != 0) continue;

		value = NXCopyStringBufferFromZone(d->d_name, [self zone]);

		key = NXZoneMalloc([self zone], d->d_namlen - (sizeof(_tiff) -1) + 1);
		memcpy(key, d->d_name, d->d_namlen - (sizeof(_tiff) -1));
		key[d->d_namlen - (sizeof(_tiff) -1)] = '\0';

		[self _loginFromFilename:key];

		/* do not overwrite existing entry. (alternative: do
		   overwrite but that is somewhat harder to implement without
		   memory leaks so I am taking the easy way out:-) */
		if ([hashTable isKey:key])
		{
			// Oh brother this is painful...
			NXZoneFree([self zone], key);
			NXZoneFree([self zone], value);
			continue;
		}
		[hashTable insertKey:key value:value];
	}
	closedir(dir);

	return YES;
}

- (BOOL)_loadAliases
{
	char filename[MAXPATHLEN+1];
	NXStream *aliases;
	char *ap, *end, *nl, *colon;
	char *key, *value;
	int len, maxlen;

	sprintf(filename, "%s/%s", directory, "aliases");
	aliases = NXMapFile(filename, NX_READONLY);

	if (aliases == NULL) return NO;

	NXGetMemoryBuffer(aliases, &ap, &len, &maxlen);

	for (end = ap + len;  (ap < end);  ap = nl + 1)
	{
		nl = strchr(ap, '\n');
		if (nl == NULL) nl = end;

		if (*ap == '#') continue;

		colon = strchr(ap, ':');
		if (colon == NULL) continue;

		key = NXZoneMalloc([self zone], colon - ap + 1);
		memcpy(key, ap, colon - ap);
		key[colon - ap] = '\0';

		// Skip spaces beyond ':'.
		while (*++colon == ' ') ;
		if (colon == nl)
		{
			NXZoneFree([self zone], key);
			continue;
		}
		value = NXZoneMalloc([self zone], nl - colon + sizeof(_tiff));
		memcpy(value, colon, nl - colon);
		strcpy(&value[nl - colon], _tiff);

		if ([hashTable isKey:key])
		{
			NXZoneFree([self zone], key);
			NXZoneFree([self zone], value);
			continue;
		}
		[hashTable insertKey:key value:value];
	}
	NXCloseMemory(aliases, NX_FREEBUFFER);
	return YES;
}

@end // FaceFileCache (Private)


#ifdef TESTING
int main()
{
	FaceFileCache *cache = [[FaceFileCache alloc] initWithDirectory:"/LocalLibrary/Images/People"];
	const char *result = [cache filenameForLogin:"tom@basil.icce.rug.nl"];

	printf("%s\n", result);

	return 0;
}
#endif
