/*
*******************************************************************************
*                                                                             *
* COPYRIGHT:                                                                  *
*   (C) Copyright International Business Machines Corporation, 1998-1999      *
*   Licensed Material - Program-Property of IBM - All Rights Reserved.        *
*   US Government Users Restricted Rights - Use, duplication, or disclosure   *
*   restricted by GSA ADP Schedule Contract with IBM Corp.                    *
*                                                                             *
*******************************************************************************
*
* File unistr.cpp
*
* Modification History:
*
*   Date        Name        Description
*   09/25/98    stephen     Creation.
*   04/20/99    stephen     Overhauled per 4/16 code review.
*   07/09/99    stephen     Renamed {hi,lo},{byte,word} to icu_X for HP/UX
*******************************************************************************
*/

#include "unistr.h"

#include "locid.h"
#include "cstring.h"
#include "cmemory.h"
#include "ustring.h"
#include "mutex.h"

#if 0
//DEBUGGING
#include <iostream.h>

void
print(const UnicodeString& s,
      const char *name)
{
  UChar c;
  cout << name << ":|";
  for(int i = 0; i < s.length(); ++i) {
    c = s[i];
    if(c>= 0x007E || c < 0x0020)
      cout << "[0x" << hex << s[i] << "]";
    else
      cout << (char) s[i];
  }
  cout << '|' << endl;
}

void
print(const UChar *s,
      int32_t len,
      const char *name)
{
  UChar c;
  cout << name << ":|";
  for(int i = 0; i < len; ++i) {
    c = s[i];
    if(c>= 0x007E || c < 0x0020)
      cout << "[0x" << hex << s[i] << "]";
    else
      cout << (char) s[i];
  }
  cout << '|' << endl;
}
// END DEBUGGING
#endif

// Local function definitions for now

// move u_arrayCompare to utypes.h ??
inline int8_t
u_arrayCompare(const UChar *src, int32_t srcStart,
         const UChar *dst, int32_t dstStart, int32_t count)
{return icu_memcmp(src+srcStart, dst+dstStart, (size_t)(count*sizeof(*src)));}

// need to copy areas that may overlap
inline void
us_arrayCopy(const UChar *src, int32_t srcStart,
         UChar *dst, int32_t dstStart, int32_t count)
{
  if(count>0) {
    icu_memmove(dst+dstStart, src+srcStart, (size_t)(count*sizeof(*src)));
  }
}

// static initialization
const UChar UnicodeString::fgInvalidUChar      = 0xFFFF;
const int32_t UnicodeString::kGrowSize         = 0x80;
const int32_t UnicodeString::kInvalidHashCode  = 0;
const int32_t UnicodeString::kEmptyHashCode    = 1;
UConverter* UnicodeString::fgDefaultConverter  = 0;

//========================================
// Constructors
//========================================
UnicodeString::UnicodeString()
  : fArray(fStackBuffer),
    fLength(0),
    fCapacity(US_STACKBUF_SIZE),
    fRefCounted(FALSE),
    fHashCode(kEmptyHashCode),
    fBogus(FALSE)
{}

UnicodeString::UnicodeString(int32_t capacity)
  : fArray(0),
    fLength(0),
    fCapacity(0),
    fRefCounted(FALSE),
    fHashCode(kEmptyHashCode),
    fBogus(FALSE)
{
  fArray = allocate(capacity, fCapacity);
  if(! fArray) {
    setToBogus();
    return;
  }

  setRefCount(1);
}

UnicodeString::UnicodeString(UChar ch)
  : fArray(fStackBuffer),
    fLength(0),
    fCapacity(US_STACKBUF_SIZE),
    fRefCounted(FALSE),
    fHashCode(kEmptyHashCode),
    fBogus(FALSE)
{
  doReplace(0, 0, &ch, 0, 1);
}

UnicodeString::UnicodeString(const UChar *text)
  : fArray(fStackBuffer),
    fLength(0),
    fCapacity(US_STACKBUF_SIZE),
    fRefCounted(FALSE),
    fHashCode(kEmptyHashCode),
    fBogus(FALSE)
{
  doReplace(0, 0, text, 0, u_strlen(text));
}

