//
//	DAYString.m -- a generic class to simplify manipulation of (char *)'s
//		Written by Don Yacktman (c) 1993 by Don Yacktman.
//				Version 1.2  All rights reserved.
//
//		This is a free object!  Contact the author for the latest version.
//		Don Yacktman, 4279 N. Ivy Lane, Provo, UT, 84604
//		e-mail:  Don_Yacktman@byu.edu
//
//		You may use and copy this class freely as long as you
//		comply with the following terms:
//			(1) Do not remove the author's name or any of the
//				copyright notices from this file.
//			(2) If you redistribute an application which uses this
//				object, you must either include the source code for
//				this object with the application or state in your
//				application's documentation that you (a) use this
//				object and (b) where to obtain the source code for
//				the object.
//			(3) In no way shall the author or his employer(s) be held
//				responsible for any damages caused by what this object
//				does or does not do.
//			(4) You have no warranty whatsoever that this object is
//				good for any purpose at all.  If you find it useful
//				for something, consider yourself lucky and leave it at that.
//

//
//	And now for an implementation note... (philosophy of this class)
//	The object is to make a bulletproof class.  That means that NULL
//	pointers and values that are out of range are ignored, or that
//	nil is returned, or that the object tries to do what would be the
//	intelligent thing to do in such boundary cases.  Obviously, all
//	this error checking costs you something:  speed.  On the other hand,
//	your code shouldn't end up with anywhere near as many bus errors
//	and other silliness!  (This is not a panacea; don't rely on the
//	DAYString to get everything right for you; if a boudary condition
//	might exist, do be sure to check return values as they can signal
//	problems for you.)  There is no substitue for good programming,
//	but hopefully this class will help you survive silly mistakes.
//	Most of the methods send to other methods to do the real work;
//	it would run faster to implement each method separately, since
//	some methods would then not need to do certain checks, and so on.
//	On the other hand, it is a whole lot easier to maintain this code
//	since there are a handful of core methods that do all the work.
//	Someday I may attempt to make a class DAYFastString that will
//	be optimized for speed... but only if there is either (1) a *whole*
//	lot of demand for it, or (2) someone pays me to write it.  :-)
//
//	Returns:  I try to follow NeXT conventions.  Return self, nil, or
//		whatever makes sense in the context.  Hopefully your idea of
//		what makes sense will coincide with mine.  Read the docs...
//
//	For this release (1.2), I have gone over every single method and
//		tried to make sure it behaved sanely on all possible boundary
//		cases, especially NULL pointers.  If there is any way to
//		break this class, or cause a bus error in here, etc. or any
//		other bugs I WANT to know about them... be sure to send bug
//		reports to me at Don_Yacktman@byu.edu so I can fix them.  I
//		want this class to be bulletproof.  Note that there are still
//		a few possibilities to get memory leaks here.  I want to fix
//		those, if at all possible, so remind me of the ones you find.
//		I got rid of the most obvious and common ones.
//
//	One memory leak possibility, and this happens in the test app a
//	little bit:  you call a method that returns a new object, but
//	then do something like this:
//		printf("%s", [[aString right:5] stringValue]);
//	A new instance is created, but never freed.  I need a way around this,
//	like a method -stringValueAndFree that returns a const char * and
//	then free itself.  The trick is that the returned char * is still
//	around.  The most elegant solution I can think of is to run the
//	pointer through NXUniqueString() first, and then return it, after
//	freeing the object.  This way the pointer is never lost, and you
//	won't really leak memory, but the string space sure will get cluttered.
//	If you have an idea on how to fix this problem, let me know.  I like
//	the convenience of the above printf, but do not like the memory leak.
//	You could always to this:
//		temp = [aString right:5];
//		printf("%s", [temp stringValue]);
//		[temp free];
//	But that's a pain in the *ss...
//	Well, send me your suggestions.
//

#import <daymisckit/DAYString.h>
#import <strings.h>

@implementation DAYString

