/****************************************************************************
 * apps/graphics/nxwidgets/src/cnxstring.cxx
 *
 *   Copyright (C) 2012, 2018 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX, NxWidgets, nor the names of its contributors
 *    me be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************
 *
 * Portions of this package derive from Woopsi (http://woopsi.org/) and
 * portions are original efforts.  It is difficult to determine at this
 * point what parts are original efforts and which parts derive from Woopsi.
 * However, in any event, the work of  Antony Dzeryn will be acknowledged
 * in most NxWidget files.  Thanks Antony!
 *
 *   Copyright (c) 2007-2011, Antony Dzeryn
 *   All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * * Neither the names "Woopsi", "Simian Zombie" nor the
 *   names of its contributors may be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Antony Dzeryn ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Antony Dzeryn BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <cstring>
#include <cstdarg>
#include <cstdio>

#include "graphics/nxwidgets/cnxstring.hxx"
#include "graphics/nxwidgets/cstringiterator.hxx"

/****************************************************************************
 * CNxString Method Implementations
 ****************************************************************************/

using namespace NXWidgets;

/**
 * Constructor to create an empty string object.
 */

CNxString::CNxString()
{
  m_text          = (FAR nxwidget_char_t *)NULL;
  m_stringLength  = 0;
  m_allocatedSize = 0;
  m_growAmount    = 16;
}

/**
 * Constructor to create a string from a C character array.
 *
 * @param text Pointer to a char array to use as the basis of the
 * string.
 */

CNxString::CNxString(FAR const char *text)
{
  m_text          = (FAR nxwidget_char_t *)NULL;
  m_stringLength  = 0;
  m_allocatedSize = 0;
  m_growAmount    = 16;

  setText(text);
}

/**
 * Constructor to create a string from a single character.
 * @param letter Single character to use as the basis of the string.
 */

CNxString::CNxString(const nxwidget_char_t text)
{
  m_text          = (FAR nxwidget_char_t *)NULL;
  m_stringLength  = 0;
  m_allocatedSize = 0;
  m_growAmount    = 16;

  setText(text);
}

CNxString::CNxString(const CNxString &string)
{
  m_text          = (FAR nxwidget_char_t *)NULL;
  m_stringLength  = 0;
  m_allocatedSize = 0;
  m_growAmount    = 16;

  setText(string);
}

/**
 * Creates and returns a new CCStringIterator object that will iterate
 * over this string.  The object must be manually deleted once it is
 * no longer needed.
 *
 * @return A new CCStringIterator object.
 */

CStringIterator *CNxString::newStringIterator() const
{
  return new CStringIterator(this);
}

/**
 * Copy the internal array to the supplied buffer.  The buffer must be
 * large enough to contain the full text in the string.  The
 * getAllocSize() method can be used to obtain the length of the string.
 * Unlike the CNxString class, the char array is null-terminated.
 * The buffer must be (getAllocSize() + 2) bytes long, in order to
 * accommodate the terminator.
 *
 * @param buffer Buffer to copy the internal char array to.
 */

void CNxString::copyToCharArray(FAR nxwidget_char_t *buffer) const
{
  unsigned int dataLength = sizeof(nxwidget_char_t) * m_stringLength;
  memcpy(buffer, m_text, dataLength);
  buffer[dataLength] = '\0';
}

/**
 * Set the text in the string.
 *
 * @param text CNxString containing the new data for this string.
 */

void CNxString::setText(const CNxString &text)
{
  // Ensure we've got enough memory available

  allocateMemory(text.getLength(), false);

  // Copy the text into the internal array

  memcpy(m_text, text.getCharArray(), sizeof(nxwidget_char_t) * text.getLength());

  // Save size (in characters) of the string

  m_stringLength = text.getLength();
}

/**
 * Set the 8-bit C-string text in the string.
 *
 * @param text Char array to use as the new data for this string.
 */

void CNxString::setText(FAR const char *text)
{
  int length = strlen(text);

  // Ensure we've got enough memory available

  allocateMemory(length, false);

  // Copy characters into m_text, converting from 8- to 16-bit characters
  // (if necessary) and cache the length

  for (int i = 0; i < length; i++)
    {
      m_text[i] = (nxwidget_char_t)text[i];
    }

  m_stringLength = length;
}

/**
 * Set the text in the string.
 *
 * @param text Char array to use as the new data for this string.
 */

void CNxString::setText(FAR const nxwidget_char_t *text, int nchars)
{
  // Ensure we've got enough memory available

  allocateMemory(nchars, false);

  // Copy characters into m_text and cache the length

  m_stringLength = nchars;
  memcpy(m_text, text, sizeof(nxwidget_char_t) * nchars);
}

