// MOString.m
//
// by Mike Ferris
// Part of MOKit
// Copyright 1993, all rights reserved.

// ABOUT MOKit
// by Mike Ferris (mike@lorax.com)
//
// MOKit is a collection of useful and general objects.  Permission is 
// granted by the author to use MOKit in your own programs in any way 
// you see fit.  All other rights pertaining to the kit are reserved by the 
// author including the right to sell these objects as objects,  as part 
// of a LIBRARY, or as SOURCE CODE.  In plain English, I wish to retain 
// rights to these objects as objects, but allow the use of the objects 
// as pieces in a fully functional program.  Permission is also granted to 
// redistribute the source code of MOKit for FREE as long as this copyright 
// notice is left intact and unchanged.  NO WARRANTY is expressed or implied.  
// The author will under no circumstances be held responsible for ANY 
// consequences from the use of these objects.  Since you don't have to pay 
// for them, and full source is provided, I think this is perfectly fair.

#import "MOKit/MOString.h"
#import <objc/objc-runtime.h>

#define CLASS_VERSION		0
#define CLASS_NAME			"MOString"

extern char *MOBuildStringFromFormatV(const char *formatStr, 
			va_list param_list);

@implementation MOString

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=- Initializing  the class -=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

+ initialize
// Set our version.
{
	if (self == objc_lookUpClass(CLASS_NAME))  {
		[self setVersion:CLASS_VERSION];
	}
	return self;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-= Initializing, copying,  and freeing MOStrings =-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

- init
// Initializes a NULL instance of the string
{
	[self initStringValueNoCopy:NULL shouldFree:NO];
	return self;
}

- initStringValue:(const char *)s
// Initializes an instance with a copy of s
{
	char *copyOfS=NULL;
	
	if (s)  {
		NX_MALLOC(copyOfS, char, strlen(s)+1);
		strcpy(copyOfS, s);
	}
	[self initStringValueNoCopy:copyOfS shouldFree:YES];
	
	return self;
}

- initStringValueNoCopy:(char *)s
// Initializes an instance with s.  No copy is made, but the string takes
// responsibility for the memory.
{
	[self initStringValueNoCopy:s shouldFree:YES];
	
	return self;
}

- initStringValueNoCopy:(char *)s shouldFree:(BOOL)flag
// DESIGNATED INITIALIZER: inits an instance using the string passed.
// No copy is made and the String object assumes responsibility for
// freeing it's memory only if flag is YES.
// Override this and the other initializers should work properly without
// having to override them (unless you want to change their behavior).
{
	[super init];
	
	[self setStringValueNoCopy:s shouldFree:flag];
	
	return self;
}

- initStringValueUnique:(const char *)s
// Initializes the string as a unique string.
{
	[self initStringValueNoCopy:NULL shouldFree:NO];
	[self setStringValueUnique:s];
	return self;
}

- initFromFormat:(const char *)formatStr, ...
// Initializes the string from printf style format string and arguments.
{
	va_list param_list;
	char *buf;
	
	va_start(param_list, formatStr);
	buf = MOBuildStringFromFormatV(formatStr, param_list);
	va_end(param_list);

	[self initStringValueNoCopy:buf shouldFree:YES];
	
	return self;
}

- copyFromZone:(NXZone *)zone
// Copies the string (making a copy of the str memory too only if the
// original will free its memory.)  The copy is always configured exactly
// as the original except for (possibly) the memory it uses.
{
	id obj = [super copyFromZone:zone];
	
	// a non-freeing MOString that "owns" its memory needs its OWN memory
	if (shouldFree)  {
		[obj setShouldFree:NO];		// So our original string won't get freed.
		[obj setStringValue:[self stringValue]];
	}
	
	return obj;
}

- deepCopy
// Returns a copy of the receiver in the same zone as the receiver.
// Always copies the contents too.  The new object always ends up with
// shouldFree = YES.  The returned MOString is NOT unique, even if the
// original was.
{
	return [self deepCopyFromZone:[self zone]];
}

- deepCopyFromZone:(NXZone *)zone
// Returns a copy of the receiver in the zone given.
// Always copies the contents too.  The new object always ends up with
// shouldFree = YES.  The returned MOString is NOT unique, even if the
// original was.
{
	id obj = [super copyFromZone:zone];
	
	// always make a copy of the contents.
	[obj setShouldFree:NO];		// So our original string won't get freed.
	[obj setStringValue:[self stringValue]];
	
	return obj;
}

- shallowCopy
// Returns a copy of the receiver in the same zone as the receiver.
// Never copies the contents.  The new object always ends up with
// shouldFree = NO.  Everything else is preserved.  This can be dangerous
// because if the original string is freed before the copy, the
// copy will point to oblivion.
{
	return [self shallowCopyFromZone:[self zone]];
}

- shallowCopyFromZone:(NXZone *)zone
// Returns a copy of the receiver in the zone given.
// Never copies the contents.  The new object always ends up with
// shouldFree = NO.  Everything else is preserved.  This can be dangerous
// because if the original string is freed before the copy, the
// copy will point to oblivion.
{
	id obj = [super copyFromZone:zone];
	
	// never make a copy of the contents, but always set the shouldFree
	// flag to NO.
	[obj setShouldFree:NO];
	
	return obj;
}

- free
// Frees the string object and storage.
{
	if (shouldFree) NX_FREE(str);
	
	return [super free];
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-= Configuring MOStrings =-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

- setStringValue:(const char *)s
// Sets the string to a copy of s.
{
	if (shouldFree) NX_FREE(str);
	
	if (s==NULL)  {
		return [self setNull];
	}
	len = strlen(s);
	NX_MALLOC(str, char, len+1);
	strcpy(str, s);
	isUnique = NO;
	uStr = NULL;
	shouldFree = YES;
	
	return self;
}

- setStringValueNoCopy:(char *)s
// Sets the string ptr to s.
// No copy is made and the String object does not assume responsibility for
// freeing it's memory.
{
	[self setStringValueNoCopy:s shouldFree:NO];
	
	return self;
}

- setStringValueNoCopy:(char *)s shouldFree:(BOOL)flag
// Sets the string ptr to s.
// No copy is made and the String object assumes responsibility for
// freeing it's memory if flag is YES.  If flag is NO, the String will NOT
// free the memory.
{
	if (shouldFree) NX_FREE(str);
	
	if (s==NULL)  {
		return [self setNull];
	}
	str = s;
	len = strlen(s);
	isUnique = NO;
	uStr = NULL;
	shouldFree = flag;

	return self;
}

- setStringValueUnique:(const char *)s
// Sets the string ptr to a 'Uniqued' version of s.  Unique strings
// are never freed by MOString.
{
	if (shouldFree) NX_FREE(str);
	
	if (s==NULL)  {
		return [self setNull];
	}
	uStr = NXUniqueString(s);
	len = strlen(uStr);
	isUnique = YES;
	str = NULL;
	shouldFree = NO;
	
	return self;
}

- setFromFormat:(const char *)formatStr, ...
// Sets the string from and printf style format string and arguments.
{
	va_list param_list;
	char *buf;
	
	va_start(param_list, formatStr);
	buf = MOBuildStringFromFormatV(formatStr, param_list);
	va_end(param_list);
	
	[self setStringValueNoCopy:buf shouldFree:YES];
	
	return self;
}

- setIntValue:(int)val
// Sets the string by converting the given int to a string.
{
	return [self setFromFormat:"%d", val];
}

- setFloatValue:(float)val
// Sets the string by converting the given float to a string.
{
	return [self setFromFormat:"%f", val];
}

- setDoubleValue:(double)val
// Sets the string by converting the given double to a string.
{
	return [self setFromFormat:"%f", val];
}

- setNull
// Sets the string ptr to NULL (freeing any string that used to be there
// if it should).
{
	if (shouldFree) NX_FREE(str);
	
	str = NULL;
	len = 0;
	isUnique = NO;
	uStr = NULL;
	shouldFree = NO;
	
	return self;
}

- makeUnique
// Transforms the string into a unique string.  Meaning that the string
// won't be freed by the MOString.  The old representation of the string
// is freed after the conversion if it should be.
{
	if ([self isNull])  {
		return nil;
	}
	
	if (isUnique) return self;
	
	uStr = NXUniqueString(str);
	if (shouldFree) NX_FREE(str);
	str = NULL;
	isUnique = YES;
	shouldFree = NO;
	
	return self;
}

- setShouldFree:(BOOL)flag
// Set whether this string will be freed.  If the string is unique, do not
// allow this flag to change.
{
	if (!isUnique)  {
		shouldFree = flag;
	}
	return self;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=- Querying  MOStrings -=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

- (const char *)stringValue
// Returns the actual internal string ptr.  DO NOT MODIFY IT.
{
	return (isUnique?uStr:str);
}

- (int)intValue
// Returns the string converted to an int.
{
	return atoi(isUnique?uStr:str);
}

- (float)floatValue
// Returns the string converted to an float.
{
	return (float)atof(isUnique?uStr:str);
}

- (double)doubleValue
// Returns the string converted to an double.
{
	return atof(isUnique?uStr:str);
}

- (char *)getCopyInto:(char *)buf
// Returns a copy of the contents of the MOString.  If buf is non-NULL then
// the memory it points to is used and returned and it better be big enough.  
// If buf is NULL then exactly enough memory is allocated and returned.
{
	if (!buf)  {
		NX_MALLOC(buf, char, len+1);
	}
	strcpy(buf, (isUnique?uStr:str));
	return buf;
}

- (size_t)length
// Returns the length of the string.
{
	return len;
}

- (unsigned int)count
// Returns the length of the string.
{
	return (unsigned int)len;
}

- (BOOL)isNull
// Returns whether the string is null.
{
	return (isUnique?uStr==NULL:str==NULL);
}

- (BOOL)isEmpty
// Returns whether the length is zero.  (ie returns YES if the string isNull
// or is "")
{
	return (len==0);
}

- (BOOL)isUnique
// Returns whether the string is unique.  If it isUnique you can treat 
// the return value of -str or -stringValue can be treated as an NXAtom.
{
	return isUnique;
}

- (BOOL)shouldFree
// Returns whether the MOString will free the memory used to hold its contents.
{
	return shouldFree;
}


// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-= Archiving methods =-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

- write:(NXTypedStream *)typedStream
// Archive the string to a typed stream.
{
	[super write:typedStream];
	NXWriteType(typedStream, "c", &isUnique);
	if (isUnique)  {
		NXWriteType(typedStream, "%", &uStr);
	}  else  {
		NXWriteType(typedStream, "*",  &str);
	}
	NXWriteType(typedStream, "i", &len);
	NXWriteType(typedStream, "c", &shouldFree);
	
	return self;
}

- read:(NXTypedStream *)typedStream
// int NXTypedStreamClassVersion(NXTypedStream *typedStream, 
// 				const char *className) 
// can be used to handle multi version reading and writing.
{
	int classVersion;

	[super read:typedStream];

	classVersion = NXTypedStreamClassVersion(typedStream, CLASS_NAME);
	
	switch (classVersion)  {
		case 0:		// First version.
			NXReadType(typedStream, "c", &isUnique);
			if (isUnique)  {
				NXReadType(typedStream, "%", &uStr);
				str = NULL;
			}  else  {
				NXReadType(typedStream, "*",  &str);
				uStr = NULL;
			}
			NXReadType(typedStream, "i", &len);
			NXReadType(typedStream, "c", &shouldFree);
			break;
		default:
			NXLogError("[%s read:] class version %d cannot read "
						"instances archived with version %d", 
						CLASS_NAME, CLASS_VERSION, classVersion);
			[self setStringValueNoCopy:NULL shouldFree:YES];
			break;
	}
	return self;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-= NXTransport  protocol =-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

- encodeRemotelyFor:(NXConnection *)connection 
			freeAfterEncoding:(BOOL *)flagp isBycopy:(BOOL)isBycopy
// This method is called when the string is vended over a distributed object
// connection.  MOString is capable of copying itself wholly over the
// connection, so if byCopy is requested we honor it.
{
	if (isBycopy) return self;
	
	return [super encodeRemotelyFor:connection 
				freeAfterEncoding:flagp isBycopy:isBycopy];
}

- encodeUsing:(id <NXEncoding>)portal
// Encode the string for transmission across a distributed object connection.
{
//	if ([super respondsTo:@selector(encodeUsing:)])  {
//		[super encodeUsing:portal];
//	}
	[portal encodeData:&isUnique ofType:"c"];
	if (isUnique)  {
		[portal encodeData:&uStr ofType:"%"];
	}  else  {
		[portal encodeData:&str ofType:"*"];
	}
	[portal encodeData:&len ofType:"i"];
	[portal encodeData:&shouldFree ofType:"c"];
	return self;
}

- decodeUsing:(id <NXDecoding>)portal
// Decode the data sent over the distributed object connection into 
// the receiving MOString.
{
//	if ([super respondsTo:@selector(decodeUsing:)])  {
//		[super decodeUsing:portal];
//	}  else  {
		[self init];
//	}
	
	[portal decodeData:&isUnique ofType:"c"];
	if (isUnique)  {
		[portal decodeData:&uStr ofType:"%"];
		str=NULL;
	}  else  {
		[portal decodeData:&str ofType:"*"];
		uStr=NULL;
	}
	[portal decodeData:&len ofType:"i"];
	[portal decodeData:&shouldFree ofType:"c"];
	return self;
	
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=- Hashing and isEqual -=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

- (unsigned int)hash
// Use the NextStep string hashing functiuon NXStrHash()  instead of 
// the pointer hashing function. 
{
	return NXStrHash(NULL, (isUnique?uStr:str));
}

- (BOOL)isEqual:anObj
// MOStrings are equal if they are identical or if their contents are
// equal by NXStrIsEqual() (which I assume is constructed to be
// compatible with NXStrHash() since they are described together in the
// NextStep docs.
{
	if (anObj == self)  return YES;
	if ([anObj isKindOfClassNamed:CLASS_NAME])  {
		if (NXStrIsEqual(NULL, (isUnique?uStr:str), [anObj stringValue]))  {
			return YES;
		}
	}
	return NO;
}

// ******************** Private Supporting Functions ********************
char *MOBuildStringFromFormatV(const char *formatStr, va_list param_list)
// This function takes a format string and a variable argument list.
// It returns a pointer to a newly allocated chunk of memory containing 
// the results of sprintf'ing the format string and va_list into the 
// new memory.
{
	NXStream *stream;
	long l;
	char *buf;
	
	stream = NXOpenMemory(NULL, 0, NX_READWRITE);
	NXVPrintf(stream, formatStr, param_list);
	NXFlush(stream);
	NXSeek(stream, 0, NX_FROMEND);
	l = NXTell(stream);
	NXSeek(stream, 0, NX_FROMSTART);
	NX_MALLOC(buf, char, l+1);
	NXRead(stream, buf, l);
	buf[l]='\0';
	NXCloseMemory(stream, NX_FREEBUFFER);
	
	return buf;
}

@end
