// ---------------------------------------------------------------------------------------------------------------------------------
//      _        _
//     | |      (_)
//  ___| |_ _ __ _ _ __   __ _
// / __| __| '__| | '_ \ / _` |
// \__ \ |_| |  | | | | | (_| |
// |___/\__|_|  |_|_| |_|\__, |
//                        __/ |
//                       |___/
//
// Description:
//
//   Character string class
//
// Notes:
//
//   Best viewed with 8-character tabs and (at least) 132 columns
//
// History:
//
//   04/13/2001 by Paul Nettle: Original creation
//
// Restrictions & freedoms pertaining to usage and redistribution of this software:
//
//   This software is 100% free. If you use this software (in part or in whole) you must credit the author. This software may not be
//   re-distributed (in part or in whole) in a modified form without clear documentation on how to obtain a copy of the original
//   work. You may not use this software to directly or indirectly cause harm to others. This software is provided as-is and without
//   warrantee -- Use at your own risk. For more information, visit HTTP://www.FluidStudios.com/
//
// Copyright 2001, Fluid Studios, Inc., all rights reserved.
// ---------------------------------------------------------------------------------------------------------------------------------

#ifndef	_FSTL_STRING
#define _FSTL_STRING

// ---------------------------------------------------------------------------------------------------------------------------------
// Module setup (required includes, macros, etc.)
// ---------------------------------------------------------------------------------------------------------------------------------

#include <cstring>
#include <cstdio>
#include <cstdlib>
#include "common"
#include "util"
#include "array"
#include "list"

FSTL_NAMESPACE_BEGIN

// ---------------------------------------------------------------------------------------------------------------------------------

class	string
{
public:
	// Construction/destruction

inline				string()
				: _length(0), _buffer((char *) 0)
				{
				}

inline				string(const string &str)
				: _length(0), _buffer((char *) 0)
				{
					*this = str;
				}

inline				string(const char *str)
				: _length(0), _buffer((char *) 0)
				{
					resize(strlen(str));
					if (_buffer)
					{
						_buffer[length()] = 0;
						memcpy(_buffer, str, length());
					}
				}

inline	explicit		string(const char value)
				: _length(0), _buffer((char *) 0)
				{
					resize(1);
					_buffer[0] = value;
					_buffer[length()] = 0;
				}

inline	explicit		string(const unsigned char value)
				: _length(0), _buffer((char *) 0)
				{
					resize(1);
					_buffer[0] = value;
					_buffer[length()] = 0;
				}

inline	explicit		string(const short value)
				: _length(0), _buffer((char *) 0)
				{
					char	s[50];
					sprintf(s, "%d", value);
					*this = string(s);
				}

inline	explicit		string(const unsigned short value)
				: _length(0), _buffer((char *) 0)
				{
					char	s[50];
					sprintf(s, "%u", value);
					*this = string(s);
				}

inline	explicit		string(const int value)
				: _length(0), _buffer((char *) 0)
				{
					char	s[50];
					sprintf(s, "%d", value);
					*this = string(s);
				}

inline	explicit		string(const unsigned int value)
				: _length(0), _buffer((char *) 0)
				{
					char	s[50];
					sprintf(s, "%u", value);
					*this = string(s);
				}

inline	explicit		string(const long value)
				: _length(0), _buffer((char *) 0)
				{
					char	s[50];
					sprintf(s, "%d", value);
					*this = string(s);
				}

inline	explicit		string(const unsigned long value)
				: _length(0), _buffer((char *) 0)
				{
					char	s[50];
					sprintf(s, "%u", value);
					*this = string(s);
				}

inline	explicit		string(const float value)
				: _length(0), _buffer((char *) 0)
				{
					char	s[50];
					sprintf(s, "%f", value);
					*this = string(s);
				}

inline	explicit		string(const double value)
				: _length(0), _buffer((char *) 0)
				{
					char	s[50];
					sprintf(s, "%f", value);
					*this = string(s);
				}

inline				~string()
				{
					erase();
				}

	// Casting & conversion

inline	const	char *		asArray() const		{return buffer();}
inline		char		asChar() const		{return (char) asInt();}
inline		unsigned char	asUChar() const		{return (unsigned char) asUInt();}
inline		short		asShort() const		{return (short) asInt();}
inline		unsigned short	asUShort() const	{return (unsigned short) asUInt();}
inline		int		asInt() const		{return atoi(buffer());}
inline		unsigned int	asUInt() const		{return (unsigned int) asInt();}
inline		long		asLong() const		{return atol(_buffer);}
inline		unsigned long	asULong() const		{return (unsigned long) asLong();}
inline		float		asFloat() const		{return (float) asDouble();}
inline		double		asDouble() const	{return atof(buffer());}

