//
// Copyright (C) 1991 Texas Instruments Incorporated.
//
// Permission is granted to any individual or institution to use, copy, modify,
// and distribute this software, provided that this complete copyright and
// permission notice is maintained, intact, in all copies and supporting
// documentation.
//
// Texas Instruments Incorporated provides this software "as is" without
// express or implied warranty.
//
// Created: MBN 06/27/89 -- Initial design and implementation
// Updated: DKM 07/07/89 -- To work around Xenix 31 char limit:
//                          Shortened is_less_than        to is_lt
//                                    is_greater_than     to is_gt
//                                    is_less_or_equal    to is_le
//                                    is_greater_or_equal to is_ge
//                          Removed is_equal_or_less and is_greater_or_less
// Updated: MBN 09/07/89 -- Added conditional exception handling
// Updated: MBN 09/26/89 -- Fixed bug to set size when growth size ratio given
// Updated: LGO 10/11/89 -- Removed operator>>
// Updated: MBN 10/24/89 -- Fixed backwards comparison in is_lt and is_gt
// Updated: LGO 10/28/89 -- Removed is_lt, is_gt, is_le, is_ge, is_equal
//                          and is_not_equal (use char* functions instead)
// Updated: LGO 01/03/90 -- Avoid use of strncat
// Updated: LGO 01/03/90 -- Re-write trim, right_trim, left_trim, upcase,
//                          downcase, capitalize, insert, replace, yank,
//                          sub_string, and strncpy to reduce heap allocation
// Updated: LGO 01/04/90 -- Delay memory allocation (see Empty_String_g)
// Updated: MJF 03/12/90 -- Added group names to RAISE
//
// This file contains  member and  friend function implementation code  for the
// String  class defined in the String.h   header file.   Where appropriate and
// possible,  interfaces to,  and us  of,   existing  system functions has been
// incorporated. An overview of the structure of the String class, along with a
// synopsis of each member  and friend function, can be  found  in the String.h
// header file.
//

#ifndef STRINGH					// If class not been defined
#include <cool/String.h>			// Include class header file
#endif

#if defined(DOS)
extern "C" {
#include <ctype.h>		// Include character processing macros
}
#else
#include <ctype.h>		// Include character processing macros
#endif

char* Empty_String_g = "";		// Used to allocate empty Strings
					// this allows operator char* to work

// update_memory -- Private method to perform common memory check/adjustment
// Input:           String reference, pointer to initial String value
// Output:          None

void update_memory (String& s) {
  long size = s.size;
  if (s.growth_ratio != 0.0) {		// If a growth ratio is set
    size = long(size * (1.0 + s.growth_ratio)); // Get new size
    if (size < s.length)		// But if this is still not big enough
      size = s.length;			// grow to big enough size
  }
  else if ((s.length - size) < s.alloc_size_s) // If small growth factor
    size += s.alloc_size_s;		// Increment by allocation size
  else					// Else adding lots of characters
    size = s.length;			// so grow to bigger size
  s.resize(size);			// Grow the String
}


// String() -- Simple constructor for a string object. Memory is allocated
//             for the char* pointer of size MEM_BLK_SIZE
// Input:      None
// Output:     String reference


String::String () {	        
  if (this->alloc_size_s == 0)		// If allocation size is uninitialized
    this->alloc_size_s = MEM_BLK_SZ;	// Set memory block allocation size
  this->growth_ratio = 0.0;		// Initialize growth ratio
  this->length = 0;			// Zero characters in string
  this->size = 0;			// Size of allocated block 
  this->str = Empty_String_g;		// No memory allocated
}


// String(char) -- Constructor to initialize a string object with a char
// Input:          character
// Output:         String reference to new object

String::String (char c) {	
  if (this->alloc_size_s == 0)		// If allocation size is uninitialized
    this->alloc_size_s = MEM_BLK_SZ;	// Set memory block allocation size
  this->growth_ratio = 0.0;		// Initialize growth ratio
  this->length = 1;			// Length of character is 1
  this->size = this->length+1;		// Size of allocated block
  this->str = new char[size];		// Allocate memory for string
  *this->str = c;			// Assign character
  *(this->str+1) = END_OF_STRING;	// END_OF_STRING terminator for string
}