/**
 * Set the text in the string.
 *
 * @param text Character to use as the new data for this string.
 */

void CNxString::setText(const nxwidget_char_t letter)
{
  // Ensure we've got enough memory available

  allocateMemory(1, false);

  // Copy the valid character into m_text and cache the length

  *m_text         = letter;
   m_stringLength = 1;
}

/**
 * Append text to the end of the string.
 *
 * @param text String to append.
 */

void CNxString::append(const CNxString &text)
{
  // Ensure we've got enough memory available

  allocateMemory(m_stringLength + text.getLength(), true);

  // Append the new string to the end of the array

  FAR nxwidget_char_t       *dest = &m_text[m_stringLength];
  FAR const nxwidget_char_t *src  = text.getCharArray();

  for (unsigned int i = 0; i < text.getLength(); i++)
    {
      *dest++ = *src++;
    }

  // Update the size in characters and the size in bytes

  m_stringLength += text.getLength();
}

/**
 * Insert text at the specified character index.
 *
 * @param text The text to insert.
 * @param index The index at which to insert the text.
 */

void CNxString::insert(const CNxString &text, int index)
{
  // Early exit if the string is empty

  if (!hasData())
    {
      CNxString::setText(text);
      return;
    }

  // Early exit if we're just appending

  if (index >= m_stringLength)
    {
      CNxString::append(text);
      return;
    }

  // Get the total size of the string that we need

  int newLength = m_stringLength + text.getLength();
  int newSize   = newLength * sizeof(nxwidget_char_t);

  // Reallocate memory if the existing memory isn't large enough

  if (m_allocatedSize < newSize)
    {
      int allocLength = newLength + m_growAmount;

      // Allocate new string large enough to contain additional data

      FAR nxwidget_char_t *newText = new nxwidget_char_t[allocLength];

      // Copy the start of the existing text to the newly allocated string

      FAR nxwidget_char_t       *dest = newText;
      FAR const nxwidget_char_t *src  = m_text;

      for (int i = 0; i < index; i++)
        {
          *dest++ = *src++;
        }

      // Insert the additional text into the new string

      src = text.getCharArray();

      for (unsigned int i = 0; i < text.getLength(); i++)
        {
          *dest++ = *src++;
        }

      // Copy the end of the existing text the the newly allocated string

      src = &m_text[index];

      for (int i = index; i < m_stringLength; i++)
        {
          *dest++ = *src++;
        }

      m_allocatedSize = allocLength * sizeof(nxwidget_char_t);

      // Delete existing string

      delete[] m_text;

      // Swap pointers

      m_stringLength = newLength;
      m_text         = newText;
    }
  else
    {
      // Existing size is large enough, so make space in string for insert

      FAR nxwidget_char_t       *dest = &m_text[newLength - 1];
      FAR const nxwidget_char_t *src  = &m_text[m_stringLength - 1];

      for (int i = 0; i < m_stringLength - index; i++)
        {
          *dest-- = *src--;
        }

      // Insert the additional text into the new string

      dest = &m_text[index];
      src  = text.getCharArray();

      for (unsigned int i = 0; i < text.getLength(); i++)
        {
          *dest++ = *src++;
        }

      m_stringLength = newLength;
    }
}

/**
 * Remove all characters from the string from the start index onwards.
 *
 * @param startIndex Index to remove from.
 */

void CNxString::remove(const int startIndex)
{
  // Reject if requested operation makes no sense

  if (!hasData() || startIndex >= m_stringLength)
    {
      return;
    }

  // Removing characters from the end of the string is trivial - simply
  // decrease the length

  m_stringLength  = startIndex;
}

/**
 * Remove specified number of characters from the string from the
 * start index onwards.
 *
 * @param startIndex Index to remove from.
 * @param count Number of characters to remove.
 */

void CNxString::remove(const int startIndex, const int count)
{
  // Reject if requested operation makes no sense

  if (!hasData() || startIndex >= m_stringLength)
    {
      return;
    }

  // Don't remove in this way if the count includes the end of the string

  int endIndex = startIndex + count;
  if (endIndex > m_stringLength)
    {
      remove(startIndex);
      return;
    }

  // Copy characters from a point after the area to be deleted into the space created
  // by the deletion

  FAR nxwidget_char_t       *dest = &m_text[startIndex];
  FAR const nxwidget_char_t *src  = &m_text[endIndex];

  for (int i = m_stringLength - endIndex; i > 0; i--)
    {
      *dest++ = *src++;
    }

  // Decrease length

  m_stringLength -= count;

}