- init
{
	 [super init];
	 [self setStringOrderTable:NXDefaultStringOrderTable()];
	 if (buffer) free(buffer);
	 buffer = NULL;
	 length = 0;
	 _length = 0;
	 return self;
}

- initString:(const char *)aString
{
	[self init];
	return [self setStringValue:aString];
}

- setStringOrderTable:(NXStringOrderTable *)table
{
	if (table) orderTable = table;
	else orderTable = NXDefaultStringOrderTable(); // just in case...
	return self;
}

- (NXStringOrderTable *)stringOrderTable
{
	return orderTable;
}

- allocateBuffer:(int)size
{
	return [self allocateBuffer:size fromZone:[self zone]];
}

- allocateBuffer:(int)size fromZone:(NXZone *)zone
{
	if (size <= _length) return self;
	[self freeString];
	if (!size) return self;
	buffer = (char *)NXZoneMalloc(zone, size);
	buffer[0] = 0;
	_length = size;

	return self;
}

- setStringValue:(const char *)aString
{
	return [self setStringValue:aString fromZone:[self zone]];
}

- setStringValue:(const char *)aString fromZone:(NXZone *)zone
{
	if (!aString) return self; // use -freeString to set to "NULL"
	// Note that I could have used NXCopyStringBufferFromZone() here
	// instead.  This works just as well, but it may be slower...
	// I haven't checked that out, though.  It might be worth doing.
	[self allocateBuffer:strlen(aString)+1 fromZone:zone];
	strcpy(buffer, aString);
	length = strlen(buffer);
	return self;
}

- takeStringValue:sender
{ // if no string value, return nil; the user is expecting a changed string
	if (![sender respondsTo:@selector(stringValue)]) return nil;
	return [self setStringValue:[sender stringValue] fromZone:[self zone]];
}

- takeStringValue:sender fromZone:(NXZone *)zone
{
	if (![sender respondsTo:@selector(stringValue)]) return nil;
	return [self setStringValue:[sender stringValue] fromZone:zone];
}

- (const char *)stringValue
{
	return buffer;
}

- read:(NXTypedStream *)stream
{
	int tempLength; char *tBuf;
	[super read:stream];
	NXReadType(stream, "i", &tempLength);
	[self allocateBuffer:tempLength fromZone:[self zone]];
	NXReadType(stream, "*", &tBuf);
	if (tBuf) strcpy(buffer, tBuf);
	if (!tBuf && buffer) buffer[0] = '\0'; // NULL string?
	return self;
}

- write:(NXTypedStream *)stream
{
	[super write:stream];
	NXWriteType(stream, "i", &_length);
	NXWriteType(stream, "*", &buffer);
	return self;
}

- copyFromZone:(NXZone *)zone
{
	DAYString *myCopy = [super copyFromZone:zone];
	// force child to have it's own copy of the string buffer
	[myCopy _unhookBuffer];
	[myCopy allocateBuffer:_length fromZone:zone];
	[myCopy setStringValue:buffer fromZone:zone];
	return myCopy;
}

- _unhookBuffer
{ // used by the copy method so that we don't free the buffer from orig. 
	buffer = NULL; _length = 0;
	return self;
}

- freeString
{
	if(buffer) free(buffer);
	buffer = NULL;
	length = 0;
	_length = 0;
	return self;
}

- free
{
	 [self freeString];
	 return [super free];
}

- cat:(const char *)aString
{
	return [self cat:aString n:strlen(aString) fromZone:[self zone]];
}

- cat:(const char *)aString n:(int)n
{
	return [self cat:aString n:n fromZone:[self zone]];
}

- cat:(const char *)aString fromZone:(NXZone *)zone
{
	return [self cat:aString n:strlen(aString) fromZone:zone];
}

- cat:(const char *)aString n:(int)n fromZone:(NXZone *)zone
{
	char *newBuffer; int newSize;
	if (!(aString || buffer)) return nil;
	if (!buffer) return [self setStringValue:aString fromZone:zone];
	if (!aString) return self;
	if (n > strlen(aString)) n = strlen(aString);
	newSize = length + n + 1;
	if (newSize > _length) {
		newBuffer = (char *)NXZoneMalloc(zone, newSize);
		_length = newSize;
		newBuffer[0] = '\0';
		strcat(newBuffer, buffer);
		strncat(newBuffer, aString, n);
		free(buffer);
		buffer = newBuffer;
	} else  strncat(buffer, aString, n);
	length = strlen(buffer);
	return self;
}