// String(char*) -- Constructor to initialize a string object with a char*
// Input:           char* pointer
// Output:          String reference to new object

String::String (CONST char* c) { 
  if (this->alloc_size_s == 0)		 // If allocation size is uninitialized
    this->alloc_size_s = MEM_BLK_SZ;	 // Set memory block allocation size
  this->growth_ratio = 0.0;		 // Initialize growth ratio
  this->length = strlen (c);		 // Determine length of string
  this->size = this->length+1;		 // Size of allocated block
  this->str = new char[size];		 // Allocate memory for string
  strcpy ((char *)this->str, c);	 // Copy characters 
}


// String(String&) -- Constructor to initialize a string object to
//                    that of another String.
// Input:             String reference
// Output:            String reference to new object

String::String (CONST String& s) { 
  if (this->alloc_size_s == 0)		// If allocation size is uninitialized
    this->alloc_size_s = MEM_BLK_SZ;	// Set memory block allocation size
  this->growth_ratio = s.growth_ratio;	// Initialize growth ratio
  this->length = s.length;		// length of character string
  if (s.length > 0) {
    this->str = new char[s.size];	// Allocate memory for string
    strcpy ((char *)this->str, s.str);	// Copy characters
    this->size = s.size;		// Size of allocated block
  }
  else {
    this->str = Empty_String_g;		// Don't allocate when empty
    this->size = 0;
  }
}


// String(char*, size) -- Constructor to initialize a string object with
//                        a char* and provide an initial size value
// Input:                 char* pointer and size value
// Output:                String reference to new object

String::String (CONST char* c, long sz) {
  if (this->alloc_size_s == 0)		// If allocation size is uninitialized
    this->alloc_size_s = MEM_BLK_SZ;	// Set memory block allocation size
  this->length = strlen (c);		// Determine length of character string
  if (this->length > sz)		// If initial string is larger
    this->size = length+1;		// Size of allocated block
  else
    this->size = sz+1;			// Size of allocated block
  this->str = new char[size];		// Allocate memory for string 
  strcpy ((char *)this->str, c);	// Copy characters
}


// String(String&, long) -- Constructor to initialize a string
//                          object with a String and provide
//                          an initial block size value
// Input:                   String reference and size value
// Output:                  String reference to new object

String::String (CONST String& s, long sz) { 
  if (this->alloc_size_s == 0)		// If allocation size is uninitialized
    this->alloc_size_s = MEM_BLK_SZ;	// Set memory block allocation size
  this->length = s.length;		// Determine length of character string
  if (this->length > sz)		// If initial string is larger
    this->size = length+1;		// Size of allocated block
  else
    this->size = sz+1;			// Size of allocated block
  this->str = new char[size];		// Allocate memory for string
  strcpy ((char *)this->str, s.str);	// Copy characters over
}


// ~String() -- String object destructor
// Input:       String object
// Output:      None

String::~String () {			// String object destructor
  if (this->str != Empty_String_g)
    delete this->str;			// Deallocate string memory
}


// clear -- Flush the character string from the string object by setting
//          the char* pointer to NULL and the length to zero.
// Input:   this* 
// Output:  None

void String::clear() {
  *(this->str) = NULL;		        // Clear this String by using NULL
  this->length = 0;			// Set new string length
}


// strcpy -- String copy of a single character to a String object
// Input:    String reference and a character
// Output:   String object containing a character string

String& strcpy (String& s, char c) {
  if (s.size < 2) {			// Allocate memory block
    if (s.str != Empty_String_g)	// When memory allocated
      delete s.str;			// Delete block
    s.size = s.alloc_size_s;		// Size of allocated block
    s.str = new char[s.size];		// Allocate memory for string
  }
  s.length = 1;				// Character string length
  *(s.str) = c;				// Assign character
  *(s.str+1) = END_OF_STRING;		// END_OF_STRING terminator for string
  return s;				// Return String reference
}  


// strcpy -- String copy of a char* to a String object
// Input:    String reference and a char* 
// Output:   String object containing a character string

String& strcpy (String& s, const char *c) {
  s.length = strlen (c);		// Determine length of character string
  if (s.size <= s.length)		// If not enough memory allocated
    update_memory (s);			// Adjust/update memory if necessary
  strcpy (s.str, c);			// Else just copy new string value
  return s;				// Return String reference
}


