//-------------------------------------------------------------- strings.cpp -
// Module:       strings.cpp
//
// Version:      $Revision: 8 $
//               $Date: 9/03/98 2:38p $
//
// Description:  String class.
//
//               #define _FASTER_STRING for somewhat faster operation --
//               strings will only have their allocation adjusted when space
//               needs increase.  Otherwise, re-allocation is also performed
//               on size DECREASE requests.
//
//               #define NDEBUG for final code - ASSERTions abound
//----------------------------------------------------------------------------

#include  <ctype.h>
#include  <iostream>
#include  <stdlib.h>
#include  <string.h>

#if ! defined( NO_WGS )
#    include  "gcomm.h"
#    include  "majorbbs.h"
#endif

#include  "strings.h"                           // 'string' class declarations

#if defined( NO_WGS )
#    define ASSERT assert
#    include  <assert.h>
#endif

#define FILREV "$Revision: 8 $"

int StringData::totalRefs;
int StringModRefs::m_refs;
static String *EmptyString = NULL;

const   size_t    Increment = 32;
int String::npos = -1;

static inline size_t RoundUp( size_t num, size_t incr )
{
  size_t    result = num + incr - 1;

  result /= incr;
  result *= incr;

  return( result );
}


String::String()
{
     m_data = EmptyString->m_data;
     AddRef();
}


String::String( const char *chs )
{
     m_data = new StringData( chs );
     AddRef();
}


String::String( const String &st )
{
     m_data = st.m_data;
     AddRef();
}


String::String( char ch, int n )
{
     m_data = new StringData( ch, n );
     AddRef();
}


String::~String()
{
     DecRef();
}


void
String::InitializeEmpty()
{
     EmptyString = new String( "" );
}


void
String::DeleteEmpty()
{
     delete EmptyString;
     EmptyString = NULL;
}


String & String::operator = ( const String & a )
{
     ASSERT( m_data != NULL );

     if ( &a != this )
     {
          DecRef();

          m_data = a.m_data;
          AddRef();
     }

     return( *this );
}


String & String::operator = ( const char *a )
{
     ASSERT( m_data != NULL );

     DecRef();

     m_data = new StringData( a );
     AddRef();

     return( *this );
}


String & String::operator +=( const String & a )
{
     ASSERT( m_data != NULL );

     int  newLen = CurLen() + a.length();

     resize( newLen );

     strcat( m_data->m_buf, a.m_data->m_buf );
     m_data->m_curlen = newLen;

     return( *this );
}


String & String::operator +=( const char *a )
{
     ASSERT( m_data != NULL );

     ASSERT( a != NULL );   // for debugging

     if (a == NULL)         // when NOT debugging, handle it sanely (no-op)
          return( *this );

     int  newLen = CurLen() + strlen( a );

     resize( newLen );

     strcat( m_data->m_buf, a );
     m_data->m_curlen = newLen;

     return( *this );
}

void String::append(const char* st, int len)
{
     ASSERT( m_data != NULL );

     ASSERT( st != NULL );    // for debugging

     if (st == NULL) {        // when NOT debugging, handle it safely
          return;
     }

     int newLen = CurLen() + len;

     resize( newLen );

     strncat(m_data->m_buf,st,len);

     m_data->m_curlen = newLen;
}


String & String::operator +=( const char ch )
{
     ASSERT( m_data != NULL );

     if (ch != 0)
     {
          resize( CurLen() + 1 );

          m_data->m_buf[ CurLen() ] = ch;
          m_data->m_curlen++;
          m_data->m_buf[ CurLen() ] = '\0';
     }

     return( *this );
}


char & String::operator[] ( int n )
{
     ASSERT( m_data != NULL );

     ASSERT( (n >= 0) && (n <= (int)CurLen()) );

     char     &result = buf()[ n ];

     return( result );
}


const char &String::operator[] ( int n ) const
{
     ASSERT( m_data != NULL );

     ASSERT( (n >= 0) && (n <= (int)CurLen()) );

     char     &result = m_data->m_buf[ n ];

     return( result );
}


//--------------------------------------------------------- String::resize() -
// Function:     void String::resize()
//
// Description:  *private* member function which handles String buffer
//               reallocation for the assignment & concatenation operators.
//
// Parameters:   size_t NewLen - new String length to accommodate
//----------------------------------------------------------------------------
//
void String::resize( size_t NewLen )
{
     ASSERT( m_data != NULL );

     if (m_data->RefCount() > 1)
     {
          StringData *newData = new StringData( '\0', NewLen );

          for ( int count = 0; (count <= NewLen) && (count <= CurLen()); ++count )
          {
               newData->m_buf[ count ] = buf()[ count ];
          }

          newData->m_buf[ NewLen ] = 0;
          newData->m_curlen = min( (int)NewLen, CurLen() );

          DecRef();

          m_data = newData;
          AddRef();

          return;
     }

     m_data->Resize( NewLen );

     return;
}