	// Operators

inline		string &	operator  =(const string &rhs)
				{
					if (this == &rhs) return *this;
					if (!rhs.length()) return *this;
					resize(rhs.length());
					memcpy(_buffer, rhs._buffer, length());
					_buffer[length()] = 0;
					return *this;
				}

inline		void		operator +=(const string &rhs)
				{
					if (!rhs.length()) return;
					unsigned int	oldLength = length();
					unsigned int	sLength = rhs.length();
					resize(oldLength + sLength);
					memcpy(&_buffer[oldLength], rhs._buffer, sLength);
					_buffer[length()] = 0;
				}

inline		string		operator  +(const string &rhs) const
				{
					string	result(*this);
					result += rhs;
					return result;
				}

inline		string		operator - ()
				{
					if (!_buffer) return *this;

					char	*p0 = _buffer;
					char	*p1 = &_buffer[length()-1];

					while(p0 < p1)
					{
						char	t = *p0;
						*p0 = *p1;
						*p1 = t;
						p0++;
						p1--;
					}

					return *this;
				}

inline				operator *=(const int value)
				{
					*this = *this * value;
				}

inline		string		operator  *(const int value) const
				{
					// Neg numbers are bad

					if (value < 0) return *this;

					// Something to work with...

					string	result;

					// Make sure we're about to DO something

					if (!length() || !value) return result;

					result.resize(length() * value);

					char	*ptr = result._buffer;

					for (int i = 0; i < value; i++, ptr += length())
					{
						memcpy(ptr, _buffer, length());
					}

					result._buffer[result.length()] = 0;
					return result;
				}

inline		void		operator >>=(const int value)
				{
					if (value <= 0) return;
					resize(length() + value);
					memmove(&_buffer[value], _buffer, length()-value);
					memset(_buffer, ' ', value);
					_buffer[length()] = 0;
				}

inline		void		operator <<=(const int value)
				{
					if (value <= 0) return;
					if (!_buffer) return;
					int	count = length() - value;
					if (count > 0) memmove(_buffer, &_buffer[value], count);
					resize(count);
				}

inline		string		operator >>(const int value) const
				{
					string	result(*this);
					if (value <= 0) return result;
					result >>= value;
					return result;
				}

inline		string		operator <<(const int value) const
				{
					string	result(*this);
					if (value <= 0) return result;
					result <<= value;
					return result;
				}

inline		bool		operator ==(const string &str) const
				{
					if (!_buffer || !str._buffer) return _buffer == str._buffer;
					return strcmp(_buffer, str._buffer) == 0;
				}

inline		bool		operator !=(const string &str) const
				{
					if (!_buffer || !str._buffer) return _buffer != str._buffer;
					return strcmp(_buffer, str._buffer) != 0;
				}

inline		bool		operator <=(const string &str) const
				{
					if (!_buffer || !str._buffer) return _buffer == str._buffer;
					return strcmp(_buffer, str._buffer) <= 0;
				}

inline		bool		operator >=(const string &str) const
				{
					if (!_buffer || !str._buffer) return _buffer == str._buffer;
					return strcmp(_buffer, str._buffer) >= 0;
				}

inline		bool		operator  <(const string &str) const
				{
					if (!_buffer || !str._buffer) return _buffer == str._buffer;
					return strcmp(_buffer, str._buffer) < 0;
				}

inline		bool		operator  >(const string &str) const
				{
					if (!_buffer || !str._buffer) return _buffer == str._buffer;
					return strcmp(_buffer, str._buffer) > 0;
				}

inline		char &		operator [](const int index) const
				{
					return _buffer[index];
				}

