#import "PAFilenameList.h"
#import <sys/dir.h>
#import <apps/InterfaceBuilder.h>

/******************************************************************************
PAFilenameList

	The PAFilenameList (a subclass of PAStringList) is a convenient way to deal with lists of filenames. It contains methods for generating lists of filenames from given directories. There is also a specific method for searching the standard libraries (/NextLibrary, /LocalLibrary, ~/Libary). Files can be limited to particular extensions and can be returned with or without their full path and extension.
	
	To use this object inside of InterfaceBuilder, simply drag its icon from the palettes window into the suitcases window. Then drag in a browser and set its delegate to be the filenameList. Then enter something like "Fonts" in the libraries textfield or "/NextLibrary/Documentation" in the directories textfield, type in an extension if you like(font, slo, rtf, etc...) and test interface.
	
	Copyright 1992, Jeff Martin (jmartin@next.com) (415) 780-3833.
******************************************************************************/

#define FILE_FROM_PATH(a)( (a)? rindex(a,'/')? rindex(a,'/')+1 : (a) : 0L )
#define EXT_FROM_PATH(a) ( (a)? rindex(a,'.')? rindex(a,'.')+1 : 0L : 0L)
#define EXT_STRIP(a)     ( (a)? rindex(a,'.')? *rindex(a,'.') = '\0' : 0L :0L)

@implementation PAFilenameList : PAStringList


/******************************************************************************
	addFilename:(char *)filename stripPath:(BOOL)stripPath 
		stripExt:(BOOL)stripExt ifAbsent:(BOOL)ifAbsent noCopy:(BOOL)noCopy 
		sorted:(BOOL)sorted at:(int)at

	This method adds the given filename to the list of strings giving the option to strip the path and/or the extension. It simply does the requested strips and calls addString:ifAbsent:noCopy:sorted:at:(PAStringList). Returns the value of that method (self).
******************************************************************************/
- addFilename:(const char *)filename stripPath:(BOOL)stripPath stripExt:(BOOL)stripExt ifAbsent:(BOOL)ifAbsent noCopy:(BOOL)noCopy sorted:(BOOL)sorted at:(int)at
{
	char newFilename[MAXPATHLEN+1];
	
	// Get filename with appropriate stripping
	sprintf(newFilename, "%s", stripPath? FILE_FROM_PATH(filename) : filename);
	if(stripExt) EXT_STRIP(newFilename);
	
	// If string was sent no copy, then free it (we have our own)
	if(noCopy) free((char *)filename);
	
	// Add new string with noCopy 
	return [self addString:newFilename ifAbsent:ifAbsent noCopy:NO 
		sorted:sorted at:at];
}

/******************************************************************************
	addFilenames:(char **)filenames stripPath:(BOOL)stripPath ...
	addPAFilenameList:filenameList stripPath:(BOOL)stripPath...

	These methods allow for lists of strings to be added as filenames (with or without their full paths and extensions).
	addFilename: adds filenames from a NULL terminated array of char pointers. Returns self.
	addPAFilenameList: add filenames from a PAFilenameList. Returns self.
******************************************************************************/
- addFilenames:(const char *const*)filenames stripPath:(BOOL)stripPath stripExt:(BOOL)stripExt ifAbsent:(BOOL)ifAbsent noCopy:(BOOL)noCopy sorted:(BOOL)sorted at:(int)at
{
	char *const*temp = filenames;
	
	// Add each string individually, incrementing 'at' to preserve their order
	if(temp) while(*temp) {
		[self addFilename:*temp stripPath:stripPath stripExt:stripExt 
			ifAbsent:ifAbsent noCopy:noCopy sorted:sorted at:at];
			at++; temp++;
	}
	
	// If 'noCopy' then we own the memory 'filenames' and should free it
	if(noCopy) free((char **)filenames);
	return self;
}

- addPAFilenameList:filenameList stripPath:(BOOL)stripPath stripExt:(BOOL)stripExt ifAbsent:(BOOL)ifAbsent noCopy:(BOOL)noCopy sorted:(BOOL)sorted at:(int)at
{
	int i;
	
	for(i=0; i<[filenameList count]; i++)
		[self addFilename:[filenameList stringAt:i] stripPath:stripPath 
		stripExt:stripExt ifAbsent:ifAbsent noCopy:noCopy sorted:sorted 
		at:at++];
	return self;
}