/**
 * Get the character at the specified index.  This function is useful
 * for finding the occasional character at an index, but for iterating
 * over strings it is exceptionally slow.  The newStringIterator()
 * method should be used to retrieve an iterator object that can iterate
 * over the string efficiently.
 *
 * @param index The index of the character to retrieve.
 * @return The character at the specified index.
 */

const nxwidget_char_t CNxString::getCharAt(int index) const
{
  return *getCharPointer(index);
}

/**
 * Returns the first index of the specified letter within the string.
 * Will return -1 if the letter is not found.
 *
 * @param letter Letter to find.
 * @return The index of the letter.
 */

const int CNxString::indexOf(nxwidget_char_t letter) const
{
  return indexOf(letter, 0, getLength());
}

/**
 * Returns the first index of the specified letter within the string.
 * Will return -1 if the letter is not found.  Scans through the string
 * from "startIndex" until it has examined all subsequent letters.
 *
 * @param letter Letter to find.
 * @param startIndex The index to start searching from.
 * @return The index of the letter.
 */

const int CNxString::indexOf(nxwidget_char_t letter, int startIndex) const
{
  return indexOf(letter, startIndex, getLength() - startIndex);
}

/**
 * Returns the first index of the specified letter within the string.
 * Will return -1 if the letter is not found.  Scans through the string
 * from "startIndex" until it has examined all letters within the
 * range "count".
 *
 * @param letter Letter to find.
 * @param startIndex The index to start searching from.
 * @param count The number of characters to examine.
 * @return The index of the letter.
 */

const int CNxString::indexOf(nxwidget_char_t letter, int startIndex, int count) const
{
  // Exit if no data available

  if (!hasData())
    {
      return -1;
    }

  int index = -1;
  int charsExamined = 0;

  CStringIterator *iterator = new CStringIterator(this);
  if (!iterator->moveTo(startIndex))
    {
      delete iterator;
      return -1;
    }

  do
    {
      if (iterator->getChar() == letter)
        {
          index = iterator->getIndex();
          break;
        }

      charsExamined++;
    }
  while (iterator->moveToNext() && (charsExamined < count));

  delete iterator;
  return index;
}

/**
 * Returns the last index of the specified letter within the string.
 * Will return -1 if the letter is not found.
 *
 * @param letter Letter to find.
 * @return The index of the letter.
 */

const int CNxString::lastIndexOf(nxwidget_char_t letter) const
{
  return lastIndexOf(letter, getLength() - 1, getLength());
}

/**
 * Returns the last index of the specified letter within the string.
 * Will return -1 if the letter is not found.  Scans through the string
 * backwards from "startIndex" until it has examined all preceding
 * letters within the string.
 *
 * @param letter Letter to find.
 * @param startIndex The index to start searching from.
 * @return The index of the letter.
 */

const int CNxString::lastIndexOf(nxwidget_char_t letter, int startIndex) const
{
  return lastIndexOf(letter, startIndex, getLength() - (getLength() - startIndex));
}

/**
 * Returns the last index of the specified letter within the string.
 * Will return -1 if the letter is not found.  Scans through the string
 * backwards from "startIndex" until it has examined all letters within
 * the range "count".
 * @param letter Letter to find.
 * @param startIndex The index to start searching from.
 * @param count The number of characters to examine.
 * @return The index of the letter.
 */

const int CNxString::lastIndexOf(nxwidget_char_t letter, int startIndex, int count) const
{
  // Exit if no data available

  if (!hasData())
    {
      return -1;
    }

  int index = -1;
  int charsExamined = 0;

  CStringIterator *iterator = new CStringIterator(this);
  if (!iterator->moveTo(startIndex))
    {
      delete iterator;
      return -1;
    }

  do
    {
      if (iterator->getChar() == letter)
        {
          index = iterator->getIndex();
          break;
        }

      charsExamined++;
    }
  while (iterator->moveToPrevious() && (charsExamined <= count));

  delete iterator;
  return index;
}

/**
 * Get a substring from this string.  It is the responsibility of the
 * caller to delete the substring when it is no longer required.
 *
 * @param startIndex The starting point of the substring.
 * @return A pointer to a new CNxString object containing the
 * substring.
 */

CNxString* CNxString::subString(int startIndex) const
{
  return subString(startIndex, getLength() - startIndex);
}

/**
 * Get a substring from this string.  It is the responsibility of the
 * caller to delete the substring when it is no longer required.
 *
 * @param startIndex The starting point of the substring.
 * @param length The length of the substring.
 * @return A pointer to a new CNxString object containing the
 * substring.
*/