//--------------------------------------------------------- String::substr() -
// Function:     String String::substr()
//
// Description:  return a substring of a String object
//
// Parameters:   int from - position in String to start from
//               int len  - maximum length of String to copy:
//                          defaults to the rest of the String
//
// Returns:      substring of up to 'len' chars, starting at 'from'
//----------------------------------------------------------------------------
//
String String::substr( int from, int len ) const
{
     ASSERT( m_data != NULL );

     ASSERT( (from >= 0) && (from <= (int)CurLen()) );

     if (len == -1)
          len = CurLen() - from;

     ASSERT( len >= 0 );             // allow too-long lengths, but not too-short

     String t( '\0', len );

     for ( int count = 0; count < len; ++count )
     {
          t[ count ] = c_str()[ from + count ];
     }

     return( t );
}


void String::insert( int at, const char *st )
{
     ASSERT( m_data != NULL );

     String    leftPart = substr( 0, at );
     String    rightPart = substr( at );

     while (strlen( leftPart ) < at)
          leftPart += ' ';

     String    result = leftPart;
     result += st;
     result += rightPart;

     int       len = strlen( result );

     buf()[ 0 ] = 0;
     resize( len );

     strcpy( buf(), result.c_str() );

     m_data->m_curlen = len;

     return;
}

void String::remove( int from, int len )
{
     ASSERT( m_data != NULL );

     ASSERT( from >= 0 );

     if (from >= (int)CurLen())
     {
          return;
     }

     if (len == -1)
     {
          len = CurLen() - from;
     }

     ASSERT( len >= 0 );             // allow too-long lengths, but not too-short

     if ((from + len) > (int)CurLen())
     {
          len = CurLen() - from;
     }

     if ((from < (int)CurLen()) && (len > 0))
     {
          if (m_data->RefCount() > 0)
          {
               StringData *newData = new StringData( *m_data );
               DecRef();
               m_data = newData;
               AddRef();
          }

          char     *dst = buf() + from;
          char     *src = buf() + from + len;

          while (*src)
          {
               *dst++ = *src++;
          }

          *dst = 0;
     }

     m_data->m_curlen -= len;

     resize( m_data->m_curlen );

     return;
}


//----------------------------------------------------------- String::Find() -
// Function:     int String::Find()
//
// Description:  find the first occurence of a character within a String
//               object
//
// Parameters:   char c - character we're looking for
//               int caseInsensitive - compare without regard to case?
//
// Returns:      index of character if found
//               < 0 if not found
//----------------------------------------------------------------------------
//
int String::find( char c, int caseInsensitive ) const
{
     ASSERT( m_data != NULL );

     if (! caseInsensitive)
     {
          const char *sp = strchr( c_str(), c );

          if (sp == NULL)
               return( -1 );
          else
               return( sp - c_str() );
     }

     const char *sp = c_str();
     c = tolower( c );

     while (*sp)
     {
          if (tolower( *sp ) == c)
               return( sp - c_str() );

          ++sp;
     }

     return( -1 );
}


int                                //   index of 'c' in string, or -1
String::reverseFind(               // find last occurence of a char in the string
char c,                            //   char to search for
int caseInsensitive) const         //   non-zero for case-insensitive search
{
     ASSERT( m_data != NULL );

     if (! caseInsensitive)
     {
          const char *sp = strrchr( c_str(), c );

          if (sp == NULL)
               return( -1 );
          else
               return( sp - c_str() );
     }

     const char *sp = c_str();
     c = tolower( c );
     int  result = -1;

     while (*sp)
     {
          if (tolower( *sp ) == c)
          {
               result = sp - c_str();
          }

          ++sp;
     }

     return( result );
}




//----------------------------------------------------------- String::Find() -
// Function:     int String::Find()
//
// Description:  find the first occurence of a character string within a
//               String object
//
// Parameters:   const char *s - char string we're looking for
//
// Returns:      index of character if found
//               < 0 if not found
//----------------------------------------------------------------------------
//
int String::find( const char *s, int caseInsensitive ) const
{
     ASSERT( m_data != NULL );

     if (! caseInsensitive)
     {
          const char *sp = strstr( c_str(), s );

          if (sp == NULL)
               return( -1 );
          else
               return( sp - c_str() );
     }

     const char *sp = (c_str() + strlen( c_str() ) - strlen( s ));
     const char *bp = c_str();

     while (bp <= sp)
     {
          if (strnicmp( bp, s, strlen( s ) ) == 0)
               return( bp - c_str() );

          ++bp;
     }

     return( -1 );
}


void
String::AddRef()
{
     ASSERT( m_data != NULL );

     m_data->AddRef();
}


void
String::DecRef()
{
     ASSERT( m_data != NULL );

     ASSERT( m_data->RefCount() > 0 );

     m_data->DecRef();

     if (m_data->RefCount() <= 0)
     {
          delete m_data;
          m_data = NULL;
     }
}


bool operator == ( const String & a, const String & b )
{
     return( strcmp( a.c_str(), b.c_str() ) == 0 );
}


