/******************************************************************************
 JString.cc

	This class was not designed to be a base class!  If you need to override it,
	be sure to make the destructor virtual.

	JStrings can contain NULL's, if they are constructed with the
	JString(const JCharacter* str, const JSize length) constructor.
	You must remember not to call Clib functions on the const JCharacter* in
	this case.

	Note that operator== is case sensitive, as one would expect.  To avoid the
	UNIX method of sorting capitalized names separately in front of lowercase
	names, operator< and operator> are not case sensitive.  One should therefore
	not mix == with < and > when comparing strings.

	Since strstream doesn't provide the control we need when converting a number
	to a string, we use the NumConvert and StrUtil modules.  We include them at
	the end of the file so they are completely hidden and JString is self-contained.

	BASE CLASS = none

	Copyright © 1994-98 by John Lindal. All rights reserved.

 ******************************************************************************/

#include <JString.h>
#include <jStreamUtil.h>
#include <jMath.h>
#include <JMinMax.h>
#include <stdlib.h>
#include <ctype.h>
#include <jErrno.h>
#include <jAssert.h>

const JSize kDefaultBlockSize = 10;

// private routines

static void		double2str(double doubleVal, int afterDec, int expMin, char *returnStr);
//static JBoolean	str2double(char *numStr, double *doubleVal);

/******************************************************************************
 Constructor

 ******************************************************************************/

JString::JString()
	:
	itsBlockSize( kDefaultBlockSize )
{
	itsStringLength = 0;
	itsAllocLength  = itsBlockSize;

	itsString = new JCharacter [ itsAllocLength+1 ];
	assert( itsString != NULL );
	itsString [ 0 ] = '\0';
}

JString::JString
	(
	const JCharacter* str
	)
	:
	itsBlockSize( kDefaultBlockSize )
{
	itsAllocLength = 0;
	itsString      = NULL;		// makes delete [] safe inside CopyToPrivateString
	CopyToPrivateString(str);
}

JString::JString
	(
	const JCharacter*	str,
	const JSize			length
	)
	:
	itsBlockSize( kDefaultBlockSize )
{
	itsAllocLength = 0;
	itsString      = NULL;		// makes delete [] safe inside CopyToPrivateString
	CopyToPrivateString(str, length);
}

JString::JString
	(
	const JCharacter*	str,
	const JIndexRange&	range
	)
	:
	itsBlockSize( kDefaultBlockSize )
{
	itsAllocLength = 0;
	itsString      = NULL;		// makes delete [] safe inside CopyToPrivateString
	CopyToPrivateString(str + range.first-1, range.GetLength());
}

JString::JString
	(
	const JFloat			number,
	const int				precision,
	const ExponentDisplay	expDisplay,
	const int				exponent
	)
	:
	itsBlockSize( kDefaultBlockSize )
{
	assert( precision >= -1 );

	itsAllocLength = 50;

	itsString = new JCharacter [ itsAllocLength+1 ];
	assert( itsString != NULL );
	double2str(number, precision,
			   (expDisplay == kUseGivenExponent ? exponent : expDisplay),
			   itsString);

	itsStringLength = strlen(itsString);
}

/******************************************************************************
 Copy constructor

	Even though JString(const JCharacter*) does the same job, we need to declare
	this copy constructor to avoid having the compiler declare it incorrectly.

 ******************************************************************************/

JString::JString
	(
	const JString& source
	)
	:
	itsBlockSize( source.itsBlockSize )
{
	itsAllocLength = 0;
	itsString      = NULL;		// makes delete [] safe inside CopyToPrivateString

	CopyToPrivateString(source.itsString, source.itsStringLength);
}

/******************************************************************************
 Destructor

 ******************************************************************************/

JString::~JString()
{
	delete [] itsString;
}

/******************************************************************************
 CopyToPrivateString (private)

	Copy the given string into our private string.

	*** This function assumes that str != itsString

 ******************************************************************************/

void
JString::CopyToPrivateString
	(
	const JCharacter*	str,
	const JSize			length
	)
{
	assert( str != itsString );

	if (itsAllocLength < length || itsAllocLength == 0)
		{
		itsAllocLength = length + itsBlockSize;

		// We allocate the new memory first.
		// If new fails, we still have the old string data.

		JCharacter* newString = new JCharacter [ itsAllocLength + 1 ];
		assert( newString != NULL );

		// now it's safe to throw out the old data

		delete [] itsString;
		itsString = newString;
		}

	// copy the characters to the new string

	memcpy(itsString, str, length);
	itsString[ length ] = '\0';

	itsStringLength = length;
}

/******************************************************************************
 InsertSubstring

	Insert the given string into ourselves starting at the specified index.

 ******************************************************************************/

void
JString::InsertSubstring
	(
	const JCharacter*	stringToInsert,
	const JSize			insertLength,
	const JIndex		insertionIndex
	)
{
	assert( 0 < insertionIndex && insertionIndex <= itsStringLength + 1 );

	// if the string to insert is empty, we don't need to do anything

	if (insertLength == 0)
		{
		return;
		}

	// If we don't have space for the result, we have to reallocate.

	JCharacter* insertionPtr = NULL;
	const JUnsignedOffset insertionOffset = insertionIndex - 1;

	if (itsAllocLength < itsStringLength + insertLength)
		{
		itsAllocLength = itsStringLength + insertLength + itsBlockSize;

		// allocate space for the combined string

		JCharacter* newString = new JCharacter [ itsAllocLength + 1 ];
		assert( newString != NULL );

		insertionPtr = newString + insertionOffset;

		// copy our characters in front of the insertion point

		memcpy(newString, itsString, insertionOffset);

		// copy our characters after the insertion point, -including the termination-

		memcpy(insertionPtr + insertLength, itsString + insertionOffset,
			   itsStringLength - insertionOffset + 1);

		// throw out our original string and save the new one

		delete [] itsString;
		itsString = newString;
		}

	// Otherwise, just shift characters to make space for the result.

	else
		{
		insertionPtr = itsString + insertionOffset;

		// shift characters after the insertion point, -including the termination-

		memmove(insertionPtr + insertLength, insertionPtr,
				itsStringLength - insertionOffset + 1);
		}

	// copy the characters from the string to insert

	memcpy(insertionPtr, stringToInsert, insertLength);

	// update our string length

	itsStringLength += insertLength;
}