/******************************************************************************
	addFilenamesFromDirectories:withExts:

	addFilenamesFromDirectories looks out in the directories specified for files with the given extensions (NULL for all files) and adds them to the list. 'directory' is a delimited list of directories and 'ext' is a delimited list of extensions. Assumed delimiters are space, tab, newline, return, colon and comma (' ', '\t', '\n', '\r', ':', ',', '\0'). Returns self.
	addFilenamesFromStandardLibrariesDirectory goes a step farther and looks in /NextLibary, /LocalLibrary and ~/Libary for the given directories. This is a convenience for the cases where an app searches the standard libraries. Returns self.
******************************************************************************/
- addFilenamesFromDirectories:(const char *)dirs withExts:(const char *)exts stripPath:(BOOL)stripPath stripExt:(BOOL)stripExt ifAbsent:(BOOL)ifAbsent sorted:(BOOL)sorted at:(int)at
{
	char dirDels[] = { ' ', '\t', '\n', '\r', ':', ',', '\0' };
	char extDels[] = { ' ', '\t', '\n', '\r', ':', ',', '.', '\0' };
	id dirList = [[[PAStringList alloc] init] addDelimitedStrings:dirs
		delimiters:dirDels];
	id extList = [[[PAStringList alloc] init] addDelimitedStrings:exts
		delimiters:extDels];
	DIR	*thedir;
	char tempName[MAXPATHLEN+1];
	struct direct *entry;
	int i;
	
	// Look at each directory
	for(i=0; i<[dirList count]; i++) {
		char directory[MAXPATHLEN];
		
		// Expand and Open the current directory
		sprintf(directory, "%s", [dirList stringAt:i]);
		PAExpandFilename(directory);
		thedir = opendir(directory);

		// Look at each entry in the dir. If they aren't hidden(start with '.') 
		//   and there was no given extension or the extension matches one in 
		//	 the extensionList, add filename.
		while(thedir && (entry=readdir(thedir)))
			if((*entry->d_name != '.') && (!exts || 
				[extList stringExists:EXT_FROM_PATH(entry->d_name)])) {
				sprintf(tempName,"%s/%s", directory, entry->d_name);
				[self addFilename:tempName stripPath:stripPath 
					stripExt:stripExt ifAbsent:ifAbsent noCopy:NO sorted:sorted 
						at:at++];
			}
	
		// Close the directory, store the file counter and return the files.
		if(thedir) closedir(thedir);
	}

	[dirList freeStrings]; [dirList free];
	[extList freeStrings]; [extList free];
	return self;
}

- addFilenamesFromStandardLibrariesDirectories:(const char *)dirs
	withExts:(const char *)exts stripPath:(BOOL)stripPath 
	stripExt:(BOOL)stripExt ifAbsent:(BOOL)ifAbsent sorted:(BOOL)sorted 
	at:(int)at
{
	char dirDels[] = { ' ', '\t', '\n', '\r', ':', ',', '\0' };
	id dirList = [[[PAStringList alloc] init] addDelimitedStrings:dirs
		delimiters:dirDels];
	char temp[3*MAXPATHLEN];
	int i, oldCount = [self count];
	
	// For each given directory
	for(i=0; i<[dirList count]; i++) {
		const char *dir = [dirList stringAt:i];

		// Add files from NextLibrary
		sprintf(temp, "/NextLibrary/%s:/LocalLibrary/%s:%s/Library/%s",
			dir, dir, NXHomeDirectory(), dir);
		[self addFilenamesFromDirectories:temp withExts:exts 
			stripPath:stripPath stripExt:stripExt ifAbsent:ifAbsent 
			sorted:sorted at:at];
		
		// Increment 'at' value
		at += [self count] - oldCount; oldCount = [self count];
	}
	[dirList freeStrings]; [dirList free];
	return self;
}