- concatenate:sender
{ // note return self here; assume that there's nothing to add...
	if (![sender respondsTo:@selector(stringValue)]) return self;
	return [self cat:[sender stringValue]
				 n:strlen([sender stringValue])
				 fromZone:[self zone]];
}

- concatenate:sender n:(int)n
{
	if (![sender respondsTo:@selector(stringValue)]) return self;
	return [self cat:[sender stringValue] n:n fromZone:[self zone]];
}

- concatenate:sender fromZone:(NXZone *)zone
{
	if (![sender respondsTo:@selector(stringValue)]) return self;
	return [self cat:[sender stringValue]
			n:strlen([sender stringValue]) fromZone:zone];
}

- concatenate:sender n:(int)n fromZone:(NXZone *)zone
{
	if (![sender respondsTo:@selector(stringValue)]) return self;
	return [self cat:[sender stringValue] n:n fromZone:zone];
}

- (const char *)strstr:(const char *)subString
{
	if (!(buffer||subString)) return NULL;
	return strstr(buffer, subString);
}

- subStringRight:subString
{
	const char *sub, *sub2;
	if (!buffer) return nil;
	if ([subString respondsTo:@selector(stringValue)])
		sub = [subString stringValue];
	else return nil;	// error if can't get string value
	if (!sub) return nil;
	sub2 = strstr(buffer,sub);
	if (!sub2) return nil;
	return [[DAYString allocFromZone:[self zone]]
			initString:sub2];
}

- subStringLeft:subString
{
/*	Old implementation:  ugly and has potential memory leak
	const char *sub;
	char *tempString, *temp2;
	id retVal = [DAYString alloc];
	
	if ([subString respondsTo:@selector(stringValue)])
		sub = [subString stringValue];
	else return nil;	// error if can't get string value
	tempString = NXCopyStringBufferFromZone(buffer, [self zone]);
	temp2 = strstr(tempString, sub); 
	if (temp2) {
		temp2[0] = '\0';	// terminate it early
		[retVal initString:tempString];
	} else { // substring not found
		return [self copy];
	}
	free(tempString);
	return retVal;
*/
	const char *sub;
	char *sub2;
	int spot;

	if ([subString respondsTo:@selector(stringValue)])
		sub = [subString stringValue];
	else return nil;	// error if can't get string value
	if (!sub) return nil;
	if (!(sub2 = strstr(buffer, sub))) return nil;
	spot = (int)sub2 - (int)buffer - 1;
	if (spot < 0) return nil;
	return [self midFrom:0 to:spot];
}

- (int)length
{
	return length;
}

- (BOOL)isEqual:anObject
{
	if (anObject == self) return YES;
	// doesn't have to be a DAYString object to be equal...
	if ([anObject respondsTo:@selector(stringValue)]) {
		if (!NXOrderStrings(buffer, [anObject stringValue],
				YES, -1, orderTable)) return YES;
	}
	return NO;
}

- (int)compareTo:sender
{
	return [self compareTo:sender n:(-1) caseSensitive:YES];
}

- (int)compareTo:sender n:(int)n
{
	return [self compareTo:sender n:n caseSensitive:YES];
}

- (int)cmp:(const char *)aString
{
	if (!aString && !buffer) return 0;		// both NULL, so "equal"
	if (!aString || !buffer) return -1;	// only one is NULL, so not equal
	return strcmp(buffer, aString);
}

- (int)cmp:(const char *)aString n:(int)n
{
	if (!aString && !buffer) return 0;		// both NULL, so "equal"
	if (!aString || !buffer) return -1;	// only one is NULL, so not equal
	// we don't check n; use strncmp's behavior here.
	return strncmp(buffer, aString, n);
}