UnicodeString::UnicodeString( const UChar *text,
                  int32_t textLength)
  : fArray(fStackBuffer),
    fLength(0),
    fCapacity(US_STACKBUF_SIZE),
    fRefCounted(FALSE),
    fHashCode(kEmptyHashCode),
    fBogus(FALSE)
{
  doReplace(0, 0, text, 0, textLength);
}

UnicodeString::UnicodeString(const char *codepageData,
                 const char *codepage)
  : fArray(fStackBuffer),
    fLength(0),
    fCapacity(US_STACKBUF_SIZE),
    fRefCounted(FALSE),
    fHashCode(kEmptyHashCode),
    fBogus(FALSE)
{
  if(codepageData != 0)
    doCodepageCreate(codepageData, icu_strlen(codepageData), codepage);
}


UnicodeString::UnicodeString(const char *codepageData,
                 int32_t dataLength,
                 const char *codepage)
  : fArray(fStackBuffer),
    fLength(0),
    fCapacity(US_STACKBUF_SIZE),
    fRefCounted(FALSE),
    fHashCode(kEmptyHashCode),
    fBogus(FALSE)
{
  doCodepageCreate(codepageData, dataLength, codepage);
}

//========================================
// Destructor
//========================================
UnicodeString::~UnicodeString()
{
  // decrement ref count and reclaim storage, if owned
  if(fRefCounted && removeRef() == 0)
    delete [] fArray;
}

//========================================
// Assignment
//========================================
UnicodeString&
UnicodeString::operator= (const UnicodeString& src)
{
  // if src is bogus, or we're bogus, or assigning to ourselves, do nothing
  if(fBogus || src.isBogus() || this == &src)
  return *this;

  // if src is ref counted, point ourselves at its array
  if(src.fRefCounted) {

    // if we're ref counted, decrement our current ref count
    if(fRefCounted && removeRef() == 0)
    delete [] fArray;

    fArray      = src.fArray;
    fLength     = src.fLength;
    fCapacity   = src.fCapacity;
    fHashCode   = src.fHashCode;
    fRefCounted = TRUE;
    addRef();
  }
  // if src isn't ref counted, just do a replace
  else {
    doReplace(0, fLength, src.fArray, 0, src.fLength);
    fHashCode = src.fHashCode;
  }

  return *this;
}

//========================================
// Miscellaneous operations
//========================================
int32_t
UnicodeString::numDisplayCells( UTextOffset start,
                int32_t length,
                bool_t asian) const
{
  // pin indices to legal values
  pinIndices(start, length);

  UChar c;
  int32_t result = 0;
  UTextOffset limit = start + length;

  while(start < limit) {
    c = getArrayStart()[start];
    switch(Unicode::getCellWidth(c)) {
    case Unicode::ZERO_WIDTH:
      break;;

    case Unicode::HALF_WIDTH:
      result += 1;
      break;

    case Unicode::FULL_WIDTH:
      result += 2;
      break;

    case Unicode::NEUTRAL:
      result += (asian ? 2 : 1);
      break;
    }
    ++start;
  }

  return result;
}

UCharReference
UnicodeString::operator[] (UTextOffset pos)
{
  return UCharReference(this, pos);
}

//========================================
// Read-only implementation
//========================================
int8_t
UnicodeString::doCompare( UTextOffset start,
              int32_t length,
              const UnicodeString& src,
              UTextOffset srcStart,
              int32_t srcLength) const
{
  // pin indices to legal values
  pinIndices(start, length);

  // get the correct pointer
  const UChar *chars = getArrayStart();

  // compare the characters
  return (src.compare(srcStart, srcLength, chars, start, length) * -1);
}

