// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.


#ifndef PDSLIB_STRING_H
#define PDSLIB_STRING_H

#include <string>


class CSString: public std::string
{
public:
	CSString()
	{
	}

	CSString(const char *s)
	{
		*(std::string *)this=s;
	}

	CSString(const std::string &s)
	{
		*(std::string *)this=s;
	}

	CSString(char c)
	{
		*(std::string *)this=c;
	}

	CSString(int i,const char *fmt="%d")
	{
		char buf[1024];
		sprintf(buf,fmt,i);
		*this=buf;
	}

	CSString(unsigned u,const char *fmt="%u")
	{
		char buf[1024];
		sprintf(buf,fmt,u);
		*this=buf;
	}

	CSString(double d,const char *fmt="%f")
	{
		char buf[1024];
		sprintf(buf,fmt,d);
		*this=buf;
	}

	CSString(const char *s,const char *fmt)
	{
		char buf[1024];
		sprintf(buf,fmt,s);
		*this=buf;
	}

	CSString(const std::string &s,const char *fmt)
	{
		char buf[1024];
		sprintf(buf,fmt,s.c_str());
		*this=buf;
	}

	char operator*()
	{
		if (empty())
			return 0;
		return (*this)[0];
	}

	// return the n right hand most characters of a string
	CSString right(unsigned count) const
	{
		if (count>=size())
			return *this;
		return substr(size()-count);
	}

	// return the string minus the n right hand most characters of a string
	CSString rightCrop(unsigned count) const
	{
		if (count>=size())
			return CSString();
		return substr(0,size()-count);
	}

	// return the n left hand most characters of a string
	CSString left(unsigned count) const
	{
		return substr(0,count);
	}

	// return the string minus the n left hand most characters of a string
	CSString leftCrop(unsigned count) const
	{
		if (count>=size())
			return CSString();
		return substr(count);
	}

	// return sub string up to but not including first instance of given character
	CSString splitTo(char c,bool truncateThis=false)
	{
		unsigned i;
		CSString result;
		for (i=0;i<size() && (*this)[i]!=c;++i)
			result+=(*this)[i];

		// remove the result string from the input string if so desired
		if (truncateThis)
		{
			if (i<size()-1)
				(*this)=substr(i+1);	// +1 to skip the separator character
			else
				clear();
		}

		return result;
	}

	// return sub string up to but not including first instance of given character
	CSString splitTo(const char *s,bool truncateThis=false)
	{
		unsigned i;
		CSString result;
		for (i=0;i<size();++i)
		{
			// perform a quick string compare
			int j;
			for (j=0;s[j]!=0 && s[j]==(&((*this)[i]))[j];++j)
			{
			}
			// if string compare matched then return result so far
			if (s[j]==0)
			{
				// remove the result string from the input string if so desired
				if (truncateThis)
				{
					if (i<size()-1)
						(*this)=substr(i+1);	// +1 to skip the separator character
					else
						clear();
				}

				return result;
			}
			result+=(*this)[i];
		}
		// we didn't find the separator string so we're returning a copy of the whole string
		if (truncateThis)
			clear();
		return result;
	}

	// return sub string from character following first instance of given character on
	CSString splitFrom(char c) const
	{
		CSString result;
		std::string::const_iterator it;
		for (it=begin();it!=end() && *it!=c;++it)
		{}
		if (it!=end())
		{
			++it;
			for (;it!=end();++it)
				result+=*it;
		}
		return result;
	}

	// return sub string from character following first instance of given character on
	CSString splitFrom(const char *s) const
	{
		unsigned int i;
		CSString result;
		for (i=0;i<size();++i)
		{
			// perform a quick string compare
			unsigned int j;
			for (j=0;i+j<size() && s[j]!=0 && s[j]==(*this)[i+j];++j)
			{
			}
			// if string compare matched then build and return a result
			if (s[j]==0)
			{
				result=substr(i+j);
				return result;
			}
		}
		return result;
	}

