//  File: bstring6.C
//
//  Implementation of BString class.
//  Version 3: made comparison functions const.
//  Version 4: added != operator
//  Version 5: char * alternate constructor becomes const char *
//             throws exceptions
//  Version 6: new and delete operators

#include <iostream.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include "errors.h"
#include "bstring6.h"

#define INITLEN 5

//===========================================================================
// Local function prototypes
//

static char *reallocate(char *, int) ;


//===========================================================================
// Local function implementations
//

// Quick & dirty function to change the size of memory block.
// Returns pointer to block of n bytes. Memory that s originally
// points to is freed. Not for public consumption.
//
static char *reallocate(char *s, int n) {
   char *newstr ;

   newstr = new char[n] ;
   if (newstr == NULL) throw (MemoryError()) ;
   if (s == NULL) return newstr ;

   strncpy(newstr, s, n) ;
   delete [] s ;
   return newstr ;
}


//===========================================================================
// BString member functions
//


// Default constructor
//
BString::BString() {

   bufsize = 0 ;    // initially str is NULL not ""
   str = NULL ;
   len = 0 ;
}


// Constructor with initial buffer size
//
BString::BString(int n) {

   if (n <= 0) n = INITLEN ;
   bufsize = n ;
   str = new char[bufsize] ;
   if (str == NULL) throw (MemoryError()) ;
   str[0] = '\0' ;
   len = 0 ;
}


// Initialize with string and buffer size
//
BString::BString(const char *s, int n /* = 0 */ ){

   if (s == NULL) {
      bufsize = n ;
      if (bufsize > 0) {
         str = new char[bufsize] ;
         if (str == NULL) throw (MemoryError()) ;
         str[0] = '\0' ;
      } else {
         str = NULL ;
      }
      len = 0 ;
      return ;
   }

   len = strlen(s) ;
   if (len < n) {    // always have enough memory for s
      bufsize = n ;
   } else {
      bufsize = len + 1 ;
   }
   str = new char[bufsize] ;
   if (str == NULL) throw (MemoryError()) ;
   strncpy(str, s, bufsize) ;
}


// Copy Constructor
//
BString::BString(const BString& bs) {

   len = bs.len ;
   bufsize = bs.bufsize ;

   if (bufsize > 0) { // might be 0
      str = new char[bufsize] ;
      if (str == NULL) throw (MemoryError()) ;
   }

   if (bs.str != NULL) {
      strncpy(str, bs.str, bufsize) ;
      str[len] = '\0' ;   // extra careful
   } else {
      str = NULL ;
   }
}


// Destructor
//
BString::~BString() {

   delete [] str ;
}



// Add char(s) to the end of this string
//

void BString::append(char c) {

   if (len > bufsize - 2) {    // need more space?
      bufsize = ( bufsize >= INITLEN ? 2*bufsize : INITLEN ) ;
      str = reallocate(str, bufsize) ;
   }

   str[len] = c ;
   str[len+1] = '\0' ;
   len++ ;
}


void BString::append(const char *s) {
   int newlen, slen ;

   if (s == NULL) return ;

   slen = strlen(s) ;
   newlen = len + slen ;  // length of combined string

   if (bufsize < newlen + 1) {
      bufsize = newlen + 1 ;
      str = reallocate(str, bufsize) ;
   }

   strncpy(str+len, s, slen+1) ;
   len = newlen ;
}


void BString::append(const BString& bs) {
   int newlen ;

   if (bs.str == NULL) return ;

   newlen = len + bs.len ;  // length of combined string
   if (bufsize < newlen + 1) {
      bufsize = newlen + 1 ;
      str = reallocate(str, bufsize) ;
   }

   strncpy(str+len, bs.str, bs.len+1) ;
   len = newlen ;
   str[len] = '\0' ;   // extra carefull
}


// Remove char(s) at the end of the string
//
void BString::chop(int n /* =1 */) {

    // degnerate cases
    //
    if (n <= 0) return ;
    if (n > len) n = len ;

    len = len - n ;
    str[len] = '\0' ;
    trim() ;
}


// Reduce buffer size if it's a good idea.
//
void BString::trim() {

   // Do nothing if string is short or uses 3/4 of buffer
   //
   if (bufsize == INITLEN || len >= 0.75*bufsize) return ;

   force_trim() ;
}


// Always reduce to just the right buffer size.
//
void BString::force_trim() {

   bufsize = len + 1 ;
   str = reallocate(str, bufsize) ;
}


// Overloaded Operators
//