int8_t
UnicodeString::doCompare( UTextOffset start,
              int32_t length,
              const UChar *srcChars,
              UTextOffset srcStart,
              int32_t srcLength) const
{
  // pin indices to legal values
  pinIndices(start, length);

  // get the correct pointer
  const UChar *chars = getArrayStart();

  // we're comparing different lengths
  if(length != srcLength) {

    // compare the minimum # of characters
    int32_t minLength     = (length < srcLength ? length : srcLength);
    const UChar *minLimit = chars + minLength;
    const UChar *limit    = chars + length;
    int8_t result;

    // adjust for starting offsets
    chars += start;
    srcChars += srcStart;

    while(chars < minLimit) {
      result = (*chars - *srcChars);

      if(result != 0)
    return result;

      ++chars;
      ++srcChars;
    }

    // if we got here, the leading portions are identical
    return (chars < limit ? 1 : -1);
  }
  // compare two identical lengths, use u_arrayCompare
  else
    return u_arrayCompare(chars, start, srcChars, srcStart, length);
}

void
UnicodeString::doExtract(UTextOffset start,
             int32_t length,
             UChar *dst,
             UTextOffset dstStart) const
{
  // pin indices to legal values
  pinIndices(start, length);
  us_arrayCopy(getArrayStart(), start, dst, dstStart, length);
}


UTextOffset
UnicodeString::doIndexOf(UChar c,
             UTextOffset start,
             int32_t length) const
{
  // pin indices
  pinIndices(start, length);

  // find the first occurrence of c
  const UChar *begin = getArrayStart() + start;
  const UChar *limit = begin + length;

  while(begin < limit && *begin != c)
    ++begin;

  return (begin == limit ? -1 : begin - getArrayStart());
}

UTextOffset
UnicodeString::doLastIndexOf(UChar c,
                 UTextOffset start,
                 int32_t length) const
{
  // pin indices
  pinIndices(start, length);

  const UChar *begin = getArrayStart() + start + length;
  const UChar *limit = begin - length;

  while(begin > limit && *begin != c)
    --begin;

  return (begin == limit ? -1 : begin - getArrayStart());
}


//========================================
// Write implementation
//========================================

UnicodeString&
UnicodeString::setCharAt(UTextOffset offset,
             UChar c)
{
  if(offset < 0)
    offset = 0;
  else if(offset >= fLength)
    offset = fLength - 1;

  doSetCharAt(offset, c);
  fHashCode = kInvalidHashCode;
  return *this;
}

UnicodeString&
UnicodeString::toUpper()
{ return toUpper(Locale::getDefault()); }

UnicodeString&
UnicodeString::toLower()
{ return toLower(Locale::getDefault()); }

UnicodeString&
UnicodeString::toUpper(const Locale& locale)
{
  UTextOffset start = 0;
  UTextOffset limit = fLength;
  UChar c;
  UnicodeString lang;

  locale.getLanguage(lang);

  // The German sharp S character (U+00DF)'s uppercase equivalent is
  // "SS", making it the only character that expands to two characters
  // when its case is changed (we don't automatically convert "SS" to
  // U+00DF going to lowercase because it can only be determined from
  // knowing the language whether a particular "SS" should map to
  // U+00DF or "ss").  So we make a preliminary pass through the
  // string looking for sharp S characters and then go back and make
  // room for the extra capital Ses if we find any.  [For performance,
  // we only do this extra work if the language is actually German]
  if(lang == "de") {
    UChar SS [] = { 0x0053, 0x0053 };
    while(start < limit) {

      c = getArrayStart()[start];

      // A sharp s needs to be replaced with two capital S's.
      if(c == 0x00DF) {
    doReplace(start, 1, SS, 0, 2);
    start++;
    limit++;
      }

      // Otherwise, the case conversion can be handled by the Unicode unit.
      else if(Unicode::isLowerCase(c))
    doSetCharAt(start, Unicode::toUpperCase(c));

      // If no conversion is necessary, do nothing
      ++start;
    }
  }

  // If the specfied language is Turkish, then we have to special-case
  // for the Turkish dotted and dotless Is.  The regular lowercase i
  // maps to the capital I with a dot (U+0130), and the lowercase i
  // without the dot (U+0131) maps to the regular capital I
  else if(lang == "tr") {
    while(start < limit) {
      c = getArrayStart()[start];

      if(c == 0x0069/*'i'*/)
    doSetCharAt(start, 0x0130);
      else if(c == 0x0131)
    doSetCharAt(start, 0x0049/*'I'*/);
      else if(Unicode::isLowerCase(c))
    doSetCharAt(start, Unicode::toUpperCase(c));
      ++start;
    }
  }

  else {
    while(start < limit) {
      c = getArrayStart()[start];
      if(Unicode::isLowerCase(c))
    doSetCharAt(start, Unicode::toUpperCase(c));
      ++start;
    }
  }

  fHashCode = kInvalidHashCode;

  return *this;
}

