/*********************************************************************
 * This file is part of the cpplibs suite.
 *
 * Copyright (C) 2001 Topi Mäenpää and Jaakko Viertola
 * All rights reserved.
 *
 * This program is free software. You can redistribute and/or modify
 * it under the terms of the free software licence found in the
 * accompanying file "COPYING". The licence terms must always be
 * redistributed with this source file. The above copyright notice
 * must be reproduced in all modified and unmodified copies of this
 * source file.
 *
 * $Revision: 1.12 $
 *********************************************************************/

#include "String.h"

#include <ctype.h>
#include <strings.h>
#include <values.h>
#include <stdio.h>

#include "Number.h"

using namespace std;

namespace util
{
	SmartPtr<Computable> String::plus(const Computable& c, bool switched) const throw (ComputationException&)
	{
		const String* str = dynamic_cast<const String*>(&c);
		if (str)
			return SmartPtr<Computable>(new String(_str + str->_str));

		const PrimitiveType* type = dynamic_cast<const PrimitiveType*>(&c);
		if (type)
			{
				if (!switched)
					return SmartPtr<Computable>(new String(_str + type->toString()));
				else
					return SmartPtr<Computable>(new String(type->toString() + _str));
			}
		
		throw createException("+",c);
	}

	SmartPtr<Computable> String::multiply(const Computable& c, bool switched) const throw (ComputationException&)
	{
		const Number* num = dynamic_cast<const Number*>(&c);
		if (!num)
			throw createException("*",c);

		int mul = num->intValue();
		string str(_str.size()*mul,0);
		while (mul--)
			str += _str;
		return SmartPtr<Computable>(new String(str));
	}
	
//  	SmartPtr<Computable> String::divide(const Computable& c) const throw (ComputationException&)
//  	{
//  		String* str = dynamic_cast<String*>(&c);
//  		if (!str)
//  			throw createException("/",c);

//  		List<string> parts(tokenize(_str,str->_str));
//  		if (parts.getLength() > 1)
//  			return SmartPtr<Computable>(new String(_str));
//  		else
//  			{
//  				return SmartPtr<Computable>(new List<>(str));
//  			}
//  	}

	string String::fix(const string& str)
	{
		string result(str);
		unsigned int i=0,len=str.size();
		while (i<len && isspace(result[i])) i++;
		if (i==len)
			return "";
		if (i)
			result = result.substr(i,result.size()-i);
		i = result.size();
		while (isspace(result[i-1])) i--;
		if (i < result.size())
			result.resize(i);
		return result;
	}
	
	string String::shrink(const string& str, int count)
	{
		string result;
		bool previousSpace = false;
		for (unsigned int i=0;i<str.size();i++)
			{
				if (isspace(str[i]))
					{
						if (!previousSpace)
							for (int j=count;j--;)
								result += ' ';
						previousSpace = true;
					}
				else
					{
						previousSpace = false;
						result += str[i];
					}
			}
		return result;
	}

	string String::addXMLEntities(const string& str)
	{
		string result;
		for (unsigned int i=0;i<str.size();i++)
			{
				char chr = str[i];
				switch (chr)
					{
					case '<': result += "&lt;"; break;
					case '>': result += "&gt;"; break;
					case '&': result += "&amp;"; break;
					case '"': result += "&quot;"; break;
					case '\'': result += "&apos;"; break;
					default: result += chr; break;
					}
			}
		return result;
	}
	
	string String::removeXMLEntities(const string& str)
	{
		string result;
		unsigned int len = str.size();
		for (unsigned int i=0;i<len;i++)
			{
				char chr = str[i];
				if (chr != '&')
					result += chr;
				else if (i<len-1)
					{
						i++;
						string entity;
						while (i < len && str[i] != ';')
							entity += str[i++];
						if (entity == "lt")
							result += '<';
						else if (entity == "gt")
							result += '>';
						else if (entity == "amp")
							result += '&';
						else if (entity == "quot")
							result += '"';
						else if (entity == "apos")
							result += '\'';
						else if (i < len)
							result += entity + ';';
						else
							result += entity;
					}
				else
					result += '&';
			}
		return result;
	}

