//
//	MiscStringFields.m
//		Written by Don Yacktman (c) 1993 by Don Yacktman.
//				Version 1.95  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>

@implementation MiscString(Fields)

// This category is composed of methods which locate specific fields
// within a MiscString.  Mostly useful for various types of parsing.

- 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 [[[self class] allocFromZone:[self zone]]
			initString:sub2];
}

- subStringLeft:subString
{
	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];
}

- 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;
	// perhaps I should return an empty MiscString here rather than nil?
	if ((count <= 0) || !buffer) return nil;
	if (count >= length) return [self copyFromZone:zone];
	newString = [[[self class] allocFromZone:zone] init];
	buffer[count] = '\0';
	[newString setStringValue:buffer fromZone:zone];
	buffer[count] = smash;
	return newString;
}

- right:(int)count fromZone:(NXZone *)zone
{
	id newString;
	// perhaps I should return an empty MiscString here rather than nil?
	if ((count <= 0) || !buffer) return nil;
	if (count >= length) return [self copyFromZone:zone];
	newString = [[[self class] allocFromZone:zone] init];
	[newString setStringValue:&buffer[length - count] fromZone:zone];
	return newString;
}

- midFrom:(int)start to:(int)end fromZone:(NXZone *)zone
{
	char smash;
	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 = [[[self class] allocFromZone:zone] init];
	smash = buffer[end+1];
	buffer[end+1] = '\0'; // inclusive; end is not.
	[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 MiscFastString.
	
	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 = [[[self class] allocFromZone:zone] init];
	buffer[spot] = '\0';
	[newString setStringValue:&buffer[start] fromZone:zone];
	buffer[spot] = smash;
	return newString;
*/
}

- extractPart:(int)n useAsDelimiter:(char)c caseSensitive:(BOOL)sense fromZone:(NXZone *)zone
{	// code has to return an empty object if field is empty, but nil if
	// requested field # is > # of fields

	// What we will do is use the midFrom:to: to grab a segment of the
	// string, and so we need to know where the left and right bounds
	// are on the field in question.
	int left, right;
	switch (n) {
		case MISC_STRING_FIRST : {
			left = 0;
			right = [self spotOf:c occurrenceNum:0 caseSensitive:sense] - 1;
			if (right < -1) return [self copy]; // only one field
			if (right == -1) // field is empty
				return [[self class] newWithString:""];
			break;
		}
		case MISC_STRING_LAST : {
			left = [self rspotOf:c occurrenceNum:0 caseSensitive:sense] + 1;
			if (left < 1) return [self copy]; // only one field
			right = length - 1;
			if (right < left) // field is empty
				return [[self class] newWithString:""];
			break;
		}
		default : { // won't work for field #0, but MISC_STRING_FIRST == 0
			left = [self spotOf:c occurrenceNum:(n-1) caseSensitive:sense] + 1;
			right = [self spotOf:c occurrenceNum:n caseSensitive:sense] - 1;
			if (left >= 1) { // if left == 0, then no field, so leave right
				// at -1 to trigger midFrom:to:fromZone: to return nil.
				if (right == -2) right = length - 1; // if it's the last field
				if (right < left) // field is empty
					return [[self class] newWithString:""];
			}
		}
	}
	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]];
}

- wordNum:(int)num
{
	return [self wordNum:num fromZone:[self zone]];
}

- wordNum:(int) num fromZone:(NXZone *)zone
// returns a new String containing the numth word in buffer
// if numth word does not exist, returns nil.
{
	int i = 0;
	int currword = 0;
	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 fromZone:zone];
}

- (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;
}

- tokenize:(const char *)breakChars into:aList
// Uses strtok() on a COPY of the strings contents to break the string
// into tokens.  Fills aList with MiscString objects containing (in order) 
// the tokens from strtok.  If aList is nil a list is allocated, but the 
// caller is still responsible for freeing it.  This is a "safe" usage of 
// strtok() (although not "thread-safe").  strtok() relies on static 
// variables to keep track of where it is in between calls.  This is
// "safe" in that we parse the whole thing before returning, so no one
// in this thread will start parsing another string with strtok() while
// we're in the middle of ours.
{
	char *toker, *token;

	// Allocate the list if we need to.
	if (aList == nil)  {
		aList = [[List allocFromZone:[self zone]] init];
	}

	// Copy our contents into a scratch buffer.
	NX_MALLOC(toker, char, length+1);
	strcpy(toker, buffer);
	
	// Start toking on it.
	token = strtok(toker, breakChars);
	while (token)  {
		[aList addObject:[[[self class] allocFromZone:[self zone]] 
					initString:token]];
		token = strtok(NULL, breakChars);
	}
	
	// As you allocate, so shall ye free.
	NX_FREE(toker);	
	return aList;
}

@end
