// Copyright (c) 2003 David Muse
// See the COPYING file for more information

#include <rudiments/charstring.h>

#include <rudiments/rawbuffer.h>
#include <rudiments/character.h>

#include <stdio.h>

// for strtold
#ifndef __USE_GNU
	#define __USE_GNU
#endif
#ifndef __USE_ISOC9X
	#define __USE_ISOC9X
#endif
#include <stdlib.h>

#include <string.h>
#ifdef RUDIMENTS_HAVE_STRINGS_H
	#include <strings.h>
#endif

#ifdef RUDIMENTS_NAMESPACE
namespace rudiments {
#endif

const char *charstring::findLast(const char *haystack, const char *needle) {

	if (!haystack || !needle) {
		return NULL;
	}

	size_t	haystacklen=length(haystack);
	size_t	needlelen=length(needle);
	if (needlelen>haystacklen) {
		return NULL;
	}
	
	const char	*ptr=haystack+haystacklen-needlelen;
	while (ptr>haystack) {
		if (!compare(ptr,needle,needlelen)) {
			return ptr;
		}
		ptr--;
	}
	return NULL;
}

void charstring::upper(char *str) {

	if (!str) {
		return;
	}

	for (int i=0; str[i]; i++) {
		if (str[i]>='a' && str[i]<='z') {
			str[i]=str[i]-32;
		}
	}
}

void charstring::lower(char *str) {

	if (!str) {
		return;
	}

	for (int i=0; str[i]; i++) {
		if (str[i]>='A' && str[i]<='Z') {
			str[i]=str[i]+32;
		}
	}
}

void charstring::rightTrim(char *str, char character) {

	if (str && str[0]) {

		int	i=0;

		// advance to the last character in the string
		while (str[i]) {
			i++;
		}
		i--;

		// back up to the first instance of the character to trim
		while (str[i]==character) { 
			i--;
		}
		i++;

		// terminate the string there
		str[i]='\0';
	}
}

void charstring::leftTrim(char *str, char character) {

	if (str && str[0]) {

		int	i=0;
		int	j=0;

		// advance past all of the characters we want to replace
		while (str[i]==character) {
			i++;
		}
	
		// swap chars to front of string
		while (str[i]) {
			str[j]=str[i];
			j++;
			i++;
		}
		// store a null to the new end of string
		str[j]='\0';
	}
}

void charstring::strip(char *str, char character) {

	if (!str) {
		return;
	}

	int	index=0;
	int	total=0;

	while (str[index]) {
		if (str[index]==character) {
			total++;
		} else {
			if (total) {
				str[index-total]=str[index];
			}
		}
		index++;
	}
	str[index-total]='\0';
}

void charstring::strip(char *str1, const char *str2) {

	if (!str1 || !str2) {
		return;
	}

	int	str2len=length(str2);
	int	index=0;
	int	total=0;

	while (str1[index]) {
		if (!strncmp(str1+index,str2,str2len)) {
			total=total+str2len;
			index=index+str2len;
		} else {
			if (total) {
				str1[index-total]=str1[index];
			}
			index++;
		}
	}
	str1[index-total]='\0';
}

bool charstring::isInteger(const char *str) {

	if (!str) {
		return false;
	}

	for (const char *ptr=str; *ptr; ptr++) {
		if (((*ptr>'9' || *ptr<'0') && *ptr!='-') || 
			(ptr>str && *ptr=='-')) {
			return false;
		}
	}
	return true;
}

bool charstring::isInteger(const char *str, int size) {

	if (!str) {
		return false;
	}

	const char	*ptr=str;
	for (int index=0; index<size; index++) {
		if (((*ptr>'9' || *ptr<'0') && *ptr!='-') || 
			(ptr>str && *ptr=='-')) {
			return false;
		}
		ptr++;
	}
	return true;
}

bool charstring::isNumber(const char *str) {

	if (!str) {
		return false;
	}

	int	decimal=0;
	for (const char *ptr=str; *ptr; ptr++) {
		if (((*ptr>'9' || *ptr<'0') && *ptr!='-' && *ptr!='.') || 
			(ptr>str && *ptr=='-') || (decimal && *ptr=='.')) {
			return false;
		}
		if (*ptr=='.') {
			decimal=1;
		}
	}
	return true;
}

bool charstring::isNumber(const char *str, int size) {

	if (!str) {
		return false;
	}

	const char	*ptr=str;
	int		decimal=0;
	for (int index=0; index<size; index++) {
		if (((*ptr>'9' || *ptr<'0') && *ptr!='-' && *ptr!='.') || 
			(ptr>str && *ptr=='-') || (decimal && *ptr=='.')) {
			return false;
		}
		if (*ptr=='.') {
			decimal=1;
		}
		ptr++;
	}
	return true;
}

char *charstring::httpEscape(const char *input) {

	if (!input) {
		return NULL;
	}

	size_t		outputlen=length(input)*3+1;
	char		*output=new char[outputlen];
	char		*outptr=output;
	const char	*ptr=input;
	
	while (*ptr) {
		if (*ptr==' ') {
			(*outptr)='+';
		} else if ((*ptr>='a' && *ptr<='z') || 
				(*ptr>='A' && *ptr<='Z') ||
				(*ptr>='0' && *ptr<='9')) {
			(*outptr)=*ptr;
		} else {
			snprintf(outptr,outputlen,"%c%02X",'%',*ptr);
			outptr=outptr+2;
			outputlen=outputlen-2;
		}
		outptr++;
		outputlen--;
		ptr++;
	}
	(*outptr)='\0';

	return output;
}

char *charstring::escape(const char *input, const char *characters) {
	char		*output;
	uint64_t	outputsize;
	escape(input,charstring::length(input),
			&output,&outputsize,characters);
	return output;
}

void charstring::escape(const char *input, uint64_t inputsize,
				char **output, uint64_t *outputsize,
						const char *characters) {

	(*output)=NULL;
	(*outputsize)=0;

	if (!input) {
		return;
	}

	for (uint16_t pass=0; pass<2; pass++) {

		uint64_t	outputindex=0;
		for (uint64_t inputindex=0;
				inputindex<inputsize;
				inputindex++) {
			if (charstring::contains(characters,
						input[inputindex]) ||
					input[inputindex]=='\\') {
				if (pass==0) {
					(*outputsize)++;
				} else {
					(*output)[outputindex]='\\';
					outputindex++;
				}
			}
			if (pass==0) {
				(*outputsize)++;
			} else {
				(*output)[outputindex]=input[inputindex];
			}
			outputindex++;
		}
		if (pass==0) {
			(*output)=new char[(*outputsize)+1];
			(*output)[(*outputsize)]='\0';
		}
	}
}

char *charstring::unescape(const char *input) {
	char		*output;
	uint64_t	outputsize;
	unescape(input,charstring::length(input),&output,&outputsize);
	return output;
}

void charstring::unescape(const char *input, uint64_t inputsize,
				char **output, uint64_t *outputsize) {

	(*output)=NULL;
	(*outputsize)=0;

	if (!input) {
		return;
	}

	for (uint16_t pass=0; pass<2; pass++) {

		bool		escaped=false;
		uint64_t	outputindex=0;
		for (uint64_t inputindex=0;
				inputindex<inputsize;
				inputindex++) {
			if (!escaped && input[inputindex]=='\\') {
				escaped=true;
				continue;
			}
			if (pass==0) {
				(*outputsize)++;
			} else {
				(*output)[outputindex]=input[inputindex];
			}
			outputindex++;
			escaped=false;
		}
		if (pass==0) {
			(*output)=new char[(*outputsize)+1];
			(*output)[(*outputsize)]='\0';
		}
	}
}

void charstring::leftJustify(char *str, int length) {

	if (!str) {
		return;
	}

	// count leading spaces
	int	spaces=countLeadingSpaces(str,length);

	// replace characters
	int	index;
	int	stop=length-spaces;
	for (index=0; index<stop; index++) {
		str[index]=str[index+spaces];
	}

	// right-pad with spaces
	for (; index<length; index++) {
		str[index]=' ';
	}
}


void charstring::rightPad(char *str, int lngth, char padchar, bool fill) {

	if (!str) {
		return;
	}
	
	if (fill) {
		int valuelength = charstring::length(str);
		for(; valuelength<lngth; valuelength++){
			str[valuelength]=' ';
		}
		str[valuelength]='\0';
	}

	// count trailing spaces
	int	spaces=countTrailingSpaces(str,lngth);
	if (spaces==0){
		return;
	}

	// replace characters
	int	index;
	int	stop=spaces-1;
	for (index=lngth-1; index>stop; index--) {
		str[index]=str[index-spaces];
	}

	// right-pad with spaces
	for (; index>-1; index--) {
		str[index]=padchar;
	}
}

void charstring::rightJustify(char *str, int length) {

	rightPad(str,length,' ',false);
}

void charstring::center(char *str, int length) {

	if (!str) {
		return;
	}

	int	leadingspaces=countLeadingSpaces(str,length);
	int	trailingspaces=countTrailingSpaces(str,length);

	int	leftpad=(leadingspaces+trailingspaces)/2;

	if (leftpad>leadingspaces) {
		// shift everything right
		int	difference=leftpad-leadingspaces;
		int	index;
		for (index=length-1; index>difference-1; index--) {
			str[index]=str[index-difference];
		}
		for (; index>-1; index--) {
			str[index]=' ';
		}
	} else if (leftpad<leadingspaces) {
		// shift everything left
		int	difference=leadingspaces-leftpad;
		int	index;
		for (index=0; index<length-difference; index++) {
			str[index]=str[index+difference];
		}
		for (; index<length; index++) {
			str[index]=' ';
		}
	}
}

int charstring::countLeadingSpaces(const char *str, int length) {

	if (!str) {
		return 0;
	}

	int	leadingspaces=0;
	for (int index=0; str[index]==' ' && index<length; index++) {
		leadingspaces++;
	}
	return leadingspaces;
}

int charstring::countTrailingSpaces(const char *str, int length) {

	if (!str) {
		return 0;
	}

	int	trailingspaces=0;
	for (int index=length-1; str[index]==' ' && index>-1; index--) {
		trailingspaces++;
	}
	return trailingspaces;
}

char *charstring::parseNumber(int16_t number) {
	return parseNumber(number,1);
}

char *charstring::parseNumber(int16_t number,
				unsigned short zeropadding) {
	int	len=integerLength(number);
	int	strlength=((zeropadding>len)?zeropadding:len)+1;
	char	*str=new char[strlength];
	snprintf(str,strlength,"%0*hd",zeropadding,number);
	return str;
}

char *charstring::parseNumber(uint16_t number) {
	return parseNumber(number,1);
}

char *charstring::parseNumber(uint16_t number,
				unsigned short zeropadding) {
	int	len=integerLength(number);
	int	strlength=((zeropadding>len)?zeropadding:len)+1;
	char	*str=new char[strlength];
	int	strindex=strlength-1;
	str[strindex--]='\0';
	while (number) {
		str[strindex--]='0'+number%10;
		number/=10;
	}
	while (strindex>-1) {
		str[strindex--]='0';
	}
	return str;
}

char *charstring::parseNumber(int32_t number) {
	return parseNumber(number,1);
}

char *charstring::parseNumber(int32_t number,
				unsigned short zeropadding) {
	int	len=integerLength(number);
	int	strlength=((zeropadding>len)?zeropadding:len)+1;
	char	*str=new char[strlength];
	snprintf(str,strlength,"%0*d",zeropadding,number);
	return str;
}

char *charstring::parseNumber(uint32_t number) {
	return parseNumber(number,1);
}

char *charstring::parseNumber(uint32_t number,
				unsigned short zeropadding) {
	int	len=integerLength(number);
	int	strlength=((zeropadding>len)?zeropadding:len)+1;
	char	*str=new char[strlength];
	int	strindex=strlength-1;
	str[strindex--]='\0';
	while (number) {
		str[strindex--]='0'+number%10;
		number/=10;
	}
	while (strindex>-1) {
		str[strindex--]='0';
	}
	return str;
}

char *charstring::parseNumber(int64_t number) {
	return parseNumber(number,1);
}

char *charstring::parseNumber(int64_t number,
				unsigned short zeropadding) {
	int	len=integerLength(number);
	int	strlength=((zeropadding>len)?zeropadding:len)+1;
	char	*str=new char[strlength];
	snprintf(str,strlength,"%0*lld",zeropadding,(long long)number);
	return str;
}

char *charstring::parseNumber(uint64_t number) {
	return parseNumber(number,1);
}

char *charstring::parseNumber(uint64_t number,
				unsigned short zeropadding) {
	int	len=integerLength(number);
	int	strlength=((zeropadding>len)?zeropadding:len)+1;
	char	*str=new char[strlength];
	int	strindex=strlength-1;
	str[strindex--]='\0';
	while (number) {
		str[strindex--]='0'+number%10;
		number/=10;
	}
	while (strindex>-1) {
		str[strindex--]='0';
	}
	return str;
}

char *charstring::parseNumber(float number) {
	// FIXME: use (q)(e|f|g)cvt(_r)?
	char	*str=new char[22];
	snprintf(str,22,"%f",number);
	return str;
}

char *charstring::parseNumber(float number,
				unsigned short scale) {
	// FIXME: use (q)(e|f|g)cvt(_r)?
	char	*str=new char[22];
	snprintf(str,22,"%.*f",scale,number);
	return str;
}

char *charstring::parseNumber(float number,
				unsigned short precision,
				unsigned short scale) {
	// FIXME: use (e|f|g)cvt(_r)?
	size_t	strlength=precision+3;
	char	*str=new char[strlength];
	snprintf(str,strlength,"%*.*f",precision,scale,number);
	return str;
}

char *charstring::parseNumber(double number) {
	// FIXME: use (q)(e|f|g)cvt(_r)?
	char	*str=new char[22];
	snprintf(str,22,"%f",number);
	return str;
}

char *charstring::parseNumber(double number,
				unsigned short scale) {
	// FIXME: use (q)(e|f|g)cvt(_r)?
	char	*str=new char[22];
	snprintf(str,22,"%.*f",scale,number);
	return str;
}

char *charstring::parseNumber(double number,
				unsigned short precision,
				unsigned short scale) {
	// FIXME: use (e|f|g)cvt(_r)?
	size_t	strlength=precision+3;
	char	*str=new char[strlength];
	snprintf(str,strlength,"%*.*f",precision,scale,number);
	return str;
}

void charstring::bothTrim(char *string, char character) {
	leftTrim(string,character);
	rightTrim(string,character);
}

int charstring::integerLength(int16_t number) {
	int	length=(number>0)?0:1;
	for (int16_t num=((number>0)?number:(-1*number)); num>0; num=num/10) {
		length++;
	}
	return length;
}

int charstring::integerLength(int32_t number) {
	int	length=(number>0)?0:1;
	for (int32_t num=((number>0)?number:(-1*number)); num>0; num=num/10) {
		length++;
	}
	return length;
}

int charstring::integerLength(int64_t number) {
	int	length=(number>0)?0:1;
	for (int64_t num=((number>0)?number:(-1*number)); num>0; num=num/10) {
		length++;
	}
	return length;
}

int charstring::integerLength(uint16_t number) {
	int	length=(number>0)?0:1;
	for (uint16_t num=number; num>0; num=num/10) {
		length++;
	}
	return length;
}

int charstring::integerLength(uint32_t number) {
	int	length=(number>0)?0:1;
	for (uint32_t num=number; num>0; num=num/10) {
		length++;
	}
	return length;
}

int charstring::integerLength(uint64_t number) {
	int	length=(number>0)?0:1;
	for (uint64_t num=number; num>0; num=num/10) {
		length++;
	}
	return length;
}

size_t charstring::length(const char *string) {
	return (string)?strlen(string):0;
}

size_t charstring::length(const unsigned char *string) {
	// FIXME: I'd think a static_cast would work here...
	return length(reinterpret_cast<const char *>(string));
}

void charstring::zero(char *str, size_t size) {
	rawbuffer::set(str,0,size);
}

char *charstring::append(char *dest, const char *source) {
	return (dest && source)?strcat(dest,source):NULL;
}

char *charstring::append(char *dest, const char *source, size_t size) {
	return (dest && source)?strncat(dest,source,size):NULL;
}

char *charstring::append(char *dest, int32_t number) {
	char	*str=charstring::parseNumber(number);
	char	*retval=append(dest,str);
	delete[] str;
	return retval;
}

char *charstring::append(char *dest, uint32_t number) {
	char	*str=charstring::parseNumber(number);
	char	*retval=append(dest,str);
	delete[] str;
	return retval;
}

char *charstring::append(char *dest, double number) {
	char	*str=charstring::parseNumber(number);
	char	*retval=append(dest,str);
	delete[] str;
	return retval;
}

char *charstring::append(char *dest, double number, unsigned short scale) {
	char	*str=charstring::parseNumber(number,scale);
	char	*retval=append(dest,str);
	delete[] str;
	return retval;
}

char *charstring::append(char *dest, double number, unsigned short precision,
							unsigned short scale) {
	char	*str=charstring::parseNumber(number,precision,scale);
	char	*retval=append(dest,str);
	delete[] str;
	return retval;
}

char *charstring::copy(char *dest, const char *source) {
	return (dest && source)?strcpy(dest,source):NULL;
}

char *charstring::copy(char *dest, const char *source, size_t size) {
	return (dest && source)?strncpy(dest,source,size):NULL;
}

char *charstring::copy(char *dest, size_t location, const char *source) {
	return (dest && source)?strcpy(dest+location,source):NULL;
}

char *charstring::copy(char *dest, size_t location,
					const char *source, size_t size) {
	return (dest && source)?strncpy(dest+location,source,size):NULL;
}

int charstring::compare(const char *str1, const char *str2) {
	// FIXME: use strcoll?
	return (str1 && str2)?strcmp(str1,str2):(str1!=str2);
}

int charstring::compare(const char *str1, const char *str2, size_t size) {
	return (str1 && str2)?strncmp(str1,str2,size):(str1!=str2);
}

int charstring::compareIgnoringCase(const char *str1, const char *str2) {
	return (str1 && str2)?strcasecmp(str1,str2):(str1!=str2);
}

int charstring::compareIgnoringCase(const char *str1,
						const char *str2, size_t size) {
	return (str1 && str2)?strncasecmp(str1,str2,size):(str1!=str2);
}

bool charstring::contains(const char *haystack, const char *needle) {
	return (findFirst(haystack,needle)!=NULL);
}

bool charstring::contains(const char *haystack, char needle) {
	return (findFirst(haystack,needle)!=NULL);
}

const char *charstring::findFirst(const char *haystack, const char *needle) {
	return (haystack && needle)?strstr(haystack,needle):NULL;
}

const char *charstring::findFirst(const char *haystack, char needle) {
	return (haystack)?strchr(haystack,needle):NULL;
}

const char *charstring::findLast(const char *haystack, char needle) {
	return (haystack)?strrchr(haystack,needle):NULL;
}

char *charstring::findFirst(char *haystack, const char *needle) {
	return const_cast<char *>(findFirst(
					const_cast<const char *>(haystack),
					needle));
}

char *charstring::findFirst(char *haystack, char needle) {
	return const_cast<char *>(findFirst(
					const_cast<const char *>(haystack),
					needle));
}

char *charstring::findLast(char *haystack, const char *needle) {
	return const_cast<char *>(findLast(
					const_cast<const char *>(haystack),
					needle));
}

char *charstring::findLast(char *haystack, char needle) {
	return const_cast<char *>(findLast(
					const_cast<const char *>(haystack),
					needle));
}

const char *charstring::findFirstOfSet(const char *haystack, const char *set) {
	return const_cast<char *>(findFirstOfSet(
					const_cast<char *>(haystack),set));
}

char *charstring::findFirstOfSet(char *haystack, const char *set) {
	#ifdef RUDIMENTS_HAVE_STRPBRK
	return (haystack && set)?strpbrk(haystack,set):NULL;
	#else
	if (!haystack || !set) {
		return NULL;
	}
	char	*retval=haystack;
	while (*retval) {
		if (contains(set,*retval)) {
			return retval;
		}
		retval++;
	}
	return NULL;
	#endif
}

size_t charstring::lengthContainingSet(const char *haystack, const char *set) {
	#ifdef RUDIMENTS_HAVE_STRSPN
	return (haystack && set)?strspn(haystack,set):0;
	#else
	if (!haystack || !set) {
		return 0;
	}
	size_t	index=0;
	while (contains(set,haystack[index])) {
		index++;
	}
	return index;
	#endif
}

size_t charstring::lengthNotContainingSet(const char *haystack,
						const char *set) {
	#ifdef RUDIMENTS_HAVE_STRCSPN
	return (haystack && set)?strcspn(haystack,set):0;
	#else
	if (!haystack || !set) {
		return 0;
	}
	size_t	index=0;
	while (!contains(set,haystack[index])) {
		index++;
	}
	return index;
	#endif
}

char *charstring::duplicate(const char *str) {
	if (!str) {
		return NULL;
	}
	return duplicate(str,length(str));
}

void charstring::rightTrim(char *string) {
	rightTrim(string,' ');
}

void charstring::leftTrim(char *string) {
	leftTrim(string,' ');
}

void charstring::bothTrim(char *string) {
	bothTrim(string,' ');
}

int64_t charstring::toInteger(const char *string) {
	return toInteger(string,NULL,10);
}

int64_t charstring::toInteger(const char *string, char **endptr) {
	return toInteger(string,endptr,10);
}

int64_t charstring::toInteger(const char *string, int base) {
	return toInteger(string,NULL,base);
}

int64_t charstring::toInteger(const char *string, char **endptr, int base) {
	#ifdef RUDIMENTS_HAVE_STRTOLL
	return (string)?strtoll(string,endptr,base):0;
	#else
	return (string)?strtol(string,endptr,base):0;
	#endif
}

uint64_t charstring::toUnsignedInteger(const char *string) {
	return toUnsignedInteger(string,NULL,10);
}

uint64_t charstring::toUnsignedInteger(const char *string, char **endptr) {
	return toUnsignedInteger(string,endptr,10);
}

uint64_t charstring::toUnsignedInteger(const char *string, int base) {
	return toUnsignedInteger(string,NULL,base);
}

uint64_t charstring::toUnsignedInteger(const char *string,
						char **endptr, int base) {
	#ifdef RUDIMENTS_HAVE_STRTOULL
	return (string)?strtoull(string,endptr,base):0;
	#else
	return (string)?strtoul(string,endptr,base):0;
	#endif
}

long double charstring::toFloat(const char *string) {
	return toFloat(string,NULL);
}

long double charstring::toFloat(const char *string, char **endptr) {
	#ifdef RUDIMENTS_HAVE_STRTOLD
	return (string)?strtold(string,endptr):0.0;
	#else
	return (string)?static_cast<long double>(strtod(string,endptr)):0.0;
	#endif
}

char *charstring::duplicate(const char *str, size_t length) {
	if (!str) {
		return NULL;
	}
	char	*buffer=new char[length+1];
	copy(buffer,str,length);
	buffer[length]='\0';
	return buffer;
}

void charstring::safePrint(const char *string, int length) {
	const char	*ch=string;
	for (int i=0; i<length; i++) {
		character::safePrint(*ch);
		ch++;
	}
}

void charstring::safePrint(const char *string) {
	safePrint(string,charstring::length(string));
}

void charstring::split(const char *string, const char *delimiter,
				bool collapse,
				char ***list, uint64_t *listlength) {
	split(string,charstring::length(string),
			delimiter,charstring::length(delimiter),
			collapse,list,listlength);
}

void charstring::split(const char *string, ssize_t stringlength,
				const char *delimiter, bool collapse,
				char ***list, uint64_t *listlength) {
	split(string,stringlength,
			delimiter,charstring::length(delimiter),
			collapse,list,listlength);
}

void charstring::split(const char *string, 
				const char *delimiter, ssize_t delimiterlength,
				bool collapse,
				char ***list, uint64_t *listlength) {
	split(string,charstring::length(string),
			delimiter,delimiterlength,
			collapse,list,listlength);
}

void charstring::split(const char *string, ssize_t stringlength,
				const char *delimiter, ssize_t delimiterlength,
				bool collapse,
				char ***list, uint64_t *listlength) {

	// handle degenerate cases
	if (!string || !string[0] || !stringlength ||
			!delimiter || !delimiter[0] || !delimiterlength) {
		(*list)=NULL;
		(*listlength)=0;
		return;
	}

	// 2 passes,
	// 1 to count the number of chunks to split the string into,
	// 1 to actually split the string
	for (int pass=0; pass<2; pass++) {

		// set pointers to the beginning and end of the string
		const char	*start=string;
		const char	*end=string+stringlength;

		// initialize the list length
		(*listlength)=0;

		// loop through the string...
		const char	*current=start;
		for (;;) {

			// if there's not enough room left in the string for
			// another delimiter, then move the current position
			// to the end
			if (end-current<delimiterlength) {
				current=end;
			}

			// if we found a delimiter or ran into the end of
			// the string...
			if (current==end ||
				!charstring::compare(current,delimiter,
							delimiterlength)) {

				// handle cases of multiple delimiters in a row
				if (current!=start || !collapse) {

					// if we're on the second pass...
					if (pass) {

						// make a copy of the string
						// between the last delimiter
						// and here
						(*list)[*listlength]=
							charstring::duplicate(
								start,
								current-start);
					}

					// increment the counter
					(*listlength)++;
				}

				if (current==end) {
					// if we're at the end of the string,
					// then we're done
					break;
				} else {
					// move the current and start pointers
					current=current+delimiterlength;
					start=current;
				}
			} else {
				current++;
			}
		}

		// if we're done with the first pass,
		// create the list and reset the counter
		if (!pass) {
			(*list)=new char *[*listlength];
		}
	}
}

char *charstring::subString(const char *str, size_t start, size_t end) {

	// handle end<start
	if (end<start) {
		size_t	temp=end;
		end=start;
		start=temp;
	}
	return duplicate(str+start,end-start+1);
}

char *charstring::base64Encode(const unsigned char *input) {
	return base64Encode(input,charstring::length(input));
}