// strcpy -- String copy of one String object to another. 
// Input:    Two String references
// Output:   Updated String object 

String& strcpy (String& s1, const String& s2) {
  s1.length = s2.length;		// Determine length of character string
  if (s1.size > s2.length &&		// If string fits in allocated memory
      s1.str != Empty_String_g)		// and s1 has memory allocated
    strcpy (s1.str, s2.str);		// Copy string value
  else {
    if (s1.str != Empty_String_g)	// When memory allocated
      delete s1.str;			// Delete block
    if (s2.length == 0) {
      s1.str = Empty_String_g;		// Don't allocate when empty
      s1.size = 0;
    }
    else {
      s1.size = s2.length+1;		// Set new block size
      s1.str = new char[s1.size];	// Allocate new block of memory
      strcpy (s1.str, s2.str);		// Copy string value
    }
  }
  return s1;				// Return string reference
}

// operator+ -- String concatenation of a character: x = x + 'A';
// Input:       Character
// Output:      String object concatenated with character

String String::operator+ (char c) {
  this->length += 1;			// Determine length of new string
  if (this->size <= this->length)	// If not enough allocated memory
    update_memory (*this);		// Adjust/update memory if necessary
  this->str[length-1] = c;		// Append new character
  this->str[length] = END_OF_STRING;	// END_OF_STRING terminator
  return *this;				// Return String
}

// strcat -- Concatenate a single character to a String object
// Input:    String reference and a character
// Output:   String object concatenated with character 

String& strcat (String& s, char c) {
  s.length += 1;			// Determine length of new string
  if (s.size <= s.length)		// If not enough allocated memory
    update_memory (s);			// Adjust/update memory if necessary
  s.str[s.length-1] = c;		// Append new character
  s.str[s.length] = END_OF_STRING;	// END_OF_STRING terminator
  return s;				// Return String
}


// operator+ -- String concatenation of a character string: x = x + "ABCDEFG";
// Input:       Character string
// Output:      String object concatenated with character string

String String::operator+ (const char* c) {
  long old_len = this->length;		// Save length for efficiency hack
  this->length += strlen (c);		// Determine length of new string
  if (this->size <= this->length)	// If not enough allocated memory
    update_memory (*this);		// Adjust/update memory if necessary
  strcpy ((char *) &this->str[old_len], c); // Copy remaining characters 
  return *this;				// Return String
}

// strcat -- Concatenate a String and a char*
// Input:    String reference and a char*
// Output    String object concatentated with character string

String& strcat (String& s, const char* c) {
  long start_length = s.length;         // Find initial string length
  s.length += strlen (c);		// Determine length of new string
  if (s.size <= s.length)		// If not enough allocated memory
    update_memory (s);			// Adjust/update memory if necessary
  strcpy (s.str+start_length, c);	// Concatenate characters   
  return s;				// Return String
}


// operator+ -- String concatenation of another string: x = x + y;
// Input:       String reference
// Output:      String object concatenated with String contents

String String::operator+ (const String& s) {
  long old_len = this->length;		// Save length for efficiency hack
  this->length += s.length;		// Determine length of new string
  if (this->size <= this->length)	// If not enough allocated memory
    update_memory (*this);		// Adjust/update memory if necessary
  strcpy (&this->str[old_len], (char *)s.str); // Copy remaining characters 
  return *this;				// Return String
}


// strncat -- Concatentate a String with "n" characters from char*
// Input:     String reference, char*, number of characters
// Output:    String object concatenedate with "n" characters

String& strncat (String& s, const char* c, int n) {
#if ERROR_CHECKING
  if (n < 0)				// If invalid length specified
    RAISE (Error, SYM(String), SYM(Negative_Length),
	   "String::strncat(): Negative length %d", n);
#endif
  long start_length = s.length;		// Find initial string length
  s.length += n;			// Determine length of new string
  if (s.size <= s.length)		// If not enough allocated memory
    update_memory (s);			// Adjust/update memory if necessary
  strncpy (s.str+start_length, c, n);	// Concatenate characters 
  return s;				// Return String
}


// strcat -- Concatenate two String objects
// Input:    Two String references
// Output:   String object concatenated with String object