/******************************************************************************
	addDelimitedFilenames:delimiters:stripPath:stripExt:ifAbsent:sorted:at:at

	This method is just like addDelimitedStrings: but it allows the filnames to be stripped of their paths and/or extensions before being added. Returns self.
******************************************************************************/
- addDelimitedFilenames:(const char *)filenames delimiters:(const char *)dels stripPath:(BOOL)stripPath stripExt:(BOOL)stripExt ifAbsent:(BOOL)ifAbsent sorted:(BOOL)sorted at:(int)at
{
	id filenameList = [[[PAStringList alloc] init] 
		addDelimitedStrings:filenames delimiters:dels];

	[self addPAFilenameList:filenameList stripPath:stripPath stripExt:stripExt
		ifAbsent:ifAbsent noCopy:YES sorted:sorted at:at];
	[filenameList free];
	return self;
}

/******************************************************************************
	sortFilenameIgnoringPath:
	filenameCmp()

	sortFilenamesIgnoringPath: is just like sortStrings except that the full paths are stripped from the file before it is compared. This is useful for cases where you want the files to be in alphabetical order regardless of their full path, while preserving the full path. Returns self.
******************************************************************************/
- sortFilenamesIgnoringPath:sender
{
	qsort(dataPtr, [self count], sizeof(char *), filenameCmp);
	isSorted = YES;
	return self;
}

// Wrap around strcasecmp to cmp filenames regardless of their path(strip path)
int filenameCmp(const void *s1, const void *s2)
{
	char *fn1 = FILE_FROM_PATH(*(char **)s1);
	char *fn2 = FILE_FROM_PATH(*(char **)s2);
	if(fn1 == fn2) return 0;
	else if(!fn1) return 1; else if(!fn2) return -1;
	else return strcasecmp(fn1, fn2);
}

// Archiving methods
- write:(NXTypedStream *)stream
{	
	[super write:stream];

	NXWriteTypes(stream, "***cccccc",
		&plLibs, &plDirs, &plExts,
		&plFromAppDir, &plFromBundleDir,
		&plStripPath, &plStripExt,
		&plIfAbsent, &plSorted);
	return self;
}

- read:(NXTypedStream *)stream
{
	[super read:stream];

	NXReadTypes(stream, "***cccccc",
		&plLibs, &plDirs, &plExts,
		&plFromAppDir, &plFromBundleDir,
		&plStripPath, &plStripExt,
		&plIfAbsent, &plSorted);
	return self;
}

// Does the work of preloading after object is read in
- awake
{
	[super awake];

	// Don't preload if editing in IB and not testing interface.
	//   'isTestingInterface' is currently bogus durring unarchival(bug #35351)
	//   so I test to see if mainMenu is visible, which it isn't while test
	//   interface mode is being set up. Hopefully 'isTestingInterface' will
	//   work in the future. I think this hack works fine for now.
	//if(!strcmp([NXApp appName], "InterfaceBuilder"))
	if([NXApp conformsTo:@protocol(IB)])
		if([[NXApp mainMenu] isVisible])
			return self;
	
	// Add filenames from given libraries
	[self addFilenamesFromStandardLibrariesDirectories:plLibs withExts:plExts 
		stripPath:plStripPath stripExt:plStripExt ifAbsent:plIfAbsent 
		sorted:plSorted at:[self count]];
	
	// Add filenames from given directies
	[self addFilenamesFromDirectories:plDirs withExts:plExts 
		stripPath:plStripPath stripExt:plStripExt ifAbsent:plIfAbsent 
		sorted:plSorted at:[self count]];

	// Add filenames from app wrapper if requested
	if(plFromAppDir)
		[self addFilenamesFromDirectories:[[NXBundle mainBundle] directory]
		withExts:plExts stripPath:plStripPath stripExt:plStripExt 
		ifAbsent:plIfAbsent sorted:plSorted at:[self count]];

	// Add filenames from bundle dir if requested
	if(plFromBundleDir) [self addFilenamesFromDirectories:
		[[NXBundle bundleForClass:[self class]] directory] withExts:plExts 
		stripPath:plStripPath stripExt:plStripExt ifAbsent:plIfAbsent 
		sorted:plSorted at:[self count]];

	// Reset all preload values
	[self setPreLoadLibraries:NULL]; [self setPreLoadDirectories:NULL];
	[self setPreLoadExtensions:NULL];
	[self setPreLoadFromAppDir:NO]; [self setPreLoadFromBundleDir:NO];
	[self setPreLoadStripPath:NO]; [self setPreLoadStripExt:NO];
	[self setPreLoadIfAbsent:NO]; [self setPreLoadSorted:NO];
	return self;
}