/******************************************************************************
 AllocateCString

	This allocates a new pointer, which the caller is responsible
	for deleting via "delete []".

 ******************************************************************************/

JCharacter*
JString::AllocateCString()
	const
{
	JCharacter* str = new JCharacter [ itsStringLength + 1 ];
	assert( str != NULL );

	memcpy(str, itsString, itsStringLength+1);

	return str;
}

/******************************************************************************
 GetCharacter

 ******************************************************************************/

JCharacter
JString::GetCharacter
	(
	const JIndex index
	)
	const
{
	assert( IndexValid(index) );

	return PrivateGetCharacter(index);
}

/******************************************************************************
 SetCharacter

 ******************************************************************************/

void
JString::SetCharacter
	(
	const JIndex		index,
	const JCharacter	c
	)
{
	assert( IndexValid(index) );

	itsString [ index - 1 ] = c;
}

/******************************************************************************
 Clear

 ******************************************************************************/

void
JString::Clear()
{
	// there is nothing to do if we are already empty

	if (itsStringLength == 0)
		{
		return;
		}

	// If we are using too much memory, reallocate.

	if (itsAllocLength > itsBlockSize)
		{
		itsAllocLength = itsBlockSize;

		// throw out the old data

		delete [] itsString;

		// Having just released a block of memory at least as large as the
		// one we are requesting, the system must really be screwed if this
		// call to new doesn't work.

		itsString = new JCharacter [ itsAllocLength + 1 ];
		assert( itsString != NULL );
		}

	// clear the string

	itsString [ 0 ] = '\0';
	itsStringLength = 0;
}

/******************************************************************************
 TrimWhitespace

	Trim leading and trailing whitespace from ourselves.

 ******************************************************************************/

void
JString::TrimWhitespace()
{
	// there is nothing to do if we are already empty

	if (itsStringLength == 0)
		{
		return;
		}

	// if there is no blank space to trim, we can stop now

	if (!isspace(PrivateGetCharacter(1)) &&
		!isspace(PrivateGetCharacter(itsStringLength)))
		{
		return;
		}

	// find last non-blank character

	JIndex lastCharIndex = itsStringLength;
	while (lastCharIndex > 0 && isspace(PrivateGetCharacter(lastCharIndex)))
		{
		lastCharIndex--;
		}

	// if we are only blank space, we can just clear ourselves

	if (lastCharIndex == 0)
		{
		Clear();
		return;
		}

	// find first non-blank character (it does exist since lastCharIndex > 0)

	JIndex firstCharIndex = 1;
	while (isspace(PrivateGetCharacter(firstCharIndex)))
		{
		firstCharIndex++;
		}

	// If we are using too much memory, reallocate.

	const JSize newLength = lastCharIndex - firstCharIndex + 1;

	if (itsAllocLength > newLength + itsBlockSize)
		{
		itsAllocLength = newLength + itsBlockSize;

		// allocate space for the new string + termination

		JCharacter* newString = new JCharacter[ itsAllocLength+1 ];
		assert( newString != NULL );

		// copy the non-blank characters to the new string

		memcpy(newString, GetCharacterPtr(firstCharIndex), newLength);

		// throw out our original string and save the new one

		delete [] itsString;
		itsString = newString;
		}

	// Otherwise, just shift the characters.

	else
		{
		memmove(itsString, GetCharacterPtr(firstCharIndex), newLength);
		}

	// terminate

	itsString [ newLength ] = '\0';

	itsStringLength = newLength;
}

/******************************************************************************
 ToLower

	Convert all characters to lower case.

 ******************************************************************************/

void
JString::ToLower()
{
	for (JIndex i=0; i<itsStringLength; i++)
		{
		itsString[i] = tolower(itsString[i]);
		}
}

/******************************************************************************
 ToUpper

	Convert all characters to upper case.

 ******************************************************************************/

void
JString::ToUpper()
{
	for (JIndex i=0; i<itsStringLength; i++)
		{
		itsString[i] = toupper(itsString[i]);
		}
}

/******************************************************************************
 LocateSubstring

	Return the index corresponding to the start of the first occurrence of
	the given string in our string.

 ******************************************************************************/

JBoolean
JString::LocateSubstring
	(
	const JCharacter*	str,
	const JSize			strLength,
	const JBoolean		caseSensitive,
	JIndex*				startIndex
	)
	const
{
	*startIndex = 1;
	return LocateNextSubstring(str, strLength, caseSensitive, startIndex);
}

/******************************************************************************
 LocateNextSubstring

	Return the index corresponding to the start of the next occurrence of
	the given string in our string, starting at *startIndex.

	In:  *startIndex is first character to consider
	Out: If function returns kTrue, *startIndex is location of next occurrence.
	     Otherwise, *startIndex is beyond end of string.

 ******************************************************************************/

JBoolean
JString::LocateNextSubstring
	(
	const JCharacter*	str,
	const JSize			strLength,
	const JBoolean		caseSensitive,
	JIndex*				startIndex
	)
	const
{
	if (IsEmpty())
		{
		*startIndex = 1;
		return kFalse;
		}
	else if (*startIndex > itsStringLength)
		{
		return kFalse;
		}

	assert( *startIndex > 0 );

	// if the given string is longer than we are, we can't contain it

	if (itsStringLength - *startIndex + 1 < strLength)
		{
		*startIndex = itsStringLength+1;
		return kFalse;
		}

	// search forward for a match

	for (JIndex i=*startIndex; i<=itsStringLength - strLength + 1; i++)
		{
		if (JCompareMaxN(GetCharacterPtr(i), itsStringLength-i+1, str, strLength,
						 strLength, caseSensitive))
			{
			*startIndex = i;
			return kTrue;
			}
		}

	// if we fall through, there was no match

	*startIndex = itsStringLength+1;
	return kFalse;
}