UnicodeString&
UnicodeString::toLower(const Locale& locale)
{
  UTextOffset start = 0;
  UTextOffset limit = fLength;
  UChar c;
  UnicodeString lang;

  locale.getLanguage(lang);

  // if the specfied language is Turkish, then we have to special-case
  // for the Turkish dotted and dotless Is.  The capital I with a dot
  // (U+0130) maps to the regular lowercase i, and the regular capital
  // I maps to the lowercase i without the dot (U+0131)
  if(lang == "tr") {
    while(start < limit) {
      c = getArrayStart()[start];
      if(c == 0x0049) // 'I'
    doSetCharAt(start, 0x0131);
      else if(c == 0x0130)
    doSetCharAt(start, 0x0069); // 'i'
      else if(Unicode::isUpperCase(c) || Unicode::isTitleCase(c))
    doSetCharAt(start, Unicode::toLowerCase(c));
      ++start;
    }
  }

  // if the specfied language is Greek, then we have to special-case
  // for the capital letter sigma (U+3A3), which has two lower-case
  // forms.  If the character following the capital sigma is a letter,
  // we use the medial form (U+3C3); otherwise, we use the final form
  // (U+3C2).
  else if(lang == "el") {
    while(start < limit) {
      c = getArrayStart()[start];
      if(c == 0x3a3) {
    if(start + 1 < limit && Unicode::isLetter(getArrayStart()[start + 1]))
      doSetCharAt(start, 0x3C3);
    else
      doSetCharAt(start, 0x3C2);
      }
      else if(Unicode::isUpperCase(c) || Unicode::isTitleCase(c))
    doSetCharAt(start, Unicode::toLowerCase(c));
      ++start;
    }
  }

  // if the specified language is anything other than Turkish or
  // Greek, we rely on the Unicode class to do all our case mapping--
  // there are no other special cases
  else {
    while(start < limit) {
      c = getArrayStart()[start];
      if(Unicode::isUpperCase(c) || Unicode::isTitleCase(c))
    doSetCharAt(start, Unicode::toLowerCase(c));
      ++start;
    }
  }

  fHashCode = kInvalidHashCode;

  return *this;
}

// for speed, no bounds checking is performed and the hash code isn't changed
UnicodeString&
UnicodeString::doSetCharAt(UTextOffset offset,
               UChar c)
{
  // clone our array, if necessary
  cloneArrayIfNeeded();

  // set the character
  fArray[ (fRefCounted ? offset + 1 : offset) ] = c;
  return *this;
}

UnicodeString&
UnicodeString::doReplace( UTextOffset start,
              int32_t length,
              const UnicodeString& src,
              UTextOffset srcStart,
              int32_t srcLength)
{
  // pin the indices to legal values
  src.pinIndices(srcStart, srcLength);

  // get the characters from src
  const UChar *chars = src.getArrayStart();

  // and replace the range in ourselves with them
  doReplace(start, length, chars, srcStart, srcLength);

  return *this;
}