bool operator == ( const char *s, const String & b )
{
     return( strcmp( s, b.c_str() ) == 0 );
}


bool operator == ( const String & a, const char *s )
{
     return( strcmp( a.c_str(), s ) == 0 );
}


String operator + ( const String & a, const String & b )
{
     String    st( a );

     st += b;

     return( st );
}


String operator + ( const String & a, const char *b )
{
     ASSERT( b != NULL );   // for debugging

     String    st( a );

     if (b != NULL)         // when NOT debugging, handle it sanely (no-op)
          st += b;

     return( st );
}


bool operator != ( const String & a, const String & b )
{
     return( strcmp( a.c_str(), b.c_str() ) != 0 );
}


bool operator >= ( const String & a, const String & b )
{
     return( strcmp( a.c_str(), b.c_str() ) >= 0 );
}


bool operator <= ( const String & a, const String & b )
{
     return( strcmp( a.c_str(), b.c_str() ) <= 0 );
}


bool operator > ( const String & a, const String & b )
{
     return( strcmp( a.c_str(), b.c_str() ) > 0 );
}


StringData::StringData()
{
     m_buf = new char[ Increment ];
     m_allocated = Increment;
     m_curlen = 0;
     m_buf[ 0 ] = 0;
     m_refs = 0;

     AddTotalRef();
}


StringData::StringData(
const char *st)
{
     ASSERT( st != NULL );

     m_allocated = RoundUp( strlen( st ) + 1, Increment );

     m_buf = new char[ m_allocated ];
     ASSERT( m_buf != NULL );

     strcpy( m_buf, st );

     m_curlen = strlen( st );

     m_refs = 0;

     AddTotalRef();
}


StringData::StringData(
const StringData &oldData)
{
     if (&oldData != this)
     {
          m_allocated = oldData.m_allocated;

          m_buf = new char[ m_allocated ];
          ASSERT( m_buf != NULL );

          strcpy( m_buf, oldData.m_buf );

          m_curlen = oldData.m_curlen;

          m_refs = 0;

          AddTotalRef();
     }
}


StringData::StringData(
char ch,
int chcount)
{
     m_allocated = RoundUp( chcount + 1, Increment );

     m_buf = new char[ m_allocated ];
     ASSERT( m_buf != NULL );

     memset( m_buf, ch, chcount );
     m_buf[ chcount ] = '\0';

     m_curlen = chcount;

     m_refs = 0;

     AddTotalRef();
}


StringData::~StringData()
{
     ASSERT( m_buf != NULL );
     ASSERT( m_refs == 0 );

     delete [] m_buf;
     m_buf = NULL;

     DecTotalRef();
}


void
StringData::Resize(
int stlen)     // new string len (buflen - 1)
{
     ASSERT( stlen >= 0 );

     int  newBufLen = stlen + 1;

     if (newBufLen > m_allocated)
     {
          newBufLen = RoundUp( newBufLen, Increment );
          char      *newData = new char[ newBufLen ];

          memcpy( newData, m_buf, m_allocated );

          delete [] m_buf;
          m_buf = newData;
          m_allocated = newBufLen;
     }

     ASSERT( m_buf != NULL );
     ASSERT( m_curlen < m_allocated );

     return;
}


void
StringData::AddRef()
{
     ASSERT( m_refs >= 0 );

     ++m_refs;
}


void
StringData::DecRef()
{
     ASSERT( m_refs > 0 );

     --m_refs;
}


StringData::RefCount()
{
     return( m_refs );
}


void
StringData::AddTotalRef()
{
     ++totalRefs;

//     cout << "-- totalRefs: " << totalRefs << "\n";
}


void
StringData::DecTotalRef()
{
     --totalRefs;

     if (totalRefs < 0)
     {
          ASSERT( totalRefs >= 0 );
     }

//     cout << "-- totalRefs: " << totalRefs << "\n";
}


StringModRefs::StringModRefs()
{
     if (m_refs++ == 0)
     {
          String::InitializeEmpty();
     }
}


StringModRefs::~StringModRefs()
{
     if (--m_refs == 0)
     {
          String::DeleteEmpty();
     }
}


int                                //   position of match, or -1
String::find_first_of(             // find first of a set of chars in a string
const char *charlist,              //   chars to search for
int startAt) const                 //   starting from what pos?
{
     int       result = npos;

     for ( int count = startAt; count < length(); ++count )
     {
          if (strchr( charlist, buf()[ count ] ) != NULL)
          {
               result = count; 
               break;
          }
     }

     return( result );
}


int                                //   first non-match, or -1
String::find_first_not_of(         // find first not of a set of chars
const char *charlist,              //   chars to match
int startAt) const                 //   start searching at
{
     int       result = npos;

     for ( int count = startAt; count < length(); ++count )
     {
          if (strchr( charlist, buf()[ count ] ) == NULL)
          {
               result = count; 
               break;
          }
     }

     return( result );
}