- (int)compareTo:sender caseSensitive:(BOOL)sense
{
	return [self compareTo:sender n:(-1) caseSensitive:sense];
}

- (int)compareTo:sender n:(int)n caseSensitive:(BOOL)sense
{
	if (![sender respondsTo:@selector(stringValue)]) return 1; // !=
	if (!buffer && ![sender stringValue]) return 0;		// both are NULL
	if (!(buffer && [sender stringValue])) return 1;	// one is NULL
	return NXOrderStrings(buffer, [sender stringValue], sense, n, orderTable);
}

- (int)casecmp:(const char *)aString
{
	if (!aString && !buffer) return 0;		// both NULL, so "equal"
	if (!aString || !buffer) return -1;	// only one is NULL, so not equal
	return strcasecmp(buffer, aString);
}

- (int)casecmp:(const char *)aString n:(int)n
{
	if (!aString && !buffer) return 0;		// both NULL, so "equal"
	if (!aString || !buffer) return -1;	// only one is NULL, so not equal
	// we don't check n; use strncasecmp's behavior here.
	return strncasecmp(buffer, aString, n);
}

- left:(int)count
{
	return [self left:count fromZone:[self zone]];
}

- right:(int)count
{
	return [self right:count fromZone:[self zone]];
}

- midFrom:(int)start to:(int)end
{
	return [self midFrom:start to:end fromZone:[self zone]];
}

- midFrom:(int)start length:(int)len
{
	return [self midFrom:start length:len fromZone:[self zone]];
}

- left:(int)count fromZone:(NXZone *)zone
{
	char smash = buffer[count];
	id newString;
	if (!buffer) return nil;
	if (count >= length) return [self copyFromZone:zone];
	newString = [[DAYString allocFromZone:zone] init];
	buffer[count] = '\0';
	[newString setStringValue:buffer fromZone:zone];
	buffer[count] = smash;
	return newString;
}

- right:(int)count fromZone:(NXZone *)zone
{
	id newString;
	if (!buffer) return nil;
	if (count >= length) return [self copyFromZone:zone];
	newString = [[DAYString allocFromZone:zone] init];
	[newString setStringValue:&buffer[length - count] fromZone:zone];
	return newString;
}

- midFrom:(int)start to:(int)end fromZone:(NXZone *)zone
{
	char smash = buffer[end+1];
	id newString;
	if (!buffer) return nil;
	if ((end < 0) || (start >= length)) return nil;
	if (end >= length) end = length-1;
	if (start < 0) start = 0;
	newString = [[DAYString allocFromZone:zone] init];
	buffer[end+1] = '\0'; // inclusive; end-1 is not. (well, end isn't either, anymore -- Carl)
	[newString setStringValue:&buffer[start] fromZone:zone];
	buffer[end+1] = smash;
	return newString;
}

- midFrom:(int)start length:(int)len fromZone:(NXZone *)zone
{
	return [self midFrom:start to:(start + len - 1) fromZone:zone];
/*	faster to have our own code here, but rather than maintain this,
	we use the cover above.  I'm keeping the code around in case I
	decide to do DAYFastString.
	
	register int spot = start + len;
	char smash = buffer[spot];
	id newString;
	if (!buffer) return nil;
	if ((end < 0) || (start >= length)) return nil;
	if (start < 0) start = 0;
	if (start+len-1 >= length) len = length-start;
	newString = [[DAYString allocFromZone:zone] init];
	buffer[spot] = '\0';
	[newString setStringValue:&buffer[start] fromZone:zone];
	buffer[spot] = smash;
	return newString;
*/
}

- encrypt:salt
{	// encrypt as a UNIX password using the DAYString "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;
	return [[DAYString alloc] initString:crypt(buffer, strv)];
}

// NXTransport protocol implementation:
- encodeUsing:(id <NXEncoding>)portal
{
	[portal encodeData:&_length ofType:"i"];
	[portal encodeData:&length ofType:"i"];
	[portal encodeData:&buffer ofType:"*"];
	return self;
}