UnicodeString&
UnicodeString::doReplace(UTextOffset start,
             int32_t length,
             const UChar *srcChars,
             UTextOffset srcStart,
             int32_t srcLength)
{
  // if we're bogus, do nothing
  if(fBogus)
    return *this;

  bool_t deleteWhenDone = FALSE;
  UChar *bufferToDelete = 0;

  // clone our array, if necessary
  cloneArrayIfNeeded();

  // pin the indices to legal values
  pinIndices(start, length);

  // calculate the size of the string after the replace
  int32_t newSize = fLength - length + srcLength;

  // allocate a bigger array if needed
  if( newSize > getCapacity() ) {

    // allocate at minimum the current capacity + needed space
    int32_t tempLength;
    UChar *temp = allocate(fCapacity + srcLength, tempLength);
    if(! temp) {
      setToBogus();
      return *this;
    }

    // if we're not currently ref counted, shift the array right by one
    if(fRefCounted == FALSE)
      us_arrayCopy(fArray, 0, temp, 1, fLength);
    // otherwise, copy the old array into temp, including the ref count
    else
      us_arrayCopy(fArray, 0, temp, 0, fLength + 1);

    // delete the old array if we were ref counted
    if(fRefCounted && removeRef() == 0) {
      // if the srcChars array is the same as this object's array,
      // don't delete it until the end of the method.  this can happen
      // in code like UnicodeString s = "foo"; s += s;
      if(srcChars != getArrayStart())
    delete [] fArray;
      else {
    deleteWhenDone = TRUE;
    bufferToDelete = fArray;
      }
    }

    // use the new array
    fCapacity = tempLength;
    fArray = temp;
    setRefCount(1);
  }

  // now do the replace

  // first copy the portion that isn't changing, leaving a hole
  us_arrayCopy(getArrayStart(), start + length,
          getArrayStart(), start + srcLength,
          fLength - (start + length));

  // now fill in the hole with the new string
  us_arrayCopy(srcChars, srcStart, getArrayStart(), start, srcLength);

  fLength = newSize;
  fHashCode = kInvalidHashCode;

  if(deleteWhenDone)
    delete [] bufferToDelete;

  return *this;
}

UnicodeString&
UnicodeString::doReverse(UTextOffset start,
             int32_t length)
{
  // if we're bogus, do nothing
  if(fBogus)
    return *this;

  // clone our array, if necessary
  cloneArrayIfNeeded();

  // pin the indices to legal values
  pinIndices(start, length);

  UChar *left = getArrayStart() + start;
  UChar *right = getArrayStart() + start + length;
  UChar swap;

  while(left < --right) {
    swap = *left;
    *left++ = *right;
    *right = swap;
  }

  fHashCode = kInvalidHashCode;

  return *this;
}

//========================================
// Hashing
//========================================
int32_t
UnicodeString::doHashCode()
{
  const UChar *key     = getArrayStart();
  int32_t len         = fLength;
  int32_t hash         = kInvalidHashCode;
  const UChar *limit     = key + len;
  int32_t inc         = (len >= 128 ? len/64 : 1);

  /*
    We compute the hash by iterating sparsely over 64 (at most)
    characters spaced evenly through the string.  For each character,
    we multiply the previous hash value by a prime number and add the
    new character in, in the manner of an additive linear congruential
    random number generator, thus producing a pseudorandom
    deterministic value which should be well distributed over the
    output range. [LIU] */

  while(key < limit) {
    hash = (hash * 37) + *key;
    key += inc;
  }

  if(hash == kInvalidHashCode)
    hash = kEmptyHashCode;

  fHashCode = hash;
  return fHashCode;
}

//========================================
// Bogusify?
//========================================
void
UnicodeString::setToBogus()
{
  fBogus = TRUE;
  if(fRefCounted) {
    if(removeRef() == 0)
      delete [] fArray;

    fArray = 0;
    fCapacity = fLength = 0;
  }

  fHashCode = kInvalidHashCode;
}

//========================================
// Codeset conversion
//========================================
int32_t
UnicodeString::extract(UTextOffset start,
               int32_t length,
               char *dst,
               const char *codepage) const
{
  // if we're bogus or there's nothing to convert, do nothing
  if(fBogus || length == 0)
    return 0;

  // pin the indices to legal values
  pinIndices(start, length);

  int32_t convertedLen = 0;

  // set up the conversion parameters
  int32_t sourceLen        = length;
  const UChar *mySource    = getArrayStart() + start;
  const UChar *mySourceEnd = mySource + length;
  char *myTarget           = dst;
  char *myTargetLimit;
  UErrorCode status        = U_ZERO_ERROR;
  int32_t arraySize        = 0x0FFFFFFF;

  // create the converter
  UConverter *converter = 0;

  // if the codepage is the default, use our cache
  if(codepage == 0)
    converter = getDefaultConverter(status);
  else
    converter = ucnv_open(codepage, &status);

  // if we failed, set the appropriate flags and return
  if(U_FAILURE(status)) {
    // close the converter
    if(codepage == 0)
      releaseDefaultConverter(converter);
    else
      ucnv_close(converter);
    return 0;
  }

  myTargetLimit = myTarget + arraySize;

  if(myTargetLimit < myTarget)  /* ptr wrapped around: pin to U_MAX_PTR */
    myTargetLimit = (char*)U_MAX_PTR; 

  // perform the conversion
  // there is no loop here since we assume the buffer is large enough

  ucnv_fromUnicode(converter, &myTarget,  myTargetLimit,
           &mySource, mySourceEnd, NULL, TRUE, &status);

  // close the converter
  if(codepage == 0)
    releaseDefaultConverter(converter);
  else
    ucnv_close(converter);

  return (myTarget - dst);
}

