//
//	MiscStringUNIX.m
//		Written by Don Yacktman (c) 1993 by Don Yacktman.
//				Version 1.7  All rights reserved.
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	

#import <misckit/MiscString.h>

static char theMiscPathSeparator = '/';
static char theMiscExtensionSeparator = '.';

@implementation MiscString(UNIX)

// This category includes methods to support various UNIX features,
// such as file/path name parsing and encryption of passwords.

+ setPathSeparator:(char)c { theMiscPathSeparator = c; return self; }
+ setExtensionSeparator:(char)c { theMiscExtensionSeparator = c; return self; }
+ (char)extensionSeparator { return theMiscExtensionSeparator; }
+ (char)pathSeparator { return theMiscPathSeparator; }

- encrypt:salt
{	// encrypt as a UNIX password using the MiscString "salt" as the salt...
	// see crypt(3) for more info
	// The cast prevents a warning:  -stringValue returns a const char *.
	// Assuming crypt() doesn't change the salt, the cast is OK.
	char *strv;
	if ([salt respondsTo:@selector(stringValue)])
		strv = (char *)[salt stringValue];
	else return nil;
	if (!(buffer && strv)) return nil;
	if (!length || !strlen(strv)) return nil;
	return [[[self class] alloc] initString:crypt(buffer, strv)];
}

- fileNameFromZone:(NXZone *)zone
{
	return [self extractPart:MISC_STRING_LAST
				useAsDelimiter:theMiscPathSeparator
				caseSensitive:YES fromZone:zone];
}

- fileName
{
	return [self fileNameFromZone:[self zone]];
}

- pathNameFromZone:(NXZone *)zone
{
	id temp = [self left:[self rspotOf:theMiscPathSeparator] fromZone:zone];
	if (temp) return temp;
	return [[self class] new];
}

- pathName
{
	return [self pathNameFromZone:[self zone]];
}

- fileExtensionFromZone:(NXZone *)zone
{
	id temp = [self fileNameFromZone:[self zone]];
	id extension = [temp extractPart:MISC_STRING_LAST
				useAsDelimiter:theMiscExtensionSeparator
				caseSensitive:YES fromZone:zone];
	if ([temp isEqual:extension] ||
			(0 == [temp rspotOf:theMiscExtensionSeparator
			occurrenceNum:0 caseSensitive:YES]))
		[extension setStringValue:""];
	[temp free];
	return extension;
}

- fileExtension
{
	return [self fileExtensionFromZone:[self zone]];
}

- fileBasenameFromZone:(NXZone *)zone
{
	id base, temp = [self fileNameFromZone:[self zone]];
	int right = [temp rspotOf:theMiscExtensionSeparator
			occurrenceNum:0 caseSensitive:YES] - 1;
	if (right < 0) right = length - 1;
	base = [temp midFrom:0 to:right fromZone:zone];
	[temp free];
	return (base ? base : [[self class] new]);
}

- fileBasename
{
	return [self fileBasenameFromZone:[self zone]];
}

- addExtensionIfNeeded:(const char *)aString
{
	if (!aString) return nil;  // maybe we should _strip_ extensions here
	if (![self endcmp:aString]) return self;
	if ([self charAt:(length - 1)] != theMiscExtensionSeparator)
		[self addCharToEndOfString:theMiscExtensionSeparator];
	[self cat:aString];
	return self;
}

- replaceHomeWithTilde
{
	id homestr = [[self class] newWithString:NXHomeDirectory()];

	if (![self compareTo:homestr n:[homestr length]]) {
	  [self replaceFrom:0 length:[homestr length] withChar:'~'];
	 }
	else {
	  id home2 = [homestr pathName];
	  if (![home2 length]) return self;
	  if (![self compareTo:home2 n:[home2 length]]) {
	    [self replaceFrom:0 length:[home2 length]+1 withChar:'~'];
	  }
	  [home2 free];
	 }
	[homestr free];
	return self;
}

- replaceTildeWithHome
{	// ***** I'd like to see this do proper csh substitution rather than
	// just the simple implementation done here. -don
	id home = [[self class] newWithString:NXHomeDirectory()];
	if (buffer[0] == '~') {
	  if (buffer[1] == theMiscPathSeparator)
		[self replaceFrom:0 length:1 withString:home];
	  else { // This assumption of all users in the same dir. is dangerous -don
	    id home2 = [home pathName];
	    [home2 addChar:theMiscPathSeparator];
	    [self replaceFrom:0 length:1 withString:home2];
	    [home2 free];
	   }
	 }
	[home free];
	return self;
}

- (BOOL)isRelativePath
// Returns YES if the path is non-empty and does NOT begin with pathSeparator.
{
	id tempPath = [self pathName];
	if ([tempPath length] && ([tempPath charAt:0] != theMiscPathSeparator)) {
		[tempPath free];
		return YES;
	}
	[tempPath free];
	return NO;
}

- (BOOL)isAbsolutePath
// Returns YES if the path is non-empty and DOES begin with pathSeparator.
{
	id tempPath = [self pathName];
	if ([tempPath length] && ([tempPath charAt:0] == theMiscPathSeparator)) {
		[tempPath free];
		return YES;
	}
	[tempPath free];
	return NO;
}

- (BOOL)doesExistInFileSystem
// Returns YES if the a file exists at our path, and is visible to the user 
// id of the process.  A NO can mean any number of things, but a YES 
// definitely indicates that the file is there and visible.
{
	struct stat statBuff;
	if (!buffer) return NO;
	if (stat([self stringValue], &statBuff) == -1) return NO;
	return YES;
}

- (BOOL)isFileOfType:(MiscFileType)fileType
// Returns YES if the named file exists, and is visible to the user id of
// the process and it is a file of type fileType.  A NO can mean any number
// of things,  but a YES definitely indicates that the file is there and
// visible and of the fileType type.  
{
	struct stat statBuff;
	if (!buffer) return NO;
	if (stat([self stringValue], &statBuff) == -1) return NO;
	return ((statBuff.st_mode & fileType) == fileType);
}

- pathComponentAt:(int)index
{
	if ((index < 0) || (index >= [self numberOfPathComponents]))
		return [[self class] newWithString:""];
	return [self extractPart:([self isAbsolutePath] ? (index + 1) : index)
				useAsDelimiter:theMiscPathSeparator
				caseSensitive:YES fromZone:[self zone]];
}

- (int)numberOfPathComponents
{
	return ([self numOfChar:theMiscPathSeparator] -
			([self isAbsolutePath] ? 1 : 0));	
}

- setDirectory:(const char *)dir file:(const char *)file
{
	return [self setFromFormat:"%s/%s", dir, file];
}

- initDirectory:(const char *)dir file:(const char *)file
{
	return [self initFromFormat:"%s/%s", dir, file];
}

@end