- decodeUsing:(id <NXDecoding>)portal
{
	int newLen;
	[self freeString];
	[portal decodeData:&newLen ofType:"i"];
	[self allocateBuffer:newLen];
	[portal decodeData:&length ofType:"i"];
	[portal decodeData:&buffer ofType:"*"];
	return self;
}

- encodeRemotelyFor:(NXConnection *)connection
	freeAfterEncoding:(BOOL *)flagp isBycopy:(BOOL)isByCopy
{
	if (isByCopy) {
		*flagp = NO; // object will copy.
		return self; //encode object (copy it)
	}
	*flagp = NO; // object will copy.
	// super will encode the proxy otherwise
	return [super encodeRemotelyFor:connection
				freeAfterEncoding:flagp isBycopy:isByCopy];
}

// Interface Builder support
- (const char *)getInspectorClassName { return "DAYStringInspector"; }
- (NXImage *)getIBImage { return [NXImage findImageNamed:"DAYStringObj"]; }

// some other new methods V1.2 -- don

- extractPart:(int)n useAsDelimiter:(char)c caseSensitive:(BOOL)sense fromZone:(NXZone *)zone
{
	int left = ((n == DAY_FIRST) ? 0 :
			( n == DAY_LAST ?
				([self rspotOf:c occurenceNum:1 caseSensitive:sense] + 1) :
				([self spotOf:c occurenceNum:(n-1) caseSensitive:sense] + 1)));
	int right = ((n == DAY_FIRST) ?
			([self spotOf:c occurenceNum:1 caseSensitive:sense] - 1) :
			( n == DAY_LAST ? length :
				[self spotOf:c occurenceNum:n caseSensitive:sense] - 1));
	return [self midFrom:left to:right fromZone:zone];
}

- extractPart:(int)n useAsDelimiter:(char)c caseSensitive:(BOOL)sense
{
	return [self extractPart:n useAsDelimiter:c
			caseSensitive:sense fromZone:[self zone]];
}

- extractPart:(int)n useAsDelimiter:(char)c fromZone:(NXZone *)zone
{
	return [self extractPart:n useAsDelimiter:c
			caseSensitive:YES fromZone:zone];
}

- extractPart:(int)n useAsDelimiter:(char)c
{
	return [self extractPart:n useAsDelimiter:c\
			caseSensitive:YES fromZone:[self zone]];
}

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

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

- pathNameFromZone:(NXZone *)zone
{
	return [self left:[self rspotOf:'/'] fromZone:zone];
}

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

//Carl's additions....

+ newWithString:(const char *)aString
// I just got tired of typing [[[DAYString alloc] init] setStringValue:xxx] 
{
	id newString = [[DAYString alloc] init]; // should I use [[self alloc]... ?
	if ([newString setStringValue:aString]) return newString;
	[newString free];
	return nil;
}

- wordNum :(int) num
// returns a new String containing the numth word in buffer
// if numth word does not exist, returns nil.
{
	int i = 0;
	int currword = 1;
	int spot = 0; 
	int spot2 = 0;

	if (!buffer) return nil;
	while ((currword <= num) && (i <= length)) {
		while ((NXIsSpace(buffer[i])) && (i <= length)) i++;
		spot2 = i;
		while ((!NXIsSpace(buffer[i])) && (i <= length)) i++;
		spot = i;
		currword++;
	}
	if (spot == spot2) return nil;
	return [self midFrom :spot2 length:spot-spot2];
}

- (int)numWords
{ //counts the number of words in buffer.
	int i=0;
	int currword = 0;

	if (!buffer) return 0;  
	while (i <= length) {
		while ((NXIsSpace(buffer[i])) && (i <= length)) i++;
		while ((!NXIsSpace(buffer[i])) && (i <= length)) i++;
		currword++;
	}
	if (NXIsSpace(buffer[length-1])) currword--;
	return currword;
}

- trimLeadSpaces
{ // removes any leading spaces from buffer
  int i = 0;
  id tmpStr;
  
  if (!buffer) return self;
  while (buffer[i] == ' ') i++;
  if (i==0) return self;
  tmpStr = [self right:length-i];
  [self takeStringValue:tmpStr];
  [tmpStr free];
  return self;
}