String& strcat (String& s1, const String& s2) {
  long start_length = s1.length;
  s1.length += s2.length;		// Determine length of new string
  if (s1.size <= s1.length)		// If not enough allocated memory
    update_memory (s1);			// Adjust/update memory if necessary
  strcpy (s1.str+start_length, s2.str); // Concat characters   
  return s1;				// Return String
}


// strncat -- Concatentate a String with "n" characters from a String
// Input:     Two String references and number of characters
// Output:    String object concatenedate with "n" characters

String& strncat (String& s1, const String& s2, int n) {
#if ERROR_CHECKING
  if (n < 0)				// If invalid length specified
    RAISE (Error, SYM(String), SYM(Negative_Length),
	   "String::strncat(): Negative length %d", n);
#endif
  long start_length = s1.length;        // Find initial string length
  s1.length += n;			// Determine length of new string
  if (s1.size <= s1.length)		// If not enough allocated memory
    update_memory (s1);			// Adjust/update memory if necessary
  strncpy (s1.str+start_length, s2.str, n); // Concat chars 
  return s1;				// Return String
}


// reverse -- Reverse the order of the characters in String object
// Input:     String object
// Output:    String object with character order reverse

void String::reverse () {
  char c;					// Temporary variable
  for (long i = 0, j = this->length-1;		// Counting from front and rear
       i < this->length / 2; i++, j--) {	// until we reach the middle
    c = this->str[i];				// Save front character
    this->str[i] = this->str[j];		// Switch with rear character
    this->str[j] = c;				// Copy new rear character
  }
}


// resize -- Adjust the memory size of a string to accomodate some size
// Input:    String object
// Output:   None

void String::resize (long sz) {
#if ERROR_CHECKING
  if (sz < 0)				// If invalid size
    RAISE (Error, SYM(String), SYM(Negative_Size),
	   "String::resize(): Negative resize %d", sz);
#endif
  char* temp = this->str;		// Save pointer to existing string
  this->size = sz+1;			// Save size of allocated memory
  this->str = new char[sz+1];		// Allocate memory for desired size
  strcpy ((char *)this->str, temp);	// Copy original string back
  if (temp != Empty_String_g)
    delete temp;			// Deallocate old memory
}


// operator<< -- Overload output operator for String objects
// Input:        String reference
// Output:       Formatted output and stream descriptor

ostream& operator<< (ostream& os, const String& s) {
  return os << s.str;				// Output char* and newline
}

// strtol -- Returns as a long integer the value represented by the
//           string pointer to by s, scanning upto the first character
//           that is inconsistent with the base. Leading white space is
//           ignored.
// Input:    Reference to String object
//           Radix of number
// Output:   Long integer representing value contained in the String

long strtol (const String& s, char** ptr, int radix) {
  return (strtol ((char *)s.str, ptr, radix)); 
}	


// atol -- Equivalent to: strtol (str, (char**)END_OF_STRING, 10)
// Input:  Reference to String object
// Output: Long integer representing value contained in the String

long atol (const String& s) {		// Convert string to long
  return (strtol ((char *)s.str, (char **) END_OF_STRING, int(10)));
}


// atoi -- Equivalent to: (int)strtol (str, (char**)END_OF_STRING, 10)
// Input:  Reference to String object
// Output: Integer representing value contained in the String

int atoi (const String& s) {		// Convert string to int
  return int(strtol ((char *)s.str, (char **) END_OF_STRING, int(10)));	
}


// strtod -- Returns as a double-precision floating-point number the
//           value represented by a String object. Characters are
//           scanned upto the first unrecognized character.
// Input:    Reference to String object
// Output:   Double representing value contained in String

double strtod (const String& s, char** ptr) {	// Convert string to double
  return (strtod ((char *)s.str, ptr));
}


// trim -- Removes any occurrence of the character(s) in "c" from "s"
// Input:  Simople_String reference, character string
// Output: Modified String "s" object

String& trim (String& sr, const char* rem) {
  char* s = sr.str;
  char* result = sr.str;
  long len = 0;
  register char c;
  while ((c=*s++) != END_OF_STRING) {
    register const char* r = rem;
    register char t;
    while ((t=*r++) != END_OF_STRING && t != c); // Scan for match
    if (t == END_OF_STRING)			 // If no match found
      *result++ = c, len++;
  }
  *result = END_OF_STRING;			// NULL terminate string
  sr.length = len;
  return sr;					// Return string
}