void
UnicodeString::doCodepageCreate(const char *codepageData,
                int32_t dataLength,
                const char *codepage)
{
  // if there's nothing to convert, do nothing
  if(codepageData == 0 || dataLength == 0)
    return;

  // set up the conversion parameters
  int32_t sourceLen        = dataLength;
  const char *mySource     = codepageData;
  const char *mySourceEnd  = mySource + sourceLen;
  UChar *myTarget          = getArrayStart();
  UErrorCode status        = U_ZERO_ERROR;
  int32_t arraySize        = getCapacity();

  // create the converter
  UConverter *converter = 0;

  // if the codepage is the default, use our cache
  converter = (codepage == 0
           ? getDefaultConverter(status)
           : ucnv_open(codepage, &status));

  // if we failed, set the appropriate flags and return
  if(U_FAILURE(status)) {
    // close the converter
    if(codepage == 0)
      releaseDefaultConverter(converter);
    else
      ucnv_close(converter);
    setToBogus();
    return;
  }

  // perform the conversion
  do {
    // reset the error code
    status = U_ZERO_ERROR;

    // perform the conversion
    ucnv_toUnicode(converter, &myTarget,  myTarget + arraySize,
           &mySource, mySourceEnd, NULL, TRUE, &status);

    // update the conversion parameters
    fLength      = myTarget - getArrayStart();
    arraySize    = getCapacity() - fLength;

    // allocate more space and copy data, if needed
    if(fLength < dataLength) {
      int32_t tempCapacity;
      UChar *temp = allocate(fCapacity, tempCapacity);

      if(! temp) {
    // close the converter
    if(codepage == 0)
      releaseDefaultConverter(converter);
    else
      ucnv_close(converter);
    // set flags and return
    setToBogus();
    return;
      }

      // if we're not currently ref counted, shift the array right by one
      if(fRefCounted == FALSE)
    us_arrayCopy(fArray, 0, temp, 1, fLength);
      // otherwise, copy the old array into temp, including the ref count
      else
          us_arrayCopy(fArray, 0, temp, 0, fLength + 1);

      if(fRefCounted && removeRef() == 0)
    delete [] fArray;

      fArray      = temp;
      fCapacity   = tempCapacity;

      setRefCount(1);

      myTarget    = getArrayStart() + fLength;
      arraySize   = getCapacity() - fLength;
    }
  }
  while(status == U_INDEX_OUTOFBOUNDS_ERROR);

  fHashCode = kInvalidHashCode;

  // close the converter
  if(codepage == 0)
    releaseDefaultConverter(converter);
  else
    ucnv_close(converter);
}

//========================================
// External Buffer
//========================================
UnicodeString::UnicodeString(UChar *buff,
                 int32_t bufLength,
                 int32_t buffCapacity)
  : fArray(buff),
    fLength(bufLength),
    fCapacity(buffCapacity),
    fRefCounted(FALSE),
    fHashCode(kInvalidHashCode),
    fBogus(FALSE)
{}