/******************************************************************************
 LocateLastSubstring

	Return the index corresponding to the start of the last occurrence of
	the given string in our string.

 ******************************************************************************/

JBoolean
JString::LocateLastSubstring
	(
	const JCharacter*	str,
	const JSize			strLength,
	const JBoolean		caseSensitive,
	JIndex*				startIndex
	)
	const
{
	*startIndex = itsStringLength;
	return LocatePrevSubstring(str, strLength, caseSensitive, startIndex);
}

/******************************************************************************
 LocatePrevSubstring

	Return the index corresponding to the start of the previous occurrence of
	the given string in our string, starting at *startIndex.

	In:  *startIndex is first character to consider
	Out: If function returns kTrue, *startIndex is location of prev occurrence.
	     Otherwise, *startIndex is zero.

 ******************************************************************************/

JBoolean
JString::LocatePrevSubstring
	(
	const JCharacter*	str,
	const JSize			strLength,
	const JBoolean		caseSensitive,
	JIndex*				startIndex
	)
	const
{
	if (IsEmpty() || *startIndex == 0)
		{
		*startIndex = 0;
		return kFalse;
		}

	assert( *startIndex <= itsStringLength );

	// if the given string runs past our end, back up *startIndex

	const JSize spaceAtEnd = itsStringLength - *startIndex + 1;
	if (spaceAtEnd < strLength && itsStringLength >= strLength)
		{
		*startIndex = itsStringLength - strLength + 1;
		}
	else if (spaceAtEnd < strLength)
		{
		*startIndex = 0;
		return kFalse;
		}

	// search backward for a match

	for (JIndex i=*startIndex; i>=1; i--)
		{
		if (JCompareMaxN(GetCharacterPtr(i), itsStringLength-i+1, str, strLength,
						 strLength, caseSensitive))
			{
			*startIndex = i;
			return kTrue;
			}
		}

	// if we fall through, there was no match

	*startIndex = 0;
	return kFalse;
}

/******************************************************************************
 BeginsWith

 ******************************************************************************/

JBoolean
JString::BeginsWith
	(
	const JCharacter*	str,
	const JBoolean		caseSensitive
	)
	const
{
	if (str[0] == '\0')
		{
		return kTrue;
		}
	else
		{
		JIndex i = 1;
		return LocatePrevSubstring(str, caseSensitive, &i);
		}
}

JBoolean
JString::BeginsWith
	(
	const JCharacter*	str,
	const JSize			length,
	const JBoolean		caseSensitive
	)
	const
{
	if (str[0] == '\0')
		{
		return kTrue;
		}
	else
		{
		JIndex i = 1;
		return LocatePrevSubstring(str, length, caseSensitive, &i);
		}
}

/******************************************************************************
 EndsWith

 ******************************************************************************/

JBoolean
JString::EndsWith
	(
	const JCharacter*	str,
	const JSize			length,
	const JBoolean		caseSensitive
	)
	const
{
	if (length == 0)
		{
		return kTrue;
		}
	else if (itsStringLength > length)
		{
		JIndex i = itsStringLength - length + 1;
		return LocateNextSubstring(str, length, caseSensitive, &i);
		}
	else
		{
		return kFalse;
		}
}

/******************************************************************************
 GetSubstring

	Return a string containing the specified substring of our string.

	Not inline because it uses assert.

 ******************************************************************************/

JString
JString::GetSubstring
	(
	const JIndex firstCharIndex,
	const JIndex lastCharIndex
	)
	const
{
	assert( IndexValid(firstCharIndex) );
	assert( IndexValid(lastCharIndex) );
	assert( firstCharIndex <= lastCharIndex );

	return JString(GetCharacterPtr(firstCharIndex), lastCharIndex - firstCharIndex + 1);
}

JString
JString::GetSubstring
	(
	const JIndexRange& range
	)
	const
{
	assert( !range.IsNothing() );

	if (range.IsEmpty())
		{
		return JString();
		}
	else
		{
		return GetSubstring(range.first, range.last);
		}
}

/******************************************************************************
 Extract

	Fills substringList with the text of each range in rangeList.  Empty
	ranges are converted to NULL.

 *****************************************************************************/

void
JString::Extract
	(
	const JArray<JIndexRange>&	rangeList,
	JPtrArray<JString>*			substringList
	)
	const
{
	assert( substringList != NULL );

	substringList->DeleteAll();

	const JSize count = rangeList.GetElementCount();
	for (JIndex i=1; i<=count; i++)
		{
		const JIndexRange r = rangeList.GetElement(i);
		JString* s          = NULL;
		if (!r.IsEmpty())
			{
			s = new JString(itsString, r);
			assert( s != NULL );
			}
		substringList->Append(s);
		}
}

/******************************************************************************
 ReplaceSubstring

	Replace the specified substring with the given string.

 ******************************************************************************/

void
JString::ReplaceSubstring
	(
	const JIndex		firstCharIndex,
	const JIndex		lastCharIndex,
	const JCharacter*	str,
	const JSize			strLength
	)
{
	assert( IndexValid(firstCharIndex) );
	assert( IndexValid(lastCharIndex) );
	assert( firstCharIndex <= lastCharIndex );

	const JSize replaceLength = lastCharIndex - firstCharIndex + 1;
	const JSize newLength     = itsStringLength - replaceLength + strLength;

	const JSize len1 = firstCharIndex - 1;
	const JSize len2 = strLength;
	const JSize len3 = itsStringLength - lastCharIndex;

	// If we don't have space, or would use too much space, reallocate.

	if (itsAllocLength < newLength || itsAllocLength > newLength + itsBlockSize)
		{
		itsAllocLength = newLength + itsBlockSize;

		// allocate space for the result

		JCharacter* newString = new JCharacter[ itsAllocLength+1 ];
		assert( newString != NULL );

		// place the characters in front and behind

		memcpy(newString, itsString, len1);
		memcpy(newString + len1 + len2, itsString + lastCharIndex, len3);

		// throw out the original string and save the new one

		delete [] itsString;
		itsString = newString;
		}

	// Otherwise, shift characters to make space.

	else
		{
		memmove(itsString + len1 + len2, itsString + lastCharIndex, len3);
		}

	// insert the new characters

	memcpy(itsString + len1, str, len2);

	// terminate

	itsString[ newLength ] = '\0';

	itsStringLength = newLength;
}