	// behave like a s strtok() routine, returning the sun string extracted from (and removed from) *this
	CSString strtok(const char *separators)
	{
		unsigned int i;
		CSString result;

		// skip leading junk
		for (i=0;i<size();++i)
		{
			// look for the next character in the 'separator' character list supplied
			unsigned j;
			for (j=0;separators[j] && (*this)[i]!=separators[j];++j)
			{}
			// if not found then we're at end of leading junk
			if (!separators[j])
				break;
		}

		// copy out everything up to the next separator character
		for (;i<size();++i)
		{
			// look for the next character in the 'separator' character list supplied
			unsigned j;
			for (j=0;separators[j] && (*this)[i]!=separators[j];++j)
			{}
			// if not found then we're at end of leading junk
			if (separators[j])
				break;
			result+=(*this)[i];
		}

		// skip trailing junk
		for (;i<size();++i)
		{
			// look for the next character in the 'separator' character list supplied
			unsigned j;
			for (j=0;separators[j] && (*this)[i]!=separators[j];++j)
			{}
			// if not found then we're at end of leading junk
			if (!separators[j])
				break;
		}

		// delete the treated bit from this string
		(*this)=substr(i);

		return result;
	}

	// return first word (blank separated)
	CSString firstWord(bool truncateThis=false)
	{
		CSString result;
		unsigned i=0;
		// skip white space
		for (i=0;i<size() && isWhiteSpace((*this)[i]);++i)
		{}

		if ( ((*this)[i]>='A' && (*this)[i]<='Z') || ((*this)[i]>='a' && (*this)[i]<='z') || 
			 ((*this)[i]>='0' && (*this)[i]<='9') || (*this)[i]=='_')
		{
			// copy out an alpha-numeric string
			for (;i<(*this).size() && 
				( ((*this)[i]>='A' && (*this)[i]<='Z') || ((*this)[i]>='a' && (*this)[i]<='z') || 
				  ((*this)[i]>='0' && (*this)[i]<='9') || (*this)[i]=='_')
				;++i)
				result+=(*this)[i];
		}
		else
		{
			// just take the first character of the input
			result=(*this)[i];
			++i;
		}

		// remove the result string from the input string if so desired
		if (truncateThis)
		{
			if (i<size())
				(*this)=substr(i);
			else
				clear();
		}

		return result;
	}

	CSString firstWordConst() const
	{
		return const_cast<CSString *>(this)->firstWord();
	}

	// return sub string up to but not including first instance of given character
	CSString tailFromFirstWord() const
	{
		CSString hold=*this;
		hold.firstWord(true);
		return hold;
	}

	// count the number of words (or quote delimited sub-strings) in a string
	unsigned countWords() const
	{
		unsigned count=0;
		CSString hold=strip();
		while (!hold.empty())
		{
			hold=hold.tailFromFirstWord().strip();
			++count;
		}
		return count;
	}

	// count the number of words (or quote delimited sub-strings) in a string
	CSString word(unsigned idx) const
	{
		CSString hold=strip();

		for (unsigned count=0;count<idx;++count)
			hold=hold.tailFromFirstWord().strip();

		return hold.firstWord();
	}

	// return first word or quote-encompassed sub-string
	CSString firstWordOrWords(bool truncateThis=false)
	{
		CSString hold=strip();
		if (hold[0]!='\"')
			return firstWord(truncateThis);

		// the string is quote enclosed
		CSString result;
		unsigned i=1; // skip leading quote
		// copy from character following opening quote to char preceding closing quote (or end of string)
		while (i<hold.size() && hold[i]!='\"')
		{
			result+=hold[i];
			++i;
		}

		// remove the result string from the input string if so desired
		if (truncateThis)
		{
			if (i<size()-1)
				(*this)=substr(i+1);	// +1 to skip the closing quote
			else
				clear();
		}

		return result;
	}

	CSString firstWordOrWordsConst() const
	{
		return const_cast<CSString *>(this)->firstWordOrWords();
	}

	// return sub string up to but not including first instance of given character
	CSString tailFromFirstWordOrWords() const
	{
		CSString hold=*this;
		hold.firstWordOrWords(true);
		return hold;
	}