const UChar*
UnicodeString::getUChars() const
{
  // if we're bogus, do nothing
  if(fBogus)
    return 0;

  // clone our array, if necessary
  ((UnicodeString*)this)->cloneArrayIfNeeded();

  // no room for null, resize
  if(getCapacity() <= fLength) {
    // allocate at minimum the current capacity + needed space
    int32_t tempLength;
    UChar *temp = allocate(fCapacity + 1, tempLength);
    if(! temp) {
      ((UnicodeString*)this)->setToBogus();
      return 0;
    }

    // if we're not currently ref counted, shift the array right by one
    if(fRefCounted == FALSE)
      us_arrayCopy(fArray, 0, temp, 1, fLength);
    // otherwise, copy the old array into temp, including the ref count
    else
      us_arrayCopy(fArray, 0, temp, 0, fLength + 1);

    // delete the old array
    if(fRefCounted && ((UnicodeString*)this)->removeRef() == 0)
      delete [] ((UnicodeString*)this)->fArray;

    // use the new array
    ((UnicodeString*)this)->fCapacity = tempLength;
    ((UnicodeString*)this)->fArray    = temp;
    ((UnicodeString*)this)->setRefCount(1);
  }

  // tack on a trailing null
  fArray[(fRefCounted ? 1 : 0) + fLength] = 0;

  return getArrayStart();
}

UChar*
UnicodeString::orphanStorage()
{
  // if we're bogus, do nothing
  if(fBogus)
    return 0;

  // clone our array, if necessary
  ((UnicodeString*)this)->cloneArrayIfNeeded();

  // if we're ref counted, get rid of the leading ref count
  if(fRefCounted) {
    us_arrayCopy(getArrayStart(), 0, fArray, 0, fLength);
  }

  UChar *retVal = fArray;

  fArray = fStackBuffer;
  fLength = 0;
  fCapacity = US_STACKBUF_SIZE;
  fHashCode = kEmptyHashCode;

  return retVal;
}

//========================================
// Miscellaneous
//========================================
void
UnicodeString::pinIndices(UTextOffset& start,
              int32_t& length) const
{
  // pin indices
  if(length < 0 || start < 0)
    start = length = 0;
  else {
    if(length > (fLength - start))
      length = (fLength - start);
  }
}

void
UnicodeString::cloneArrayIfNeeded()
{
  // if we're ref counted, make a copy of the buffer if necessary
  if(fRefCounted && refCount() > 1) {
    UChar *copy = new UChar [ fCapacity ];
    if( ! copy ) {
      setToBogus();
      return;
    }

    // copy the current shared array into our new array
    us_arrayCopy(fArray, 0, copy, 0, fLength + 1);

    // remove a reference from the current shared array
    // if there are no more references to the current shared array,
    // after we remove the reference, delete the array
    if(removeRef() == 0)
      delete [] fArray;

    // make our array point to the new copy and set the ref count to one
    fArray = copy;
    setRefCount(1);
  }
}

// private function for C API
U_CFUNC const UChar*
T_UnicodeString_getUChars(const UnicodeString *s)
{
  return s->getUChars();
}


//========================================
// Default converter caching
//========================================

UConverter*
UnicodeString::getDefaultConverter(UErrorCode &status)
{
  UConverter *converter = 0;

  if(fgDefaultConverter != 0) {
    Mutex lock;

    // need to check to make sure it wasn't taken out from under us
    if(fgDefaultConverter != 0) {
      converter = fgDefaultConverter;
      fgDefaultConverter = 0;
    }
  }

  // if the cache was empty, create a converter
  if(converter == 0) {
    converter = ucnv_open(0, &status);
    if(U_FAILURE(status))
      return 0;
  }

  return converter;
}

void
UnicodeString::releaseDefaultConverter(UConverter *converter)
{
  if(fgDefaultConverter == 0) {
    Mutex lock;

    if(fgDefaultConverter == 0) {
      fgDefaultConverter = converter;
      converter = 0;
    }
  }

  // it's safe to close a NULL converter
  ucnv_close(converter);
}

//========================================
// Streaming (to be removed)
//========================================

#include <iostream.h>
#include "unistrm.h"
#include "filestrm.h"


inline uint8_t
icu_hibyte(uint16_t x)
{ return (uint8_t)(x >> 8); }

inline uint8_t
icu_lobyte(uint16_t x)
{ return (uint8_t)(x & 0xff); }