/******************************************************************************
 MatchCase

	Adjusts the case of our text to match the case of the specified range
	in the source.  Returns kTrue if any changes were made.

	If the source range and our text have the same length, we match the
	case of each character individually.

	Otherwise:

	If both first characters are letters, the first letter of replace is
	adjusted to the same case as the first character of source range.

	If the rest of the source range contains at least one alphabetic
	character and all its alphabetic characters have the same case, all the
	alphabetic characters in our text are coerced to that case.

 *****************************************************************************/

JBoolean
JString::MatchCase
	(
	const JCharacter*	source,
	const JIndexRange&	range
	)
{
	assert( source != NULL );

	if (IsEmpty() || range.IsEmpty())
		{
		return kFalse;
		}

	JBoolean changed      = kFalse;
	const JSize sourceLen = range.GetLength();

	// if not the same length, match all but first character

	if (sourceLen > 1 && itsStringLength > 1 &&
		sourceLen != itsStringLength)
		{
		JBoolean hasUpper = kFalse;
		JBoolean hasLower = kFalse;
		for (JIndex i = range.first; i<range.last; i++)
			{
			if (islower(source[i]))
				{
				hasLower = kTrue;
				}
			else if (isupper(source[i]))
				{
				hasUpper = kTrue;
				}

			if (hasLower && hasUpper)
				{
				break;
				}
			}

		// first character is fixed separately below

		if (hasLower && !hasUpper)
			{
			ToLower();
			changed = kTrue;
			}
		else if (hasUpper && !hasLower)
			{
			ToUpper();
			changed = kTrue;
			}
		}

	// if not same length, match first character
	// else, match all characters

	const JIndex endIndex = (sourceLen == itsStringLength ? sourceLen : 1);
	for (JIndex i=0; i<endIndex; i++)
		{
		if (islower(source [ range.first-1+i ]) && isupper(itsString[i]))
			{
			itsString[i] = tolower(itsString[i]);
			changed = kTrue;
			}
		else if (isupper(source [ range.first-1+i ]) && islower(itsString[i]))
			{
			itsString[i] = toupper(itsString[i]);
			changed = kTrue;
			}
		}

	return changed;
}

/******************************************************************************
 ConvertToFloat

	Convert ourselves to a floating point value.  Returns kTrue if successful.

	This function accepts hex values like 0x2A.

 ******************************************************************************/

JBoolean
JString::ConvertToFloat
	(
	JFloat* value
	)
	const
{
	if (IsHex())
		{
		JUInt v;
		const JBoolean ok = ConvertToUInt(&v);
		*value = v;
		return ok;
		}

	jclear_errno();
	char* endPtr;
	*value = strtod(itsString, &endPtr);
	if (jerrno_is_clear() && CompleteConversion(itsString, itsStringLength, endPtr))
		{
		return kTrue;
		}
	else
		{
		*value = 0.0;
		return kFalse;
		}
}

/******************************************************************************
 ConvertToInteger

	Convert ourselves to a signed integer.  Returns kTrue if successful.

	base must be between 2 and 36 inclusive.
	If the string begins with 0x or 0X, base is forced to 16.

 ******************************************************************************/

JBoolean
JString::ConvertToInteger
	(
	JInteger*	value,
	const JSize	origBase
	)
	const
{
	JSize base = origBase;
	if (IsHex())
		{
		base = 0;	// let strtol notice "0x"
		}

	jclear_errno();
	char* endPtr;
	*value = strtol(itsString, &endPtr, base);
	if (jerrno_is_clear() && CompleteConversion(itsString, itsStringLength, endPtr))
		{
		return kTrue;
		}
	else
		{
		*value = 0;
		return kFalse;
		}
}

/******************************************************************************
 ConvertToUInt

	Convert ourselves to an unsigned integer.  Returns kTrue if successful.

	base must be between 2 and 36 inclusive.
	If the string begins with 0x or 0X, base is forced to 16.

 ******************************************************************************/

JBoolean
JString::ConvertToUInt
	(
	JUInt*		value,
	const JSize	origBase
	)
	const
{
	JSize base = origBase;
	if (IsHex())
		{
		base = 0;	// let strtoul notice "0x"
		}
	else
		{
		// We do not let strtoul() wrap negative numbers.

		JIndex i=0;
		while (i < itsStringLength && isspace(itsString[i]))
			{
			i++;
			}
		if (i < itsStringLength && itsString[i] == '-')
			{
			*value = 0;
			return kFalse;
			}
		}

	jclear_errno();
	char* endPtr;
	*value = strtoul(itsString, &endPtr, base);
	if (jerrno_is_clear() && CompleteConversion(itsString, itsStringLength, endPtr))
		{
		return kTrue;
		}
	else
		{
		*value = 0;
		return kFalse;
		}
}

/******************************************************************************
 IsHex

	Returns kTrue if string begins with whitespace+"0x".

 ******************************************************************************/

JBoolean
JString::IsHex()
	const
{
	JIndex i=0;
	while (i < itsStringLength && isspace(itsString[i]))
		{
		i++;
		}

	return JConvertToBoolean(
		i < itsStringLength-2 && itsString[i] == '0' &&
		(itsString[i+1] == 'x' || itsString[i+1] == 'X'));
}