	string String::backslashify(const string& str)
	{
		string result;
		for (unsigned int i=0;i<str.size();i++)
			{
				char chr = str[i];
				switch (chr)
					{
					case '\n': result += "\\n"; break;
					case '\r': result += "\\r"; break;
					case '\t': result += "\\t"; break;
					case '\\': result += "\\\\"; break;
					case '\'': result += "\\\'"; break;
					case '\"': result += "\\\""; break;
					default: result += chr; break;
					}
			}
		return result;
	}
	
	string String::unslashify(const string& str)
	{
		string result;
		for (unsigned int i=0;i<str.size();i++)
			{
				char chr = str[i];
				if (chr != '\\')
					result += chr;
				else if (i<str.size()-1)
					{
						chr = str[++i];
						switch (chr)
							{
							case 'n': result += '\n'; break;
							case 'r': result += '\r'; break;
							case 't': result += '\t'; break;
							case '\\': result += '\\'; break;
							case '\'': result += '\''; break;
							case '\"': result += '\"'; break;
							default:
								result += '\\';
								result += chr;
								break;
							}
					}
				else
					result += '\\';
			}
		return result;
	}
	
	string String::replace(const string& str, const string& from, const string& to, int n)
	{
		int cmpLen = from.size();
		if (cmpLen == 0)
			return str;

		char start = from[0];
		string result;

		//Replace all
		if (n<=0)
			n = MAXINT;

		for (unsigned int i=0;i<str.size();i++)
			{
				char chr = str[i];
				if (chr != start || n<=0 || str.substr(i,cmpLen) != from)
					result += chr;
				else
					{
						result += to;
						i += cmpLen-1;
						n--;
					}
			}
		return result;
	}

	string String::reverseReplace(const string& str, const string& from, const string& to, int n)
	{
		int cmpLen = from.size();
		if (cmpLen == 0)
			return str;

		char start = from[cmpLen-1];
		string result;
		string reverseTo(reverse(to));

		//Replace all
		if (n<=0)
			n = MAXINT;

		for (int i=str.size();i--;)
			{
				char chr = str[i];
				if (chr != start || n<=0 || i<cmpLen-1 || str.substr(i+1-cmpLen,cmpLen) != from)
					result += chr;
				else
					{
						result += reverseTo;
						i -= cmpLen-1;
						n--;
					}
			}
		return reverse(result);
	}

	string String::reverse(const string& str)
	{
		string result(str.size(),0);
		for (int i=str.size(),j=0;i--;j++)
			result[j] = str[i];
		return result;
	}

	char* String::getData(const string& str)
	{
		int size = str.size();
		char* result = new char[size+1];
		strncpy(result,str.data(),size);
		result[size] = 0;
		return result;
	}

	string String::substitute(const string& str, Map<string,string>& variables)
	{
		char previous = 0;
		int len = str.size();
		string result;
		for (int i=0;i<len;i++)
			{
				if (str[i] == '$' && previous != '\\' && i<len-1)
					{
						string variableName;
						
						if (str[++i] != '{')
							{
								while (i<len && isalnum(str[i]))
									variableName += str[i++];
							}
						else
							{
								int openCount = 1;
								bool nestedVariables = false;
								i++;
								while (i<len && openCount)
									{
										if (str[i] == '}')
											openCount--;
										else if (str[i] == '{')
											openCount++;
										else if (str[i] == '$')
											nestedVariables = true;
										if (openCount)
											variableName += str[i];
										i++;
									}
								if (nestedVariables)
									variableName = substitute(variableName,variables);
							}

						string* replacement = variables.get(variableName);
						if (replacement)
							result += *replacement;
						i--;
					}
				else
					result += str[i];
			}
		return result;
	}
	