	// count the number of words (or quote delimited sub-strings) in a string
	unsigned countWordOrWords() const
	{
		unsigned count=0;
		CSString hold=strip();
		while (!hold.empty())
		{
			hold=hold.tailFromFirstWordOrWords().strip();
			++count;
		}
		return count;
	}

	// count the number of words (or quote delimited sub-strings) in a string
	CSString wordOrWords(unsigned idx) const
	{
		CSString hold=strip();
		
		for (unsigned count=0;count<idx;++count)
			hold=hold.tailFromFirstWordOrWords().strip();

		return hold.firstWordOrWords();
	}

	// a handy utility routine for knowing if a character is a white space character or not
	static bool isWhiteSpace(char c) { return c==' ' || c=='\t' || c=='\n' || c=='\r' || c==26; }

	// return a copy of the string with leading and trainling spaces rmoved
	CSString strip() const
	{
		CSString result;
		int i,j;
		for (j=size()-1; j>=0 && isWhiteSpace((*this)[j]); --j) {}
		for (i=0;		 i<j  && isWhiteSpace((*this)[i]); ++i) {}
		for (; i<=j; ++i)
			result+=(*this)[i];
		return result;
	}

	// making an upper case copy of a string
	CSString toUpper() const
	{
		CSString result;
		std::string::const_iterator it;
		for (it=begin();it!=end();++it)
		{
			char c=(*it);
			if (c>='a' && c<='z')
				c^=('a'^'A');
			result+=c;
		}
		return result;
	}

	// making a lower case copy of a string
	CSString toLower() const
	{
		CSString result;
		std::string::const_iterator it;
		for (it=begin();it!=end();++it)
		{
			char c=(*it);
			if (c>='A' && c<='Z')
				c^=('a'^'A');
			result+=c;
		}
		return result;
	}

	// replacing all occurences of one string with another
	CSString replace(const char *toFind,const char *replacement) const
	{
		// just bypass the problems that can cause a crash...
		if (toFind==NULL || *toFind==0)
			return *this;

		unsigned i,j;
		CSString result;
		for (i=0;i<size();)
		{
			// string compare toFind against (*this)+i ...
			for (j=0;toFind[j];++j)
				if ((*this)[i+j]!=toFind[j])
					break;
			// if strings were identical then j reffers to ASCIIZ terminator at end of 'toFind'
			if (toFind[j]==0)
			{
				if (replacement!=NULL)
					result+=replacement;
				i+=j;
			}
			else
			{
				result+=(*this)[i];
				++i;
			}
		}
		return result;
	}

	// find index at which a sub-string starts - if sub-string not found then returns size()
	unsigned find(const char *toFind,unsigned startLocation=0) const
	{
		// just bypass the problems that can cause a crash...
		if (toFind==NULL || *toFind==0 || startLocation>size())
			return size();

		unsigned i,j;
		for (i=startLocation;i<size();++i)
		{
			// string compare toFind against (*this)+i ...
			for (j=0;toFind[j];++j)
				if ((*this)[i+j]!=toFind[j])
					break;
			// if strings were identical then we're done
			if (toFind[j]==0)
				return i;
		}
		return i;
	}

	// return true if this contains given sub string
	bool contains(const char *toFind) const
	{
		return find(toFind)!=size();
	}

	// a couple of handy atoi routines...
	template <class C> bool atoi(C& result) const
	{
		result=::atoi(c_str());
		return (result!=0 || *this=="0");
	}
	unsigned atoi() const
	{
		return ::atoi(c_str());
	}

	// a couple of handy atof routines...
	template <class C> bool atof(C& result) const
	{
		result=::atof(c_str());
		return (result!=0 || *this=="0");
	}
	double atof() const
	{
		return ::atof(c_str());
	}

	// case insensitive string compare
	bool operator==(const std::string &other) const
	{
		return stricmp(c_str(),other.c_str())==0;
	}

	// case insesnsitive string compare
	bool operator!=(const std::string &other) const
	{
		return !(*this==other);
	}
};

#endif