// left_trim -- Removes any occurrence of the character(s) in "c" from
//              "s" that appear as a prefix to the string. The first
//              non-matching character encountered terminates the remove
//              operation and the rest of the string is copied intact.
// Input:       String reference, character string
// Output:      Modified String "s" object

String& left_trim (String& sr, const char* rem) { // Trim prefix from String
  char* result = sr.str;
  char* s = sr.str;
  long len = 0;
  register char c;
  for (; (c=*s) != END_OF_STRING; s++) {
    register const char* r = rem;
    register char t;
    while ((t=*r++) != END_OF_STRING && t != c); // Scan for match
    if (t == END_OF_STRING)			 // If no match found
      break;
  }
  if (s != result)				  // when characters trimed
    while ((*result++ = *s++) != END_OF_STRING) len++; // shift string down
  sr.length = len;
  return sr;				// Return string reference
}


// right_trim -- Removes any occurrence of the character(s) in "c" from
//               "s" that appear as a suffix to the string. The first
//               non-matching character encountered terminates the remove
//               operation and the rest of the string is copied intact.
// Input:        String reference, character string
// Output:       Modified String "s" object

String& right_trim (String& sr, const char* rem) {
  char* str = sr.str;
  char* s = str + strlen(str) - 1;		// last character of string
  for (; s >= str; s--) {
    register const char* r = rem;
    register char t;
    register char c = *s;
    while ((t=*r++) != END_OF_STRING && t != c); // Scan for match
    if (t == END_OF_STRING)			 // If no match found
      break;
  }
  *(s+1) = END_OF_STRING;
  sr.length = s - str;
  return sr;				// Return String reference
}


// upcase -- Convert all alphabetical characters to uppercase
// Input:    String reference
// Output:   Updated string

String& upcase (String& s) {	// Convert entire string to upper case
  c_upcase(s.str);
  return s;			// Return reference to modified string
}


// downcase -- Convert all alphabetical characters to lowercase
// Input:      String reference
// Output:     Updated string

String& downcase (String& s) {	// Convert entire string to lower case
  c_downcase(s.str);
  return s;			// Return reference to modified string
}

// capitalize -- Capitalize all words in a String. A word is define as
//               a sequence of characters separated by non alphanumerics
// Input:        String reference
// Output:       Updated string

String& capitalize (String& s) {	// Capitalize each word in string
  c_capitalize(s.str);
  return s;			// Return reference to modified string
}


// insert -- Insert a char* at the position specified by start.
// Input  -- A char* to be inserted and a long index.          
// Output -- A Boolean, true if insertion took place, false if error.

Boolean String::insert (const char* ins, long start) {
  if (start<0 || start>this->length) return(FALSE);  // Boundary check
  long len=strlen(ins);                 // length of char*
  this->length += len;  	        // Determine length of new string
  if (this->size <= this->length)	// If not enough allocated memory
    update_memory (*this);		// Adjust/update memory if necessary
  register char* ptr = this->str;
  register char* st = ptr + start;
  register char* end = ptr + this->length;
  for (ptr = end - len; ptr >= st;)		// Make space for ins
    *end-- = *ptr--;
  for (end = st+len; st < end; *st++ = *ins++);	// Insert ins into that
  return(TRUE);				// Insert worked
}


// remove -- Removes everything between the start and end indexes of string
//           The character at start is removed and all characters up to but 
//           not including the character at end.
// Input  -- A start and end index into the string.
// Output -- A Boolean, true if remove worked, false if error.

Boolean String::remove (long start, long end) {
  if (start<0 || start > this->length || end<0	// Boundary check
      || end > this->length || end<=start)
    return(FALSE);				// Out of bounds, failure
  this->length -= (end-start);			// New (shorter) length
  for(; this->str[start]=this->str[end]; start++) end++; // Remove everything
  return(TRUE);                                 // Remove worked
}


// replace -- Removes everything between start and end as in remove(), and
//            inserts the char* argument into the place of what was removed.
// Input   -- A start and end for remove and a char*, c, to insert.
// Output  -- A Boolean, true if replace worked, false if error.