- trimTailSpaces
// removes any trailing spaces from buffer
{
  int i = length;
  id tmpStr;
  
  if (!buffer) return self;
  while (buffer[i-1] == ' ') i--;
  if (i==length) return self;
  tmpStr = [self left:i];
  [self takeStringValue:tmpStr];
  [tmpStr free];
  return self;
}


- trimSpaces
// takes off leading and trailing spaces of the buffer
{
  return [[self trimLeadSpaces] trimTailSpaces];
}

- reverse
// reverses the characters in the buffer.  If it's a palindrome, you won't
// notice much of a difference :-)
{
  char tmp[length+1];
  int j=0;
  int i;
  
  if (length <= 1) return self;
  for (i=length-1;i>=0;i--)
   {
     tmp[j] = buffer[i];
     j++;
   }
  tmp[length] = 0;
  if (length != 0) [self setStringValue:tmp];
  return self;
}
  
  
- toUpper
// converts any lowercase characters in buffer to uppercase
{
  int i;
  
  for (i=0;i<length;i++)
   {
     if (NXIsLower(buffer[i])) buffer[i] = NXToUpper(buffer[i]);
   }
  
  return self;
}

- toLower
// converts any uppercase chars in buffer to lowercase
{
  int i;
  
  for (i=0;i<length;i++)
   {
     if (NXIsUpper(buffer[i])) buffer[i] = NXToLower(buffer[i]);
   }
  
  return self;
}

- insert:(const char *)aString at:(int)index
// inserts given string into buffer starting at index.
// (the first character is position #0)
{
  id temp1;
  id temp2;
  
  if ((aString == NULL) || (strlen(aString)<=0)) return self;
  if (index < 0) index = 0;         
  if (index >= length) return [self cat:aString];
  
  temp1 = [self left:index];
  temp2 = [self right:length-index];  
  [[temp1 cat:aString] concatenate:temp2];
  [self takeStringValue:temp1];

  [temp1 free];
  [temp2 free];
  return self;
}

- insertString:(id)sender at:(int)index
// cover for insert:at: for a String object
{
  if (![sender respondsTo:@selector(stringValue)]) return self;
  return [self insert:[sender stringValue] at:index];
}

- insertChar:(char)aChar at:(int)index
{
  id tempStr;
  id retval;
  
  if (aChar == 0) return self;	// or should this return nil? DAY: leave it
  
  tempStr = [[[[DAYString alloc] init] allocateBuffer:2] addChar:aChar];
  retval = [self insert:[tempStr stringValue] at:index];
  [tempStr free];
  return retval;
}
  
- (char) charAt:(int)index
{
  if ((index < 0) || (index >length-1)) return 0;
  return (char)buffer[index];
}

- removeFrom:(int)index length:(int)len
{ // to avoid memory leaks, this should NEVER return nil!
  id temp1,temp2;
  
  if (!buffer) return self;	// everything's already gone
  if (len <= 0) return self; // noting to remove
  // DAY: should I presume to fix index<0 like so? or just index = 0?
  // or just return self?
  if (index < 0) { length += index; index = 0; if (len <= 0) return self; }
  if (index > length - 1) return self; // nothing out there
  temp1 = [self left:index];
  temp2 = [self midFrom:index+len to:length];
  [temp1 concatenate:temp2];
  [self takeStringValue:temp1];
  [temp1 free];
  [temp2 free];
  return self;
}

- removeFrom:(int)start to:(int)end
{
  return [self removeFrom:start length:end-start+1];
}

- replaceFrom:(int)start length:(int)len with:(const char *)aString
{
  return [[self removeFrom:start length:len] insert:aString at:start];
}

- replaceFrom:(int)start to:(int)end with:(const char *)aString
{
  return [[self removeFrom:start to:end] insert:aString at:start];
}

- replaceFrom:(int)start length:(int)len withString:(id)sender
{
  if (![sender respondsTo:@selector(stringValue)]) return self;
  return [self replaceFrom:start length:len with:[sender stringValue]];
}