/******************************************************************************
 CompleteConversion (private)

	Returns kTrue if convEndPtr is beyond the end of the string or the
	remainder is only whitespace.

 ******************************************************************************/

JBoolean
JString::CompleteConversion
	(
	const JCharacter*	startPtr,
	const JSize			length,
	const JCharacter*	convEndPtr
	)
	const
{
	if (convEndPtr == startPtr)		// avoid behavior guaranteed by strto*()
		{
		return kFalse;
		}

	const JCharacter* endPtr = startPtr + length;
	while (convEndPtr < endPtr)
		{
		if (!isspace(*convEndPtr))
			{
			return kFalse;
			}
		convEndPtr++;
		}
	return kTrue;
}

/******************************************************************************
 Read

	Read the specified number of characters from the stream.
	This replaces the current contents of the string.

 ******************************************************************************/

void
JString::Read
	(
	istream&	input,
	const JSize	count
	)
{
	if (itsAllocLength < count || itsAllocLength == 0)
		{
		itsAllocLength = count + itsBlockSize;

		// We allocate the new memory first.
		// If new fails, we still have the old string data.

		JCharacter* newString = new JCharacter [ itsAllocLength + 1 ];
		assert( newString != NULL );

		// now it's safe to throw out the old data

		delete [] itsString;
		itsString = newString;
		}

	input.read(itsString, count);
	itsStringLength = input.gcount();
	itsString[ itsStringLength ] = '\0';
}

/******************************************************************************
 Print

	Display the text in such a way that the user can understand it.
	(i.e. don't display it in quotes, and don't use the internal \")

 ******************************************************************************/

void
JString::Print
	(
	ostream& output
	)
	const
{
	output.write(itsString, itsStringLength);
}

/******************************************************************************
 Global functions for JString class

 ******************************************************************************/

/******************************************************************************
 JStringCompare

	Returns the same as strcmp(): + if s1>s2, 0 if s1==s2, - if s1<s2

 ******************************************************************************/

inline int
JDiffChars
	(
	const JCharacter	c1,
	const JCharacter	c2,
	const JBoolean		caseSensitive
	)
{
	return (caseSensitive ? (c1 - c2) : (tolower(c1) - tolower(c2)));
}

int
JStringCompare
	(
	const JCharacter*	s1,
	const JCharacter*	s2,
	const JBoolean		caseSensitive
	)
{
	while (*s1 != '\0' && *s2 != '\0')
		{
		int c = JDiffChars(*s1, *s2, caseSensitive);
		if (c != 0)
			{
			return c;
			}
		s1++;
		s2++;
		}
	return JDiffChars(*s1, *s2, caseSensitive);
}

int
JStringCompare
	(
	const JCharacter*	s1,
	const JSize			length1,
	const JCharacter*	s2,
	const JSize			length2,
	const JBoolean		caseSensitive
	)
{
	const JCharacter* end1 = s1 + length1;
	const JCharacter* end2 = s2 + length2;

	while (s1 < end1 && s2 < end2)
		{
		int c = JDiffChars(*s1, *s2, caseSensitive);
		if (c != 0)
			{
			return c;
			}
		s1++;
		s2++;
		}

	if (s1 == end1 && s2 == end2)
		{
		return 0;
		}
	else if (s1 == end1)
		{
		return -1;
		}
	else
		{
		assert( s2 == end2 );
		return +1;
		}
}

/******************************************************************************
 JCompareMaxN

	Returns kTrue if the first N characters of the two strings are equal.
	(replaces strncmp())

 ******************************************************************************/

JBoolean
JCompareMaxN
	(
	const JCharacter*	s1,
	const JCharacter*	s2,
	const JSize			N,
	const JBoolean		caseSensitive
	)
{
	for (JIndex i=0; i<N; i++)
		{
		const JCharacter c1 = s1[i];
		const JCharacter c2 = s2[i];
		if ((caseSensitive && c1 != c2) ||
			(!caseSensitive && tolower(c1) != tolower(c2)))
			{
			return kFalse;
			}
		else if (c1 == '\0' && c2 == '\0')	// same length, shorter than N
			{
			return kTrue;
			}
		}

	return kTrue;
}

JBoolean
JCompareMaxN
	(
	const JCharacter*	s1,
	const JSize			length1,
	const JCharacter*	s2,
	const JSize			length2,
	const JSize			N,
	const JBoolean		caseSensitive
	)
{
	for (JIndex i=0; i<length1 && i<length2 && i<N; i++)
		{
		const JCharacter c1 = s1[i];
		const JCharacter c2 = s2[i];
		if ((caseSensitive && c1 != c2) ||
			(!caseSensitive && tolower(c1) != tolower(c2)))
			{
			return kFalse;
			}
		}

	return kTrue;
}

/******************************************************************************
 JCalcMatchLength

	Calculates the number of characters that match from the beginning
	of the given strings.

	JCalcMatchLength("abc", "abd")         -> 2
	JCalcMatchLength("abc", "xyz")         -> 0
	JCalcMatchLength("abc", "aBd", kFalse) -> 2

 *****************************************************************************/

JSize
JCalcMatchLength
	(
	const JCharacter*	s1,
	const JCharacter*	s2,
	const JBoolean		caseSensitive
	)
{
	JSize i = 0;
	while (((caseSensitive && s1[i] == s2[i]) ||
			(!caseSensitive && tolower(s1[i]) == tolower(s2[i]))) &&
		   s1[i] != '\0')		// kTrue => s2[i] != '\0'
		{
		i++;
		}
	return i;
}

/******************************************************************************
 JCopyMaxN

	A version of strncpy() without the horrible bug.  In addition, it returns
	kTrue if the entire string was copied or kFalse if it wasn't, since the
	strncpy() return value is totally useless.  The name maxBytes should remind
	you that there must also be room for a null terminator.

 *****************************************************************************/