 char *charstring::base64Encode(const unsigned char *input,
						uint64_t inputsize) {
	char		*retval=NULL;
	uint64_t	retvalsize=0;
	base64Encode(input,inputsize,&retval,&retvalsize);
	return retval;
}

static char	b64code[]="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
				"abcdefghijklmnopqrstuvwxyz0123456789+/";
static unsigned char	b64dcode[]={
				-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,	// 0-9
				-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,	// 10-19
				-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,	// etc.
				-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
				-1,-1,-1,62,-1,-1,-1,63,52,53,
				54,55,56,57,58,59,60,61,-1,-1,
				-1,0,-1,-1,-1,0,1,2,3,4,
				5,6,7,8,9,10,11,12,13,14,
				15,16,17,18,19,20,21,22,23,24,
				25,-1,-1,-1,-1,-1,-1,26,27,28,
				29,30,31,32,33,34,35,36,37,38,
				39,40,41,42,43,44,45,46,47,48,
				49,50,51,-1,-1,-1,-1,-1};

void charstring::base64Encode(const unsigned char *input, uint64_t inputsize,
				char **output, uint64_t *outputsize) {

	// handle null input
	if (!input) {
		*output=NULL;
		*outputsize=0;
		return;
	}

	// handle 0-length input
	if (!inputsize) {
		*output=charstring::duplicate("");
		*outputsize=0;
		return;
	}

	// handle real input...

	// figure out the output size:
	//
	// let x = inputsize
	// let y = outputsize
	//
	// (I know this sequence is true by
	//  observation of input vs output sizes)
	// x   y
	// -----
	// 9   12 = (x*4/3)+0
	// 8   12 = (x*4/3)+2
	// 7   12 = (x*4/3)+3
	// 6   8  = (x*4/3)+0
	// 5   8  = (x*4/3)+2
	// 4   8  = (x*4/3)+3
	// 3   4  = (x*4/3)+0
	// 2   4  = (x*4/3)+2
	// 1   4  = (x*4/3)+3
	//
	// y=((x*4/3)+z)
	//
	// x   z
	// -----
	// 9   0 = 9-x+0
	// 8   2 = 9-x+1
	// 7   3 = 9-x+1
	// 6   0 = 6-x+0
	// 5   2 = 6-x+1
	// 4   3 = 6-x+1
	// 3   0 = 3-x+0
	// 2   2 = 3-x+1
	// 1   3 = 3-x+1
	//
	// z=(a-x+b)
	//
	// x   a
	// -----
	// 9   9 = ((x+2)/3)*3
	// 8   9 = ((x+2)/3)*3
	// 7   9 = ((x+2)/3)*3
	// 6   6 = ((x+2)/3)*3
	// 5   6 = ((x+2)/3)*3
	// 4   6 = ((x+2)/3)*3
	// 3   3 = ((x+2)/3)*3
	// 2   3 = ((x+2)/3)*3
	// 1   3 = ((x+2)/3)*3
	//
	// a=(((x+2)/3)*3)
	//
	// x   b
	// -----
	// 9   0 = 1-((x+2)%3/2)
	// 8   1 = 1-((x+2)%3/2)
	// 7   1 = 1-((x+2)%3/2)
	// 6   0 = 1-((x+2)%3/2)
	// 5   1 = 1-((x+2)%3/2)
	// 4   1 = 1-((x+2)%3/2)
	// 3   0 = 1-((x+2)%3/2)
	// 2   1 = 1-((x+2)%3/2)
	// 1   1 = 1-((x+2)%3/2)
	//
	// b=(1-((x+2)%3/2))
	//
	// z=(a-x+b)
	// z=(a-x+(1-((x+2)%3/2)))
	// a=(((x+2)/3)*3)
	// z=((((x+2)/3)*3)-x+(1-((x+2)%3/2)))
	// y=((x*4/3)+z)
	// y=((x*4/3)+((((x+2)/3)*3)-x+(1-((x+2)%3/2))))
	//
	// yay!
	*outputsize=((inputsize*4/3)+((((inputsize+2)/3)*3)-
				inputsize+(1-((inputsize+2)%3/2))));
	*output=new char [(*outputsize)+1];

	uint64_t	outputindex=0;
	unsigned char	data[3];
	uint64_t	bytesremaining=0;
	for (uint64_t inputindex=0;
			inputindex<inputsize;
			inputindex=inputindex+3) {

		bytesremaining=inputsize-inputindex;
		if (bytesremaining>=3) {
			rawbuffer::copy(data,input+inputindex,3);
		} else {
			rawbuffer::copy(data,input+inputindex,bytesremaining);
			rawbuffer::zero(data+bytesremaining,3-(bytesremaining));
		}

		(*output)[outputindex++]=b64code[data[0]>>2];
		(*output)[outputindex++]=b64code[(data[0]<<4|data[1]>>4)&0x3F];
		(*output)[outputindex++]=b64code[(data[1]<<2|data[2]>>6)&0x3F];
		(*output)[outputindex++]=b64code[data[2]&0x3F];
	}
	(*output)[outputindex]='\0';

	// convert any excess A's to ='s
	uint64_t	excess=3-bytesremaining;
	for (uint64_t i=0; i<excess; i++) {
		if ((*output)[--outputindex]=='A') {
			(*output)[outputindex]='=';
		}
	}
}

unsigned char *charstring::base64Decode(const char *input) {
	return base64Decode(input,charstring::length(input));
}

unsigned char *charstring::base64Decode(const char *input,
						uint64_t inputsize) {
	unsigned char	*retval=NULL;
	uint64_t	retvalsize=0;
	base64Decode(input,inputsize,&retval,&retvalsize);
	return retval;
}

void charstring::base64Decode(const char *input, uint64_t inputsize,
				unsigned char **output, uint64_t *outputsize) {

	// handle null input
	if (!input) {
		*output=NULL;
		*outputsize=0;
		return;
	}

	// handle 0-length input
	if (!inputsize) {
		*output=(unsigned char *)charstring::duplicate("");
		*outputsize=0;
		return;
	}

	// handle real input...
	*outputsize=inputsize*3/4;
	*output=new unsigned char [(*outputsize)+1];
	uint64_t	outputindex=0;
	unsigned char	data[4];
	uint64_t	inputindex=0;
	while (inputindex<inputsize) {

		data[0]=b64dcode[(int)input[inputindex++]];
		data[1]=b64dcode[(int)input[inputindex++]];
		data[2]=b64dcode[(int)input[inputindex++]];
		data[3]=b64dcode[(int)input[inputindex++]];

		(*output)[outputindex++]=data[0]<<2|data[1]>>4;
		(*output)[outputindex++]=(data[1]&0x0F)<<4|data[2]>>2;
		(*output)[outputindex++]=(data[2]&0x03)<<6|data[3];
	}
	(*output)[outputindex]='\0';

	// the output could contain some trailing \0's that are artifacts of
	// the ='s in the encoded data, no big deal if the decoded data is
	// supposed to be a string, but not ok for binary data.  Reduce
	// outputsize accordingly.

	// there can be at most 2 trailing ='s, each equal represents an
	// additional trailing NULL, reduce outputsize accordingly
	(*outputsize)-=(input[--inputindex]=='=')+
				(input[--inputindex]=='=');
}


#ifdef RUDIMENTS_NAMESPACE
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1