// assignment
//
BString& BString::operator =(const BString& bs) {

   // Handle degenerate case of self-assignment
   if (this == &bs) return *this ;

   // free old string!
   delete [] str ;

   len = bs.len ;
   bufsize = bs.bufsize ;
   if (bufsize > 0) {
      str = new char[bufsize] ;
      if (str == NULL) throw (MemoryError()) ;
   }

   if (bs.str != NULL) {
      strncpy(str, bs.str, bufsize) ;
      str[len] = '\0' ; // extra careful
   } else {
      str = NULL ;
   }

   return *this ;
}


// + is concatenate
//
BString  BString::operator +(const BString& bs) {
   BString newbs(*this) ;

   newbs.append(bs) ;
   return newbs ;
}


// Allow use of str[i] notation for the ith character.
//
char& BString::operator [](int i) {

   if (i < 0 || i > len) {
      cerr << "String index out of bounds: " << i << endl ;
      exit(1) ;
   }
   return str[i] ;
}


// comparisons
//  use convention that NULL pointer is the smallest string
//

bool BString::operator  <(const BString& bs) const {
   int minlen ;

   if (str == NULL && bs.str == NULL) return false ;
   if (str == NULL) return true ;
   if (bs.str == NULL) return false ;

   minlen = (len <= bs.len) ? len : bs.len ;
   if (strncmp(str, bs.str, minlen+1) < 0) return true ;
   return false ;
}

bool BString::operator <=(const BString& bs) const {
   int minlen ;

   if (str == NULL && bs.str == NULL) return true ;
   if (str == NULL) return true ;
   if (bs.str == NULL) return false ;

   minlen = (len <= bs.len) ? len : bs.len ;
   if (strncmp(str, bs.str, minlen+1) <= 0) return true ;
   return false ;
}

bool BString::operator ==(const BString& bs) const {
   int minlen ;

   if (str == NULL && bs.str == NULL) return true ;
   if (str == NULL) return false ;
   if (bs.str == NULL) return false ;

   minlen = (len <= bs.len) ? len : bs.len ;
   if (strncmp(str, bs.str, minlen+1) == 0) return true ;
   return false ;
}

bool BString::operator >=(const BString& bs) const {
   int minlen ;

   if (str == NULL && bs.str == NULL) return true ;
   if (str == NULL) return false ;
   if (bs.str == NULL) return true ;

   minlen = (len <= bs.len) ? len : bs.len ;
   if (strncmp(str, bs.str, minlen+1) >= 0) return true ;
   return false ;
}

bool BString::operator  >(const BString& bs) const {
   int minlen ;

   if (str == NULL && bs.str == NULL) return false ;
   if (str == NULL) return false ;
   if (bs.str == NULL) return true ;

   minlen = (len <= bs.len) ? len : bs.len ;
   if (strncmp(str, bs.str, minlen+1) > 0) return true ;
   return false ;
}

bool BString::operator !=(const BString& bs) const {
   return ! operator==(bs) ;
}


// Read a line from specified stream and store it in the host object.
//
void BString::GetLine(istream& istrm) {
   int ch ;

   // Remove old data. Zero out buffer.
   //
   delete [] str ;
   len = 0 ;
   str = NULL ;
   bufsize = 0 ;

   // First check for EOF
   //
   ch = istrm.get() ;  // peek ahead to trigger EOF settings
   if (istrm.eof()) {
      return ;
   }
   istrm.putback(ch) ; // continue as if we didn't peek


   // keep reading until done
   //
   while(true) {
      ch = istrm.get() ;
      if (ch == EOF || ch == '\n') break ;
      append(ch) ; // piece of cake!
   }

   trim() ;
}


// Dump vital stats
//
void BString::debug() {
   cout << "*** BString::debug()" << endl ;
   cout << "this="<< this << " str=" << (void *) str << endl ;
   cout << "str=" << str << endl ;
   cout << "len=" << len << endl ;
   cout << "bufsize=" << bufsize << endl ;
   cout << "***" << endl ;
}


// Memory Allocation
//
void *BString::operator new(size_t n) {         // new BString
   void *ptr = malloc(n) ;
   if (ptr == NULL) throw(MemoryError()) ;
   return ptr ;
}

void *BString::operator new[](size_t n) {       // new array of BString
   void *ptr = malloc(n) ;
   if (ptr == NULL) throw(MemoryError()) ;
   return ptr ;
}

void BString::operator delete(void *ptr) {      // free a BString
   free(ptr) ;
}

void BString::operator delete[](void *ptr) {    // free array of BString
   free(ptr) ;
}


//===========================================================================
// Overloaded I/O operators << and >>


istream& operator >>(istream& istrm, BString& bs) {
   bs.GetLine(istrm) ;
   return istrm ;
}


ostream& operator <<(ostream& ostrm, const BString& bs) {
   ostrm << bs.str ;
   return ostrm ;
}