JBoolean
JCopyMaxN
	(
	const JCharacter*	source,
	const JIndex		maxBytes,
	JCharacter*			destination
	)
{
	JIndex i;
	for (i=0; i<maxBytes; i++)
		{
		destination[i] = source[i];
		if (destination[i] == '\0')
			{
			return kTrue;
			}
		}

	destination[maxBytes-1] = '\0';
	return kFalse;
}

/******************************************************************************
 Stream operators

	The string data is delimited by double quotes:  "this is a string".

	To include double quotes in a string, use \"
	To include a backslash in a string, use \\

	An exception is made if the streams are cin or cout.
	For input, characters are simply read until 'return' is pressed.
	For output, Print() is used.

 ******************************************************************************/

istream&
operator>>
	(
	istream&	input,
	JString&	aString
	)
{
	if (&input == &cin)
		{
		// Read characters until return is pressed.
		// Following convention, the return must be left in the stream.

		aString = JReadLine(cin);
		cin.putback('\n');
		aString.TrimWhitespace();
		return input;
		}

	// skip white space

	input >> ws;

	// the string must start with a double quote

	JCharacter c;
	input.get(c);
	if (c != '"')
		{
		input.putback(c);
		JSetState(input, ios::failbit);
		return input;
		}

	// read until we hit a non-backslashed double quote

	aString.Clear();

	const JSize bufSize = 100;
	JCharacter buf[ bufSize ];

	JIndex i = 0;
	while (1)
		{
		input.get(c);
		if (input.fail())
			{
			break;
			}
		else if (input.eof())
			{
			JSetState(input, ios::failbit);
			break;
			}
		else if (c == '\\')
			{
			input.get(c);
			if (input.fail())
				{
				break;
				}
			}
		else if (c == '"')
			{
			break;
			}

		buf[i] = c;
		i++;
		if (i == bufSize)
			{
			aString.Append(buf, bufSize);
			i=0;
			}
		}

	aString.Append(buf, i);

	// allow chaining

	return input;
}

ostream&
operator<<
	(
	ostream&		output,
	const JString&	aString
	)
{
	if (&output == &cout)
		{
		aString.Print(cout);
		return output;
		}
	else if (&output == &cerr)
		{
		aString.Print(cerr);
		return output;
		}

	// write the starting delimiter

	output << '"';

	// write the string data, substituting \" in place of "

	const JSize length = aString.GetLength();

	for (JIndex i=1; i<=length; i++)
		{
		const JCharacter c = aString.PrivateGetCharacter(i);

		if (c == '"')
			{
			output << '\\' << '"';
			}
		else if (c == '\\')
			{
			output << '\\' << '\\';
			}
		else
			{
			output << c;
			}
		}

	// write the ending delimiter

	output << '"';

	// allow chaining

	return output;
}

/*-----------------------------------------------------------------------------
 Routines required for number<->string conversion

 -----------------------------------------------------------------------------*/

// private routines

static void  Round(int start, int B, int D[], JBoolean A[], int *exp);
static void  Shift(int* start, int D[], JBoolean A[], int* exp);

static void  JAssignString(char s1[], char *s2);
static void  JInsertSubstring(char s1[], short pos1, char *s2, short pos2, short pos3);
static void  JShiftSubstring(char s[], short pos1, short pos2, short shift);
static short JLocateSubstring(char *s1, char *s2);
//static void  JTrimSpaces(char* s);

/*-----------------------------------------------------------------------------
 NumConvert.c (encapsulated by JString class)

	Assembled routines for dealing with string<->number conversion.

	Copyright © 1992 John Lindal. All rights reserved.

 -----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
 double2str (from FNUM in FORTRAN)

	Converts doubleVal to a string stored at returnStr.

	afterDec = # of digits after decimal point, doubleVal is rounded appropriately
	           -1 => as many as needed, -2 => truncate, 0 => round up

	exp = desired exponent, decimal point is shifted appropriately
	      kStandardExponent => use exponent if magnitude >=7
	      kForceExponent    => force use of exponent

 -----------------------------------------------------------------------------*/