- replaceFrom:(int)start to:(int)end withString:(id)sender
{
  if (![sender respondsTo:@selector(stringValue)]) return self;
  return [self replaceFrom:start to:end with:[sender stringValue]];
}

- replace:(const char *)subString with:(const char *)newString
{
  const char *spot;
  
  if (spot = [self strstr:subString])
    [self replaceFrom:(int)(spot-buffer) length:strlen(subString) with:newString];
  return self;
}

- replace:(const char *)subString withString:(id)sender
{
  if (![sender respondsTo:@selector(stringValue)]) return self;
  return [self replace:subString with:[sender stringValue]];
}

- (int)spotOf:(char)aChar
{
  return [self spotOf:aChar occurenceNum:1 caseSensitive:YES];
}

- (int)spotOf:(char)aChar caseSensitive:(BOOL)sense
{
  return [self spotOf:aChar occurenceNum:1 caseSensitive:sense];
}

- (int)spotOf:(char)aChar occurenceNum:(int)n
{
  return [self spotOf:aChar occurenceNum:n caseSensitive:YES];
}

- (int)rspotOf:(char)aChar
{
  return [self rspotOf:aChar occurenceNum:1 caseSensitive:YES];
}

- (int)rspotOf:(char)aChar caseSensitive:(BOOL)sense
{
  return [self rspotOf:aChar occurenceNum:1 caseSensitive:sense];
}

- (int)rspotOf:(char)aChar occurenceNum:(int)n
{
  return [self rspotOf:aChar occurenceNum:n caseSensitive:YES];
}

- (int)spotOf:(char)aChar occurenceNum:(int)n caseSensitive:(BOOL)sense
{
  int currnum = 0;
  int count = 0;
  
  if (n<1) return -1;
  
  while ((currnum < n) && (count<length)) {
    if (!sense) {
      if (NXToUpper(buffer[count]) == NXToUpper(aChar)) currnum++;
     }
    else {
      if (buffer[count] == aChar) currnum++;
     }
    count++;
   }
  if (currnum != n) return -1;
  return (count-1);
}

- (int)rspotOf:(char)aChar occurenceNum:(int)n caseSensitive:(BOOL)sense
{
  int currnum = 0;
  int count = length-1;
  
  if (n<1) return -1;
  
  while ((currnum < n) && (count >= 0)) {
    if (!sense) {
      if (NXToUpper(buffer[count]) == NXToUpper(aChar)) currnum++;
     }
    else {
      if (buffer[count] == aChar) currnum++;
     }
    count--;
   }
  if (currnum != n) return -1;
  return (count+1);
}

- addChar:(char)aChar
{
  if (aChar) [self cat:&aChar n:1];
  return self;
}

- squashSpaces
{
  int count = 0;
  id tempStr;
  
  if (!buffer) return self;
  [self trimSpaces];
  tempStr = [[[DAYString alloc] init] allocateBuffer:length];
  while (count<length) {
    while (buffer[count]!=' ') {
      [tempStr addChar:buffer[count]];
      count++;
     }
    if ((count<length) && (buffer[count] == ' ')) {
      [tempStr addChar:buffer[count]];
      count++;
     }
    if ((count<length) && (buffer[count]==' ') && 
       ((buffer[count-2] == ':') || (buffer[count-2] =='.'))) {
         [tempStr addChar:buffer[count]];
         count++;
     }
    while (buffer[count]==' ') count++;
   }
  [self takeStringValue:tempStr];
  [tempStr free];
  return self;
}

- (const char *)rindex:(char)aChar
{
	return [self rindex:aChar occurenceNum:1 caseSensitive:YES];
}

- (const char *)rindex:(char)aChar occurenceNum:(int)n
{
	return [self rindex:aChar occurenceNum:n caseSensitive:YES];
}

- (const char *)rindex:(char)aChar caseSensitive:(BOOL)sense
{
	return [self rindex:aChar occurenceNum:1 caseSensitive:sense];
}