CNxString *CNxString::subString(int startIndex, int length) const
{
  CNxString *newString = new CNxString();
  CStringIterator *iterator = new CStringIterator(this);

  if (!iterator->moveTo(startIndex))
    {
      delete iterator;
      return (CNxString *)0;
    }

  // Build up the string character by character.  We could do a memcpy
  // here and improve performance.

  int count = 0;
  while (count < length)
    {
      newString->append(iterator->getChar());
      iterator->moveToNext();
      count++;
    }

  delete iterator;
  return newString;
}

/**
 * Overloaded assignment operator.  Copies the data within the argument
 * string to this string.
 *
 * @param string The string to copy.
 * @return This string.
 */

CNxString& CNxString::operator=(const CNxString &string)
{
  if (&string != this)
    {
      setText(string);
    }

  return *this;
}

/**
 * Overloaded assignment operator.  Copies the data within the argument
 * char array to this string.
 *
 * @param string The string to copy.
 * @return This string.
 */

CNxString& CNxString::operator=(FAR const char *string)
{
  setText(string);
  return *this;
}

/**
 * Overloaded assignment operator.  Copies the data from the argument
 * char to this string.
 *
 * @param letter The char to copy.
 * @return This string.
 */

CNxString& CNxString::operator=(const nxwidget_char_t letter)
{
  setText(letter);
  return *this;
}

/**
 * Compares this string to the argument.
 *
 * @param string String to compare to.
 * @return Zero if both strings are equal.  A value greater than zero
 * indicates that this string is greater than the argument string.  A
 * value less than zero indicates the opposite.  Note that the return
 * value indicates the *byte* that does not match, not the *character*.
 */

int CNxString::compareTo(const CNxString &string) const
{
  return memcmp((FAR const char*)m_text,
                (FAR const char*)string.getCharArray(),
                getLength());
}

int CNxString::compareTo(FAR const char *string) const
{
  return memcmp((FAR const char*)m_text,
                (FAR const char*)string,
                getLength());
}

/**
 * Allocate memory for the string.
 *
 * @param chars Number nxwidget_char_t size characters to allocate.
 * @param preserve If true, the data in the existing memory will be
 * preserved if new memory must be allocated
 */

void CNxString::allocateMemory(int nChars, bool preserve)
{
  // This is the size of the allocation that we need

  int nBytesNeeded = nChars * sizeof(nxwidget_char_t);

  // Do we already have enough memory allocated to contain this new size?
  // If so, we can avoid deallocating and allocating new memory by re-using the old

  if (nBytesNeeded > m_allocatedSize)
    {
      // Not enough space in existing memory; allocate new memory

      int allocChars = nChars + m_growAmount;
      nxwidget_char_t *newText = new nxwidget_char_t[allocChars];

      // Free old memory if necessary

      if (m_text != NULL)
        {
          // Preserve existing data if required

          if (preserve)
            {
              memcpy(newText, m_text, sizeof(nxwidget_char_t) * m_stringLength);
            }

          delete[] m_text;
        }

      // Set pointer to new memory

      m_text = newText;

      // Remember how much memory we've allocated.

      m_allocatedSize = allocChars * sizeof(nxwidget_char_t);
    }
}

/**
 * Return a pointer to the specified characters.
 *
 * @param index Index of the character to retrieve.
 */

FAR nxwidget_char_t *CNxString::getCharPointer(const int index) const
{
  // Early exit if the string is empty

  if (!hasData())
    {
      return (FAR nxwidget_char_t*)NULL;
    }

  // Early exit if the index is greater than the length of the string

  if (index >= m_stringLength)
    {
      return (FAR nxwidget_char_t*)NULL;
    }

  return &m_text[index];
}

CNxString CNxString::format(const char *fmt, ...)
{
  va_list args;
  va_start(args, fmt);
  size_t len = std::vsnprintf((FAR char *)0, 0, fmt, args) + 1;
  va_end(args);

  va_start(args, fmt);
  CNxString result;
  result.allocateMemory(len, false);
  char *buf = reinterpret_cast<char*>(result.m_text);
  vsnprintf(buf, len, fmt, args);
  va_end(args);

  if (sizeof(nxwidget_char_t) > sizeof(char))
    {
      // Expand the string to full width, beginning from the last
      // character so that we don't overwrite characters before
      // we have converted them.

      for (int i = len - 1; i >= 0; i--)
        {
          result.m_text[i] = (nxwidget_char_t)buf[i];
        }
    }

  result.m_stringLength = len - 1;
  return result;
}