/******************************************************************************
	(const char *)preLoadLibraries, setPreLoadLibraries:(const char *)libs
	(const char *)preLoadDirectories, setPreLoadDirectories:(const char *)dirs
	(const char *)preLoadExtensions, setPreLoadExtensions:(const char *)exts
	(BOOL)preLoadFromAppDir, setPreLoadFromAppDir:(BOOL)flag
	(BOOL)preLoadFromBundleDir, setPreLoadFromBundleDir:(BOOL)flag
	(BOOL)preLoadStripPath, setPreLoadStripPath:(BOOL)flag
	(BOOL)preLoadStripExt, setPreLoadStripExt:(BOOL)flag
	(BOOL)preLoadIfAbsent, setPreLoadIfAbsent:(BOOL)flag
	(BOOL)preLoadSorted, setPreLoadSorted:(BOOL)flag
	
	These methods query and set search paths, extensions and flags to preload the string list when unarchived. This allows for an InterfaceBuilder palette to set the StringList to load interesting things when unarchived (ie, "get all of the files of type 'font' from the standard library directory 'Fonts' ").
******************************************************************************/
// Return and set the Libraries for preload
- (const char *)preLoadLibraries { return plLibs; }
- setPreLoadLibraries:(const char *)libs
{ 
	free(plLibs); 
	plLibs = (libs && strlen(libs))? 
		NXCopyStringBufferFromZone(libs, [self zone]) : NULL;
	return self;
}

// Return and set the Directories for preload
- (const char *)preLoadDirectories { return plDirs; }
- setPreLoadDirectories:(const char *)dirs
{ 
	free(plDirs); 
	plDirs = (dirs && strlen(dirs))? 
		NXCopyStringBufferFromZone(dirs, [self zone]) : NULL;
	return self;
}

// Return and set the Extensions for preload
- (const char *)preLoadExtensions { return plExts; }
- setPreLoadExtensions:(const char *)exts
{ 
	free(plExts); 
	plExts = (exts && strlen(exts))?
		NXCopyStringBufferFromZone(exts, [self zone]) : NULL;
	return self;
}

// Return and set whether or not to load from application dir
- (BOOL)preLoadFromAppDir 
{ return plFromAppDir; }
- setPreLoadFromAppDir:(BOOL)flag { plFromAppDir = flag; return self; }

// Return and set whether or not to load from bundle dir
- (BOOL)preLoadFromBundleDir { return plFromBundleDir; }
- setPreLoadFromBundleDir:(BOOL)flag { plFromBundleDir = flag; return self; }

// Return and set whether or not to strip paths from preloaded files
- (BOOL)preLoadStripPath { return plStripPath;}
- setPreLoadStripPath:(BOOL)flag { plStripPath = flag; return self; }

// Return and set whether or not to strip extensions from preloaded files
- (BOOL)preLoadStripExt { return plStripExt; }
- setPreLoadStripExt:(BOOL)flag { plStripExt = flag; return self; }

// Return and set whether or not to 'add preloaded files only if absent'
- (BOOL)preLoadIfAbsent { return plIfAbsent; }
- setPreLoadIfAbsent:(BOOL)flag { plIfAbsent = flag; return self; }

// Return and set whether or not to sort preloaded files
- (BOOL)preLoadSorted { return plSorted; }
- setPreLoadSorted:(BOOL)flag { plSorted = flag; return self; }

// Interface Builder support
- (const char *)getInspectorClassName { return "PAFilenameListInspector"; }

@end

char *PAExpandFilename(char *fn)
{
	char oldFilename[MAXPATHLEN], *fn2 = fn+1;
	if(*fn=='~') {
		if(fn[1]=='/') fn2++;
		strcpy(oldFilename, fn2);
		sprintf(fn, "%s/%s", NXHomeDirectory(), oldFilename);
	}
	return fn;
}