- (const char *)rindex:(char)aChar occurenceNum:(int)n caseSensitive:(BOOL)sense
{
  int num;
  num = [self rspotOf:aChar occurenceNum:n caseSensitive:sense];
  if (num == -1) return NULL;
  return buffer+num;
}

- (const char *)index:(char)aChar
{
	return [self index:aChar occurenceNum:1 caseSensitive:YES];
}

- (const char *)index:(char)aChar occurenceNum:(int)n
{
	return [self index:aChar occurenceNum:n caseSensitive:YES];
}

- (const char *)index:(char)aChar caseSensitive:(BOOL)sense
{
	return [self index:aChar occurenceNum:1 caseSensitive:sense];
}

- (const char *)index:(char)aChar occurenceNum:(int)n caseSensitive:(BOOL)sense
{
  int num;
  num = [self spotOf:aChar occurenceNum:n caseSensitive:sense];
  if (num==-1) return NULL;
  return buffer+num;
}

- (int)endcmp:(const char *)aString
{
  return [self endcmp:aString n:-1];
}

- (int)endcmp:(const char *)aString n:(int)n
{
  id tempStr;
  int retval;
  if (!aString) return -1;
  tempStr = [DAYString newWithString:aString];
  retval = [self endCompareTo:tempStr n:n caseSensitive:YES];
  [tempStr free];
  return retval;
}

- (int)endcasecmp:(const char *)aString
{
  return [self endcasecmp:aString n:-1];
}

- (int)endcasecmp:(const char *)aString n:(int)n
{
  id tempStr;
  int retval;
  if (!aString) return -1;
  tempStr = [DAYString newWithString:aString];
  retval = [self endCompareTo:tempStr n:n caseSensitive:NO];
  [tempStr free];
  return retval;
}

- (int)endCompareTo:(id)sender
{
  return [self endCompareTo:sender n:-1 caseSensitive:YES];
}

- (int)endCompareTo:(id)sender caseSensitive:(BOOL)sense
{
  return [self endCompareTo:sender n:-1 caseSensitive:sense];
}

- (int)endCompareTo:(id)sender n:(int)n
{
  return [self endCompareTo:sender n:n caseSensitive:YES];
}

- (int)endCompareTo:(id)sender n:(int)n caseSensitive:(BOOL)sense
{ // DAY: fixed it: sender doesn't have to be a DAYString anymore.
	id tempStr, temp2, temp3,
		smaller = ((length >= [sender length]) ? sender : self);
	int retval, smallLen;
	if (!([sender respondsTo:@selector(stringValue)])) return -1;
	if (![sender stringValue] || !buffer) return -1;
	if ([sender respondsTo:@selector(length)]) smallLen = [smaller length];
	else smallLen = strlen([smaller stringValue]);
	if ((n == -1) || (n > smallLen)) n = smallLen;
	tempStr = [self right:n];
	if ([sender respondsTo:@selector(right:)]) temp2 = [sender right:n];
	else {
		temp3 = [DAYString newWithString:[sender stringValue]];
		temp2 = [temp3 right:n];
		[temp3 free];
	}
	retval = [tempStr compareTo:temp2 caseSensitive:sense];
	[tempStr free];
	[temp2 free];
	return retval;
}

- replaceCharAt:(int)index with:(char)aChar
{	// no change if index is out of range.
	// Other option is to limit index to be in range.  which is better?
  if ((index < 0) || (index >= length)) return self;

  if (aChar == 0) return self; // should this return nil? DAY: leave it.

  buffer[index] = aChar;
  return self;
}

- replaceFrom:(int)start length:(int)len withChar:(char)aChar
{
    id retval,tempStr;
   
    if (aChar == 0) return nil;
    tempStr = [[[DAYString alloc] init] addChar:aChar];
    retval = [self  replaceFrom:start length:len
					withString:tempStr];
    [tempStr free];
    return retval;
}

- replaceFrom:(int)start to:(int)end withChar:(char)aChar
{
  return [self replaceFrom:start length:end-start+1 withChar:aChar];
}

@end
