/*
 *  Copyright (c) 2008-2009 Cyrille Berger <cberger@cberger.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * either version 2, or (at your option) any later version of the License.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "String.h"

#include <cstdlib>
#include <sstream>
#include <iostream>

#include "SharedPointer.h"

// Can't use Debug.h, since debug.h will use the String class which can lead to interesting reccursion
#ifdef CAUCHY_ENABLE_DEBUG_OUTPUT

#define CAUCHY_ASSERT(assrt) \
  if( not (assrt ) ) \
  { \
    std::cerr << "Assertion failed: " << #assrt << std::endl; \
    abort(); \
  }

#else

#define CAUCHY_ASSERT(assrt)

#endif

using namespace Cauchy;

struct String::Private : public SharedPointerData {
  Private() {}
  Private(const Private& _rhs) : SharedPointerData(), str(_rhs.str)
  {}
  std::string str;
};

String::String() : d(new Private)
{
  d->ref();
}

String::String(char c) : d(new Private)
{
  d->str = c;
  d->ref();
}

String::String(const char* c) : d(new Private)
{
  d->str = c;
  d->ref();
  }

String::String(const std::string& str) : d(new Private)
{
  d->str = str;
  d->ref();
}

CAUCHY_SHARED_DATA(String)

String& String::append(const char* c)
{
  deref();
  d->str += c;
  return *this;
}

String& String::append(const std::string& str)
{
  deref();
  d->str += str;
  return *this;
}

String& String::append(const String& str)
{
  deref();
  d->str += str;
  return *this;
}

String& String::replace(const String& pat, const String& rep)
{
  deref();
  std::size_t p;
  while( ( p = d->str.find(pat)) != std::string::npos )
  {
    d->str.replace(p, pat.length(), rep);
  }
  return *this;
}


String String::number(int i )
{
  std::stringstream ss;
  std::string str;
  ss << i;
  ss >> str;
  return str;
}

String String::number(unsigned int i)
{
  std::stringstream ss;
  std::string str;
  ss << i;
  ss >> str;
  return str;
}

String String::number(float f )
{
  std::stringstream ss;
  std::string str;
  ss << f;
  ss >> str;
  return str;
}

String String::number(double f)
{
  std::stringstream ss;
  std::string str;
  ss << f;
  ss >> str;
  return str;
}

const char* String::c_str() const
{
  return d->str.c_str();
}

int String::toInt() const
{
  return atoi( c_str() );
}

float String::toFloat() const
{
  return float(atof( c_str() ));
}

String String::head(int n) const
{
  return substr(0, n );
}
String String::tail(int n) const
{
  return substr(length() - n, n );
}

String String::toLower() const
{
  const char* c = c_str();
  String res;
  for(unsigned int i = 0; i < length(); ++i)
  {
    res += tolower(c[i]);
  }
  return res;
}


String String::toUpper() const
{
  const char* c = c_str();
  String res;
  for(unsigned int i = 0; i < length(); ++i)
  {
    res += toupper(c[i]);
  }
  return res;
}

String& String::operator=(char c)
{
  deref();
  d->str = c;
  return *this;
}

String& String::operator+=(const String& s)
{
  deref();
  d->str += s.d->str;
  return *this;
}

String String::operator+(const char* _rhs) const
{
  return d->str + _rhs;
}

String String::operator+(const String& _rhs) const
{
  return d->str + _rhs.d->str;
}

bool String::operator==(const char* _rhs) const
{
  return d->str == _rhs;
}

bool String::operator==(const String& _rhs) const
{
  return d->str == _rhs.d->str;
}

bool String::operator!=(const char* _rhs) const
{
  return d->str != _rhs;
}

bool String::operator!=(const String& _rhs) const
{
  return d->str != _rhs.d->str;
}

bool String::operator<(const String& _rhs) const
{
  return d->str < _rhs.d->str;
}

char String::operator[](std::size_t idx) const
{
  CAUCHY_ASSERT(idx < length());
  return d->str[idx];
}

String::operator const std::string& () const
{
  return d->str;
}

std::size_t String::length() const
{
  return d->str.length();
}

String String::substr(std::size_t _pos, std::size_t __n ) const
{
  return d->str.substr(_pos, __n);
}

bool String::isEmpty() const
{
  return d->str.empty();
}

std::vector<String> String::split( const String& _separators , bool _allowEmpty) const
{
  std::list<String> separatorsList;
  for( size_t i = 0; i < _separators.length(); ++i)
  {
    separatorsList.push_back( _separators[ i ] );
  }
  return split( separatorsList, _allowEmpty );
}

std::vector<String> String::split( const std::list<String>& _separators, bool _allowEmpty) const
{
  std::vector<String> result;
  int lastPos = 0;
  for( size_t i = 0; i < length(); ++i )
  {
    std::string::const_reference cc = d->str[i];
    for( std::list<String>::const_iterator it = _separators.begin();
         it != _separators.end(); ++it)
    {
      if( (*it)[0] == cc )
      {
        if( i - lastPos > 0 and (_allowEmpty or (i - lastPos) != 0 ) )
        {
          result.push_back( substr( lastPos, i - lastPos ) );
        }
        lastPos = i + 1;
        break;
      }
    }
  }
  if(_allowEmpty or (length() - lastPos) != 0)
  {
    result.push_back( substr( lastPos, length() - lastPos ) );
  }
  return result;
}

bool String::startWith(const String& _sw) const
{
  if(_sw.length() > length() ) return false;
  return substr(0, _sw.length()) == _sw;
}

bool String::endWith(const String& _sw) const
{
  if(_sw.length() > length() ) return false;
  return substr( length() - _sw.length(), _sw.length() ) == _sw;
}

String String::trimmed() const
{
  int start = 0;
  int end = length() - 1;
  while( (*this)[start] == ' ' ) ++start;
  while( (*this)[end] == ' ' ) --end;
  return substr(start, end - start + 1);
}

String Cauchy::operator+(const char* arg1, const String& arg2)
{
  return arg1 + std::string(arg2);
}

bool Cauchy::operator==(const char* arg1, const String& arg2)
{
  return arg2 == arg1;
}

std::ostream& Cauchy::operator<< (std::ostream& _ostr, const String& string)
{
  _ostr << static_cast<const std::string>(string);
  return _ostr;
}