void double2str(double doubleVal,int afterDec,int expMin,char returnStr[])
{
int			D[8],i,j,exp,start,decPt,aD1;
double		logVal;
JBoolean	A[8],neg;

	neg=kFalse;
	if (doubleVal<0.0) {				/* can't take log of x<0 */
		neg=kTrue;
		doubleVal=-doubleVal;
		}

	if (doubleVal!=0.0) {				/* find exponent */
	  logVal=log10(doubleVal*1.00001);	/* bump it up slightly if exact multiple of 10 */
	  exp=JLFloor(logVal)+1;			/* floor always rounds down, not towards zero */
	  }
	else exp=0;							/* can't take log of zero */

	for (i=0;i<8;i++) {
		D[i]=0; A[i]=kTrue;
		}

	for (i=exp-1;i>=exp-8;i--) {		/* digit values */
		D[exp-i-1]=JLFloor(doubleVal/pow(10.0,i))-10*JLFloor(doubleVal/pow(10.0,i+1));
		}

	for (i=1;i<=6;i++) {
		if (D[i]==-1) {
			for (j=i+1;j<=7;j++) if (D[j]<9) break;
			if (j<8) D[j]++;
			}
		}
	Round(7,7,D,A,&exp);
	if (D[7]>4) Round(6,7,D,A,&exp);
	D[7]=0;

	j=0;
	for (i=5;i>=0;i--) {
		if (D[i]==9) j=j+1;
		else break;
		}
	if (j>=2) {			/* round up if lots of 9's on end */
		Round(5,7,D,A,&exp);
		for (i=5;i<=7;i++) D[i]=0;
		}

	exp--;
	if ((exp>=-6 && exp<=6) && expMin==JString::kStandardExponent) expMin=0;
	if ((exp<=-7 || exp>=7) && expMin==JString::kStandardExponent) expMin=exp;
	if (expMin==JString::kForceExponent) expMin=exp;
	if (doubleVal==0.0) expMin=0;

	for (i=7;i>=0;i--) {		/* strip trailing zeros */
		if (D[i]==0) A[i]=kFalse;
		else break;
		}

	aD1=afterDec;
	if (afterDec!=-1) {							/* adjust digits to fit specs */
		if (afterDec==-2) afterDec=0;			/* truncate */
		start=1+afterDec+(exp-expMin);			/* adjust for spec'd # digits after dp */
		if (start<1) Shift(&start,D,A,&exp);
		if (start>7) start=7;
		if (aD1!=-2 && D[start]>4) Round(start-1,0,D,A,&exp);	/* round up */

		for (i=start;i<8;i++) {
			D[i]=0; A[i]=kFalse;			/* clear digits beyond where you rounded */
			}
		}

	for (i=0;i<8;i++) {							/* create string of digits */
		if (A[i]) returnStr[i]='0'+D[i];
		else returnStr[i]=0;
		}
	if (strcmp(returnStr,"0")==0) neg=kFalse;	/* "-0" is funny */

	if (strlen(returnStr)==0) {					/* number has ended up as zero */
		exp=0; expMin=0;
		}

	if (expMin>exp) {
		if (expMin-exp>1)	/* add leading zero's */
			for (j=0;j<=expMin-exp-2;j++) JInsertSubstring(returnStr,0,"0",0,1);
		JInsertSubstring(returnStr,0,"0.",0,2);							/* add dp */
		if (strcmp(returnStr,"0.")==0) JAssignString(returnStr,"0");	/* 0. is funny */
		}
	else if (expMin<=exp) {
		i=strlen(returnStr)-1;
		if (exp-expMin>i)	/* add trailing zero's */
			for (j=1;j<=exp-expMin-i;j++) JInsertSubstring(returnStr,-1,"0",0,1);
		else if (exp-expMin<i) JInsertSubstring(returnStr,exp-expMin+1,".",0,1);	/* dp */
		}
	exp=expMin;

	i=JLocateSubstring(returnStr,".")+1;	/* find position of dp */
	if (i==0) decPt=0;
	else decPt=strlen(returnStr)-i;
	if (afterDec>decPt) {					/* add dp and trailing zeros */
		if (decPt==0) JInsertSubstring(returnStr,-1,".",0,1);
		for (i=decPt+1;i<=afterDec;i++) JInsertSubstring(returnStr,-1,"0",0,1);
		}

	if (exp!=0)								/* append exponent */
		{
		if (exp>0)
			{
			JInsertSubstring(returnStr,-1,"e+",0,3);
			}
		else	/* exp<0 */
			{
			JInsertSubstring(returnStr,-1,"e-",0,3);
			exp=-exp;
			}

		double2str(exp,0,0, returnStr + strlen(returnStr));
		}

	if (neg) JInsertSubstring(returnStr,0,"-",0,1);	/* include negative sign */
}

/*-----------------------------------------------------------------------------
 Round (from ROUND in FORTRAN)

	Rounds D starting from start.  If B<7, sets A[] false for trailing zeros.

 -----------------------------------------------------------------------------*/

void Round(int start,int B,int D[],JBoolean A[],int* exp)
{
int i;

	if (start<7) D[start]++;	/* round up digit # start */

	if (start>0) {
		for (i=start;i>=1;i--) {
			if (D[i]>9) {D[i]=0; D[i-1]++;};	/* move a 10 up to next digit */
			}
		}

	if (D[0]>9) {			/* make space for extra digit in front */
		for (i=7;i>=2;i--) D[i]=D[i-1];
		D[0]=1; D[1]=0; (*exp)++;
		}

	if (B==7) return;

	for (i=7;i>=B;i--) {	/* strip trailing zeros */
		if (D[i]==0) A[i]=kFalse;
		else break;
		}
}

/*-----------------------------------------------------------------------------
 Shift (from SHIFT in FORTRAN)

	Shifts digits in D array so that start can be increased to 1.
	This takes care of the situation when doubleVal=0.002 and afterDec=0.

 -----------------------------------------------------------------------------*/

void Shift(int* start,int D[],JBoolean A[],int* exp)
{
int i,shift;

	shift=1-*start;				/* how far to shift each digit */

	if (shift<=7) {				/* shift digits */
		for (i=7;i>=shift;i--) {
			D[i]=D[i-shift]; A[i]=A[i-shift];
			}
		for (i=0;i<=shift-1;i++) {		/* set preceding digits to zero */
			D[i]=0; A[i]=kTrue;
			}
		}
	else {						/* shift too large, just clear all digits */
		for (i=0;i<8;i++) {
			D[i]=0; A[i]=kFalse;
			}
		}

	(*start)=1;			/* shifting digits fixed start */
	(*exp)+=shift;		/* exponent has changed */
}

/*-----------------------------------------------------------------------------
 str2double (from FSTR in FORTRAN)

	Converts the C string numStr to doubleVal.  Returns kFalse if an error occurs.

	We use recursion to handle the exponent.

 -----------------------------------------------------------------------------*/