	// Utilitarian (public)

inline		string		substring(int start, int count = 0x7fffffff) const
				{
					string	result;
					if (start < 0)
					{
						count += start;
						start = 0;
					}
					else if ((unsigned int) start > length()) start = length();

					if (!_buffer || !length() || (unsigned int) start >= length()) return result;
					if ((unsigned int) start + count > length()+1) count = length()+1 - start;

					result.set(&_buffer[start], count);
					return result;
				}

inline		void		erase(int start = 0, int count = 0x7fffffff)
				{
					if (start >= length()) return;
					if (start + count > length()) count = length() - start;

					memmove(&_buffer[start], &_buffer[start+count], length() - count - start);
					resize(length() - count);
				}

inline		int		find(const string &str, int start = 0) const
				{
					return find(str._buffer, start);
				}

inline		int		find(const char *str, int start = 0) const
				{
					if (!_buffer || !str) return -1;
					if (start < 0) start = 0;
					else if ((unsigned int) start > length()) return -1;

					char	*ptr = strstr(&_buffer[start], str);
					if (!ptr) return -1;
					return ptr - _buffer;
				}

inline		int		rfind(const string &str, int start = 0x7fffffff) const
				{
					if (!_buffer || !str._buffer) return -1;
					if (start > (int) length()) start = length();
					// We'll back up by the length of the string since we can't find
					// a string in reverse order wihtout at least str's length at the end
					// of it...
					start -= str.length();

					while(start > 0)
					{
						if (!strncmp(&_buffer[start], str._buffer, str.length())) return start;
						start--;
					}

					return -1;
				}

inline		int		ncfind(const string &str, int start = 0) const
				{
					if (!_buffer || !str._buffer) return -1;
					if (start < 0) start = 0;
					int	len = length() - str.length();
					if (len < 0) return -1;
					if (start > len) return -1;

					while(start <= len)
					{
						if (!strnicmp(&_buffer[start], str._buffer, str.length())) return start;
						start++;
					}

					return -1;
				}

inline		int		ncrfind(const string &str, int start = 0x7fffffff) const
				{
					if (!_buffer || !str._buffer) return -1;
					if (start > (int) length()) start = length();
					// We'll back up by the length of the string since we can't find
					// a string in reverse order wihtout at least str's length at the end
					// of it...
					start -= str.length();

					while(start > 0)
					{
						if (!strnicmp(&_buffer[start], str._buffer, str.length())) return start;
						start--;
					}

					return -1;
				}

inline		int		ncCompare(const string &str) const
				{
					if (!_buffer || !str._buffer) return _buffer == str._buffer ? 0:-1;
					return stricmp(_buffer, str._buffer);
				}

inline		int		ncCompare(const string &str, unsigned int len) const
				{
					if (!_buffer || !str._buffer) return _buffer == str._buffer ? 0:-1;
					if (len > length()) len = length();
					return strnicmp(_buffer, str._buffer, len);
				}

inline		int		findFirstOf(const char *set, int start = 0) const
				{
					if (!_buffer || !set) return -1;
					if (start < 0) start = 0;
					else if (start > (int) length()) return -1;

					return strspn(&_buffer[start], set);
				}

inline		int		findFirstNotOf(const char *set, int start = 0) const
				{
					if (!_buffer || !set) return -1;
					if (start < 0) start = 0;
					else if (start > (int) length()) return -1;

					return strcspn(&_buffer[start], set);
				}

inline		int		findLastOf(const char *set, int start = 0x7fffffff) const
				{
					if (!_buffer || !set) return -1;
					if (start < 0) return -1;
					else if (start >= (int) length()) start = length() - 1;

					const	char	*ptr = &_buffer[start];
					while(ptr >= _buffer)
					{
						const	char *	s = set;
						while(*s)
						{
							if (*ptr != *s)
							{
								// If there was no match at all....
								if (ptr == &_buffer[start]) return -1;
								else return ptr - _buffer + 1;
							}
							--s;
						}
						--ptr;
					}

					// All mached, retrun the beginning of the string

					return 0;
				}

inline		int		findLastNotOf(const char *set, int start = 0x7fffffff) const
				{
					if (!_buffer || !set) return -1;
					if (start < 0) return -1;
					else if (start >= (int) length()) start = length() - 1;

					const	char	*ptr = &_buffer[start];
					while(ptr >= _buffer)
					{
						const	char *	s = set;
						while(*s)
						{
							if (*ptr == *s)
							{
								// If there was an immediate match....
								if (ptr == &_buffer[start]) return -1;
								else return ptr - _buffer + 1;
							}
							--s;
						}
						--ptr;
					}

					// None mached, retrun the beginning of the string

					return 0;
				}

inline		string		findWord(const int wordIndex, const char token = '|') const
				{
					// Searching backwards?

					if (wordIndex < 0) return rfindWord(-wordIndex, token);

					// Our result will go here

					string	result;

					// No buffer, bail

					if (!_buffer || !length()) return result;

					// Find the start of the word

					int	index = wordIndex+1;
					char	*start = _buffer;
					while(--index && start)
					{
						start = strchr(start, token);
						if (start) start++;
					}

					// If the word wasn't in the string, then bail

					if (index || !start) return result;

					// Where does the word end?

					char	*end = strchr(start, token);
					if (!end) end = start + strlen(start);

					// Copy it into the new string

					result.set(start, end-start);

					// Return the result

					return result;
				}
				
inline		string		rfindWord(const int wordIndex, const char token = '|') const
				{
					// Searching forwards?

					if (wordIndex < 0) return findWord(-wordIndex, token);

					// Our result will go here

					string	result;

					// No buffer, bail

					if (!_buffer || !length()) return result;

					// Find the end of the word

					int	index = wordIndex;
					char	*end = &_buffer[length() - 1];
					while(end >= _buffer)
					{
						if (*end == token && !--index) break;
						end--;
					}

					// If the word wasn't in the string, then bail

					if (index) return result;

					// Where does the word start?

					char	*start = end - 1;
					while(start >= _buffer)
					{
						if (*start == token) break;
						start--;
					}
					if (start < _buffer) start = _buffer;

					// Copy it into the new string

					result.set(start, end-start);

					// Return the result

					return result;
				}

inline		void		fill(const char c = ' ')
				{
					if (!_buffer) return;
					memset(_buffer, c, length());
				}

inline		void		trimBeginning(const char *charList = " \t\v\r\n\f")
				{
					// We need SOME input

					if (!charList || !*charList || !_buffer) return;

					// How many characters to trim?

					unsigned int	count = strspn(_buffer, charList);

					// Trim none?

					if (count == 0) return;

					// Trim all?

					if (count == length())
					{
						erase();
						return;
					}

					// Trim some

					memmove(_buffer, &_buffer[count], length() - count);
					resize(length() - count);
				}

inline		void		trimEnd(const char *charList = " \t\v\r\n\f")
				{
					// We need SOME input

					if (!charList || !*charList || !_buffer || !length()) return;

					// Find the last character in the string that is not of set charList

					char	*end = &_buffer[length()-1];
					while(end > _buffer && strchr(charList, *end)) end--;

					// Set the new string length

					resize(end-_buffer);
				}

inline		void		trim(const char *charList = " \t\v\r\n\f")
				{
					trimBeginning(charList);
					trimEnd(charList);
				}

inline		void		toupper()
				{
					if (!_buffer) return;
					char	*ptr = _buffer;
					for (unsigned int i = 0; i < length(); i++, ptr++) *ptr = (char) fstl::toupper(*ptr);
				}

inline		void		tolower()
				{
					if (!_buffer) return;
					char	*ptr = _buffer;
					for (unsigned int i = 0; i < length(); i++, ptr++) *ptr = (char) fstl::tolower(*ptr);
				}