inline uint16_t
icu_hiword(uint32_t x)
{ return (uint16_t)(x >> 16); }

inline uint16_t
icu_loword(uint32_t x)
{ return (uint16_t)(x & 0xffff); }

inline void
writeLong(FileStream *os,
      int32_t x)
{
  uint16_t word = icu_hiword((uint32_t)x);
  T_FileStream_putc(os, icu_hibyte(word));
  T_FileStream_putc(os, icu_lobyte(word));
  word = icu_loword((uint32_t)x);
  T_FileStream_putc(os, icu_hibyte(word));
  T_FileStream_putc(os, icu_lobyte(word));
}

inline int32_t
readLong(FileStream *is)
{
  int32_t x = 0;
  uint16_t byte;

  byte = T_FileStream_getc(is);
  x |= byte;
  byte = T_FileStream_getc(is);
  x = (x << 8) | byte;
  byte = T_FileStream_getc(is);
  x = (x << 8) | byte;
  byte = T_FileStream_getc(is);
  x = (x << 8) | byte;

  return x;
}

inline void
writeUChar(FileStream *os,
       UChar c)
{
  T_FileStream_putc(os, icu_hibyte(c));
  T_FileStream_putc(os, icu_lobyte(c));
}

inline UChar
readUChar(FileStream *is)
{
  UChar c = 0;
  uint16_t byte;

  byte = T_FileStream_getc(is);
  c |= byte;
  byte = T_FileStream_getc(is);
  c = (c << 8) | byte;

  return c;
}

void
UnicodeStringStreamer::streamOut(const UnicodeString *s,
                 FileStream *os)
{
  if(!T_FileStream_error(os))
    writeLong(os, s->fLength);

  const UChar *c   = s->getArrayStart();
  const UChar *end = c + s->fLength;

  while(c != end && ! T_FileStream_error(os))
    writeUChar(os, *c++);
}

void
UnicodeStringStreamer::streamIn(UnicodeString *s,
                FileStream *is)
{
  int32_t newSize;

  // handle error conditions
  if(T_FileStream_error(is) || T_FileStream_eof(is)) {
    s->setToBogus();
    return;
  }
  newSize = readLong(is);
  if((newSize < 0) || T_FileStream_error(is)
     || ((newSize > 0) && T_FileStream_eof(is))) {
    s->setToBogus(); //error condition
    return;
  }

  // clone s's array, if needed
  s->cloneArrayIfNeeded();

  // if the string isn't big enough to hold the data, enlarge it
  if(s->getCapacity() < newSize) {

    int32_t tempLength;
    UChar *temp = s->allocate(newSize, tempLength);
    if(! temp) {
      s->setToBogus();
      return;
    }

    // if s is not currently ref counted, shift the array right by one
    if(s->fRefCounted == FALSE)
      us_arrayCopy(s->fArray, 0, temp, 1, s->fLength);
    // otherwise, copy the old array into temp, including the ref count
    else
      us_arrayCopy(s->fArray, 0, temp, 0, s->fLength + 1);

    // delete the old array if s is ref counted
    if(s->fRefCounted && s->removeRef() == 0)
      delete [] s->fArray;

    // use the new array
    s->fCapacity = tempLength;
    s->fArray    = temp;
    s->setRefCount(1);
  }

  UChar *c = s->getArrayStart();
  UChar *end = c + newSize;

  while(c < end && ! (T_FileStream_error(is) || T_FileStream_eof(is)))
    *c++ = readUChar(is);

  // couldn't read all chars
  if(c < end) {
    s->setToBogus();
    return;
  }

  s->fLength = newSize;
}

// console IO

ostream&
operator<<(ostream& stream,
       const UnicodeString& s)
{
  UTextOffset i;
  UChar c;
  int32_t saveFlags = stream.flags();

  stream << hex;

  for(i = 0; i < s.length(); i++) {
    c = s.charAt(i);
    if((c >= ' ' && c <= '~') || c == '\n')
      stream << (char)c;
    else
      stream << "[0x" << c << "]";
  }
  stream.flush();
  stream.setf(saveFlags & ios::basefield, ios::basefield);
  return stream;
}