/*
JBoolean str2double(char *numStr1,double *doubleVal)
{
int			i,numlen,decPt,decEnd,expMrk,digit;
char		numStr[256],expNumStr[100];
double		exp0,exp1;
JBoolean	neg,valid;

	*doubleVal=0.0; JAssignString(numStr,numStr1);

	JTrimSpaces(numStr);

	numlen=strlen(numStr);
	if (numlen==0) return kFalse;	// empty string

	decPt=-1; decEnd=-1; expMrk=-1; neg=kFalse;
	if (numStr[0]=='+' || numStr[0]=='-') {			// trim sign
		if (numStr[0]=='-') neg=kTrue;
		JShiftSubstring(numStr,1,-1,-1); numlen--;
		}

	for (i=1;i<=numlen;i++)
		{
		const char c = numStr[i-1];
		if (c == '.')									// found dp
			decPt=i;
//		else if (c == ' ') {							// end of digits
//			if (decPt==-1 && expMrk==-1) decPt=i;		// forced dp
//			if (decPt>-1 && decEnd==-1) decEnd=i;		// end of decimal string
//			}
		else if (c == 'E' || c == 'e' ||
				 c == 'D' || c == 'd')					// found exponent
			{
			if (i==1 || i==numlen) return kFalse;		// no digits, no exponent string
			expMrk=i;
			if (decEnd==-1)				// forced end of decimal string
				decEnd=i;
			if (decPt==-1) {			// forced dp
				decPt=i; decEnd=i;
				}
			break;						// let recursion handle the exponent
			}
		else if (!isdigit(c))
			return kFalse;
		}
	if (decPt==-1) decPt=numlen+1;		// forced dp
	if (decEnd==-1) decEnd=numlen+1;	// forced end of decimal string
	if (expMrk==-1) expMrk=numlen+1;	// forced exponent (zero)

	if (decPt==1 && decPt>=numlen) return kFalse;	// no digits at all

	if (decPt!=1) {						// decode digits preceding dp
		for (i=1;i<=decPt-1;i++) {
			digit=numStr[i-1]-'0';
			if (digit<0 || digit>9) return kFalse;		// not a number
			*doubleVal+=(digit*pow(10.0,decPt-i-1));	// add in weighted values
			}
		}

	if (decPt<numlen) {					// decode digits after dp
		for (i=decPt+1;i<=decEnd-1;i++) {
			digit=numStr[i-1]-'0';
			if (digit<0 || digit>9) return kFalse;	// not a number
			*doubleVal+=(digit/pow(10.0,i-decPt));	// add in weighted values
			}
		}

	if (expMrk<numlen && *doubleVal > 0.0) {	// decode exponent
		exp0=log10(*doubleVal);
		expNumStr[0] = '\0';
		JInsertSubstring(expNumStr,0,numStr,expMrk,-1);
		valid=str2double(expNumStr,&exp1);			// recursive
		if (exp0+exp1>FLT_MAX_10_EXP) valid=kFalse;	// exponent too big
		if (!valid) return kFalse;
		i=JLFloor(exp1);
		*doubleVal=(*doubleVal)*pow(10.0,i);		// adjust number
		}

	if (neg) *doubleVal=-(*doubleVal);	// adjust sign

	return kTrue;
}
*/
/*-----------------------------------------------------------------------------
 JString.StrUtil.c (a stripped version required by NumConvert.c)

	Assembled routines for dealing with string conversion.
	These routines only require vanilla C.

	Copyright © 1992 John Lindal. All rights reserved.

 -----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
 JAssignString

	Clears the s1 array and dumps in s2 (including termination).
	Assumes there are enough elements in s1 to hold s2.

 -----------------------------------------------------------------------------*/

void JAssignString(char s1[],char *s2)
{
short i,len2;

	len2=strlen(s2);
	for (i=0;i<=len2;i++) s1[i]=*(s2+i);
}

/*-----------------------------------------------------------------------------
 JInsertSubstring (augments strcat in string.h)

	Inserts s2 into s1 at the specified postion in s1.
	pos# start at zero.  pos1 = -1 => append.  pos3 = -1 => to end of s2.
	Assumes there is enough space in s1 to hold s1+s2.

 -----------------------------------------------------------------------------*/

void JInsertSubstring(char s1[],short pos1,char *s2,short pos2,short pos3)
{
short i,len1,len2;

	len1=strlen(s1); len2=strlen(s2);
	if (pos1==-1 || pos1>len1) pos1=len1;
	if (pos2<0) pos2=0;
	if (pos3==-1 || pos3>len2) pos3=len2;
	JShiftSubstring(s1,pos1,-1,pos3-pos2);
	for (i=pos2;i<pos3;i++) s1[pos1+i-pos2]=*(s2+i);
}

/*-----------------------------------------------------------------------------
 JShiftSubstring

	Shifts characters in s from pos1 to pos2 by shift positions.
	Overwrites old characters.  Gives up if you try to shift past left end of string.
	pos# start at zero.  pos2 = -1 => end.  shift > 0 => shift to right.
	Assumes there is enough space in s to hold shifted results.

 -----------------------------------------------------------------------------*/

void JShiftSubstring(char s[],short pos1,short pos2,short shift)
{
short i,len;

	if (shift==0) return;

	len=strlen(s);
	if (pos1<0) pos1=0;
	if (pos2==-1 || pos2>len) pos2=len;

	if (pos2<pos1 || pos1+shift<0) return;

	/* pos2=len => termination shifts too */

	if (shift<0)
		for (i=pos1;i<=pos2;i++) s[i+shift]=s[i];
	if (shift>0) {
		for (i=pos2;i>=pos1;i--) s[i+shift]=s[i];
		if (pos2+shift+1>len && pos2<len) s[pos2+shift+1]=0;	/* Terminate string */
		}
}

/*-----------------------------------------------------------------------------
 JLocateSubstring (equivalent to INDEX()-1 in FORTRAN)

	Returns the offset to the 1st occurrence of s2 inside s1.
	-1 => Not found.

 -----------------------------------------------------------------------------*/

short JLocateSubstring(char *s1,char *s2)
{
short i,len1,len2;

	len1=strlen(s1); len2=strlen(s2);

	for (i=0;i<=len1-len2;i++)
		if (strncmp(s1+i,s2,len2)==0) return i;

	return -1;
}

/*-----------------------------------------------------------------------------
 JTrimSpaces

	Trims off leading and trailing spaces from s.

 -----------------------------------------------------------------------------*/
/*
void JTrimSpaces(char* s)
{
short i,j,len;

	len = strlen(s);
	while (len>0 && s[len-1]==' ') {	// shift termination
		s[len-1]=0; len--;
		}

	j=0;
	while (j<len && s[j]==' ') j++;
	for (i=0;i<len-j;i++) s[i]=s[i+j];

	s[i]=0;			// terminate
}
*/