	// Accessors

inline		unsigned int &	length()	{return _length;}
inline	const	unsigned int	length() const	{return _length;}

private:
	// Utilitarian (private)

inline		void		set(const char *str, const unsigned int len)
				{
					resize(len);
					if (!len) return;
					strncpy(_buffer, str, len);
					_buffer[length()] = 0;
				}

inline		void		resize(const unsigned int len)
				{
					// Do we need to resize?

					if (len == length()) return;

					// Are they clearing the buffer?

					if (!len)
					{
						deallocate(_buffer);
						_buffer = static_cast<char *>(0);
						_length = 0;
						return;
					}

					// Resize the buffer

					char	*temp = allocate<char>(len + 1);

					// Move the data over

					if (len > length())
					{
						memcpy(temp, _buffer, length());
						temp[length()] = 0;
					}
					else
					{
						memcpy(temp, _buffer, len);
						temp[len] = 0;
					}

					// Commit the changes

					deallocate(_buffer);
					_buffer = temp;
					length() = len;
				}

inline	const	char *		buffer() const {if (_buffer) return _buffer; return "";}

		// The string

		unsigned int	_length;
		char		*_buffer;
};

typedef	array<string> StringArray;
typedef	list<string> StringList;

FSTL_NAMESPACE_END
#endif // _FSTL_STRING
// ---------------------------------------------------------------------------------------------------------------------------------
// string - End of file
// ---------------------------------------------------------------------------------------------------------------------------------