	string String::readInteger(const char* ptr)
	{
		string result;
		if (*ptr == '-')
			result += *(ptr++);
		while (*ptr && isdigit(*ptr))
			{
				result += *ptr;
				ptr++;
			}
		if (!result.compare("-"))
			result = "";
		return result;
	}

	string String::readDouble(const char* ptr, const char decimalSeparator)
	{
		string result;
		bool separatorRead = false, exponentRead = false, numbersRead = false;
		if (*ptr == '-')
			result += *(ptr++);
		int index = 0, stopIndex=0;
		while (*ptr)
			{
				if (isdigit(*ptr))
					{
						numbersRead = true;
						result += *ptr;
					}
				//Decimal separator
				else if (!separatorRead && *ptr == decimalSeparator)
					{
						separatorRead = true;
						numbersRead = false;
						stopIndex = index;
						result += *ptr;
					}
				//Exponent
				else if (!exponentRead && numbersRead && (*ptr == 'e' || *ptr == 'E'))
					{
						exponentRead = separatorRead = true;
						numbersRead = false;
						stopIndex = index;
						result += *ptr;
						//Negative exponent
						if (*(ptr+1) == '-')
							{
								result += '-';
								ptr++;
							}
					}
				else
					break;
				index++;
				ptr++;
			}
		return numbersRead? result : result.substr(0,stopIndex);
	}

	template <> string String::toString(int i)
	{
		char bfr[64];
		sprintf(bfr,"%d",i);
		return string(bfr);
	}

	int String::parseInt(const string& str) { return parse<int>(str); }
	long String::parseLong(const string& str) { return parse<long>(str); }
	double String::parseDouble(const string& str) { return parse<double>(str); }

	template <> string String::toString(long l)
	{
		char bfr[64];
		sprintf(bfr,"%li",l);
		return string(bfr);
	}

	template <> string String::toString(double d)
	{
		char bfr[64];
		sprintf(bfr,"%.14f",d);
		char* ptr = bfr+15;
		while (*(++ptr));
		while (*(--ptr) == '0') *ptr = 0;
		if (*ptr == '.') *ptr = 0;
		return string(bfr);
	}

	template<> int String::parse<int>(const string& str)
	{
		char* bfr = getData(str);
		int result = atoi(bfr);
		delete[] bfr;
		return result;
	}

	template <> long String::parse<long>(const string& str)
	{
		char* bfr = getData(str);
		long result = atol(bfr);
		delete[] bfr;
		return result;
	}

	template <> double String::parse<double>(const string& str)
	{
		char* bfr = getData(str);
		double result = atof(bfr);
		delete[] bfr;
		return result;
	}

	List<string> String::tokenize(const string& msg, string separators, int maxParts)
	{
		List<string> result;
		tokenize(msg,separators,result,maxParts);
		return result;
	}
	
	void String::tokenize(const string& msg, string separatorChars, List<string>& result, int maxParts)
	{
		unsigned startIndex = 0, length = 0;
		bool previousSpace = true, space = true;
		for (unsigned i=0;i<msg.length();i++)
			{
				char c = msg[i];
				space = (separatorChars.find(c) != string::npos);
				if (!previousSpace && space)
					{
						if (maxParts > 0 && result.getLength() >= maxParts-1)
							{
								result += msg.substr(startIndex);
								length = 0;
								break;
							}
						else
							result += msg.substr(startIndex, length);
						length = 0;
					}
				else if (previousSpace && !space)
					{
						startIndex = i;
						length = 1;
					}
				else if (!previousSpace && !space)
					length++;

				previousSpace = space;
			}
		if (length > 0)
			result += msg.substr(startIndex, length);
	}

	string String::join(const List<string>& parts, string glue)
	{
		if (parts.getLength())
			{
				string result(parts[0]);
				for (int i=1;i<parts.getLength();i++)
					result += glue + parts[i];
				return result;
			}
		return "";
	}

	string String::substr(const string& str,int start,int length)
	{
		int len=str.length();
		if (start < 0) start += len;
		if (length < 0)	length += (len-start+1);
		return str.substr(start,length);
	}
}