Boolean String::replace (const char* c, long start, long end) {
  if (start<0 || start > this->length || end<0	// Boundary check
      || end > this->length || end<=start)	// out of bounds, failure
    return(FALSE); 
  long len=strlen(c);                   // length of c
  long delta = len - end + start;	// find overall change in length
  this->length += delta;		// set new length
  if (this->size <= this->length)	// If not enough allocated memory
    update_memory (*this);		// Adjust/update memory if necessary
  long ind;
  if (delta > 0) {			          // If replacement is bigger
    for (ind=this->length; start+len<=ind; ind--) // than chacters replaced
      this->str[ind] = this->str[ind-delta];
  }
  else if (delta < 0) {  		// Replacement is shorter
    char* st = this->str + start + len;
    char* ptr = this->str + end;
    char* end = this->str + this->length;
    while (st<=end) *st++ = *ptr++;
  }
  { char* ptr = this->str+start;
    char ch;
    while ((ch = *c++) != END_OF_STRING) // replace characters
      *ptr++ = ch;
  }
  return(TRUE);
}


// yank   -- Removes everything between start and end, copies it into a 
//           a string and returns that string.
// Input  -- A reference to a String.  A start and end for the remove.
// Output -- none.  Sets the String.

void String::yank (String& s, long start, long end) {
#if ERROR_CHECKING
  if (start<0 || start > this->length || end<0	// Boundary check
      || end > this->length || end<=start)
    RAISE (Error, SYM(String), SYM(Invalid_Start_End),
	   "String::sub_yank(): Start %d and/or end %d index invalid",
	   start, end);
#endif
  long len = end - start;
  strncpy(s, this->str+start, len);	// Copy stuff to yank into s
  this->length -= len;			// set new length
  for(; this->str[start]=this->str[end]; start++) end++;  // Do the remove
}


// sub_string -- Returns a new string initialized to what is between the
//               start and end indexes.
// Input      -- A reference to a String.  Two longs, a start and an end
// Output     -- none.  Sets the String.

void String::sub_string (String& s, long start, long end) {
#if ERROR_CHECKING
  if (start<0 || start > this->length || end<0	// Boundary check
      || end > this->length || end<=start)
    RAISE (Error, SYM(String), SYM(Invalid_Start_End),
	   "String::sub_string(): Start %d and/or end %d index invalid",
	   start, end);
#endif
  strncpy(s, this->str+start, end - start);	// Copy substring into s
}


// strncpy -- Returns s, with the first length characters of source copied
//            into it.  The old value of s is lost.
// Input   -- A reference to a String s, a char* source, and a long length.
// Output  -- The modified String s.

String& strncpy(String& s, const char* source, long length) {
#if ERROR_CHECKING
  if (length<0)					// Boundary check
    RAISE (Error, SYM(String), SYM(Negative_Length),
	   "String::strncpy(): Negative length %d", length);
#endif
  s.length = length;			// Set new string length
  if (s.size <= length)	{		// If not enough allocated memory
    if (s.str) *s.str = END_OF_STRING;	// Don't copy old string
    update_memory (s);			// Adjust/update memory if necessary
  }
  char* p = s.str;
  while (length-- > 0)			// Copy source into string
    *p++ = *source++;
  *p = END_OF_STRING;			// set the end byte
  return s;
}


// bracket_error -- Raise exception for operator[]
// Input:           Index value
// Output:          None

void String::bracket_error (long n) CONST {
  RAISE (Error, SYM(String), SYM(Out_Of_Range),
	 "String::operator[](): Index %d out of range", n);
}


// growth_error -- Raise exception for set_alloc_size()
// Input:          Growth value
// Output:         None

void String::growth_error (int i) CONST {
  RAISE (Error, SYM(String), SYM(Negative_Size),
	 "String::set_alloc_size(): Negative growth size %d", i);
}

// ratio_error -- Raise exception for set_growth_ratio()
// Input:         Growth ration
// Output:        None

void String::ratio_error (float ratio) CONST {
  RAISE (Error, SYM(String), SYM(Negative_Ratio),
	 "String::set_growth_ratio(): Negative growth ratio %f", ratio);
}
