/*****
 NAME
	cstring.m - source code for CString class
 VERSION
	$Id$
 CHANGELOG
	$Log$
 */

#include <coconut/cstring.h>
#include <coconut/cmemory.h>
#include <coconut/csystem.h>
#include <coconut/cerror.h>
#include <coconut/ptext.h>
#include <coconut/dconststr.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>

#define	INT(C)		((int) C)
#define	LENGTH(STR)	((STR) ? strlen(STR) : 0)

/* the default tab length
 */
static int	s_default_tab_len = 8 ;

@implementation CString

+ (utf8_char *) duplicate: (const utf8_char *) str
{
	char *	result ;
	jmp_buf	env ;

	result = strdup(str) ;
	if(result == NULL){
		[CSystem getJump: &env] ;
		longjmp(env, (int) no_mem_space_err) ;
	}
	return result ;
}

+ (utf8_char *) duplicate: (const utf8_char *) str length: (u_int) len
{
	char *	result ;
	jmp_buf	env ;

	result = malloc(sizeof(utf8_char) * (len+1)) ;
	if(result == NULL){
		[CSystem getJump: &env] ;
		longjmp(env, (int) no_mem_space_err) ;
	}
	strncpy(result, str, len) ; result[len] = '\0' ;
	return result ;
}

+ free: (utf8_char *) str
{
	g_free(str) ;
	return nil ;
}

#define	IS1STIDENT(C)	(isalpha(C) || (C) == '_')
#define	IS2NDIDENT(C)	(isalpha(C) || isdigit(C) || (C) == '_')
+ (boolean) isIdentifier: (const utf8_char *) str
{
	utf8_char	c ;
	boolean		result ;

	c = *(str++) ; result = TRUE ;
	if(IS1STIDENT(c)){
		while((c = *(str++)) != '\0'){
			if(!IS2NDIDENT(c)){
				result = FALSE ;
				break ;
			}
		}
	} else
		result = FALSE ;
	return result ;
}

+ (u_int) length: (const utf8_char *) str
{
	return LENGTH(str) ;
}

+ (u_int) countSpaces: (const utf8_char *) str tab: (u_int) tablen
{
	const utf8_char *	ptr ;
	utf8_char		c ;
	u_int			len ;
	u_int			divlen ;

	if(tablen == 0) tablen = s_default_tab_len ;
	len = 0 ;
	for(ptr = str ; (c = *ptr) != '\0' ; ptr++){
		if(isspace(c)){
			if(c == '\t'){
				divlen = len / tablen ;
				len = (divlen + 1) * tablen ;
			} else
				len++ ;
		} else
			break ;
	}
	return len ;
}

+ (u_int) stepSpaces: (const utf8_char *) str step: (u_int) step 
    tab: (u_int) tablen
{
	const utf8_char *	ptr ;
	utf8_char		c ;
	u_int			len ;

	if(tablen == 0) tablen = s_default_tab_len ;
	if(step == 0)
		return 0 ;
	for(len=0, ptr=str ; (c = *ptr) != '\0' ; ptr++){
		if(isspace(c)){
			if(c == '\t'){
				len /= tablen ;
				len = (len + 1) * tablen ;
			} else 
				len++ ;
			if(len <= step)
				continue ;
		} 
		break ;
	}
	return ptr - str ; 
}

#define	CASE(BEFORE, AFTER) \
	case BEFORE: result = AFTER ; break ;
+ (utf8_char) decodeEscapeChar: (utf8_char) c
{
	utf8_char	result ;

	/* about these characters, see "The C Programming
	   Language, Second Edition" Japanese, p.46 */
	switch(c){
		CASE('a', '\a') ;
		CASE('b', '\b') ;
		CASE('f', '\f') ;
		CASE('n', '\n') ;
		CASE('r', '\r') ;
		CASE('t', '\t') ;
		CASE('v', '\v') ;
		default: result='\0' ; break ;
	}
	return result ;
}

+ (id <PString>) newString
{
	id <PString>	str ;
	str = [[CString alloc] initStringWithPtr: EMPTY_STR length: 0] ;
	[CSystem checkPtr: str] ;
	return str ;
}

+ (id <PString>) newString: (const utf8_char *) ptr
{
	id <PString>	str ;
	str = [[CString alloc] initStringWithPtr: ptr length: LENGTH(ptr)];
	[CSystem checkPtr: str] ;
	return str ;
}

+ (id <PString>) newString: (const utf8_char *) ptr length: (u_int) len
{
	id <PString>	str ;
	str = [[CString alloc] initStringWithPtr: ptr length: len] ;
	[CSystem checkPtr: str] ;
	return str ;
}

+ (id <PString>) newStringFromBasicStr: (id <PBasicStr>) conststr
{
	id <PString>	str ;
	str = [[CString alloc] initStringWithPtr: 
	  conststr ? [conststr ptr] : NULL]  ;
	[CSystem checkPtr: str] ;
	return str ;
}

+ (u_int) defaultTabLength
{
	return s_default_tab_len ;
}

+ setDefaultTabLength: (u_int) len
{
	s_default_tab_len = len ;
	return nil ;
}

- init
{
	id <PMemory>	mem ;
	mem = [[CMemory alloc] init] ;
	[CSystem checkPtr: mem] ;
	return [self initString: mem str: EMPTY_STR length: 0] ;
}

- initStringWithPtr: (const utf8_char *) str
{
	id <PMemory>	mem ;

	mem = [[CMemory alloc] init] ;
	[CSystem checkPtr: mem] ;
	return [self initString: mem str: str length: LENGTH(str)] ;
}

- initStringWithPtr: (const utf8_char *) str length: (u_int) len
{
	id <PMemory>	mem ;
	mem = [[CMemory alloc] init] ;
	[CSystem checkPtr: mem] ;
	return [self initString: mem str: str length: len] ;
}

- initStringWithPage: (size_t) page
{
	id <PMemory>	mem ;
	mem = [[CMemory alloc] initMemoryWithPage: page] ;
	[CSystem checkPtr: mem] ;
	return [self initString: mem str: EMPTY_STR length: 0] ;
}

- initStringWithPtrAndPage: (const utf8_char *) str page: (size_t) page
{
	id <PMemory>	mem ;
	mem = [[CMemory alloc] initMemoryWithPage: page] ;
	[CSystem checkPtr: mem] ;
	return [self initString: mem str: str length: LENGTH(str)] ;
}

- initStringWithPtrAndPage: (const utf8_char *) str length: (u_int) len
    page: (size_t) page
{
	id <PMemory>	mem ;
	mem = [[CMemory alloc] initMemoryWithPage: page] ;
	[CSystem checkPtr: mem] ;
	return [self initString: mem str: str length: len] ;
}

- initString: (id <PMemory>) memory str: (const utf8_char *) str 
{
	return [self initString: memory str: str length: LENGTH(str)] ;
}

- initString: (id <PMemory>) memory str: (const utf8_char *) str 
    length: (u_int) length 
{
	utf8_char *	newstr ;
	size_t		newsize ;

	this_memory = memory ;
	newstr = [this_memory new: newsize = length+1] ;
	if(str){
		memmove(newstr, str, newsize) ;
	}
	newstr[length] = '\0' ;
	return [super init] ;
}

- (void) dealloc
{
	[this_memory release] ;
	[super dealloc] ;
}

- (const utf8_char *) ptr 
{
	return [this_memory ptr] ;
}

- (u_int) length
{
	size_t len = [this_memory size] ;
	return len > 0 ? len - 1 : 0 ;
}

- (size_t) size
{
	return [this_memory size] ;
}

- (const utf8_char *) setStr: (id <PBasicStr>) str
{
	if(str)
		return [self setPtr: [str ptr] length: [str length]] ;
	else
		return [self setPtr: EMPTY_STR length: 0] ;
}

- (const utf8_char *) setPtr: (const utf8_char *) ptr
{
	return [self setPtr: ptr length: LENGTH(ptr)] ;
}

- (const utf8_char *) setPtr: (const utf8_char *) str length: (u_int) len
{
	utf8_char *	newstr ;
	size_t		size ;
	newstr = [this_memory new: size = len+1] ;
	if(str)
		memmove(newstr, str, size) ;
	newstr[len] = '\0' ;
	return newstr ;
}

- (const utf8_char *) setFormat: (const utf8_char *) format, ...
{
	va_list			args ;
	const utf8_char *	result ;

	va_start(args, format) ;
	  result = [self setFormat: format valist: args] ;
	va_end(args) ;
	return result ;
}

#define	BUFSIZE		512

- (const utf8_char *) setFormat: (const utf8_char *) format 
  valist: (va_list) args
{
	id <PMemory>		mem ;
	size_t			current = BUFSIZE ;
	size_t			realsize ;
	const utf8_char *	result ;

	mem = [[CMemory alloc] init] ;
	[CSystem checkPtr: mem] ;
	[mem new: current] ;
	while(TRUE) {
		realsize = vsnprintf([mem ptr], current, format, args) ;
		if(realsize >= current){
			current = (realsize + BUFSIZE - 1) / BUFSIZE ;
			current *= BUFSIZE ;
			[mem new: current] ;
		} else
			break ;
	} 
	result = [self setPtr: [mem ptr] length: realsize] ;
	[mem release] ;
	return result ;
}

- (boolean) isEmpty
{
	utf8_char *	str ;
	int		ch ;
	for(str = [this_memory ptr] ; (ch = *str) != '\0' ; str++)
		if(!isspace(ch))
			return FALSE ;
	return TRUE ;
}

- (int) compare: (id <PConstStr>) str
{
	const utf8_char * thisstr = [this_memory ptr] ;
	return str ? strcmp(thisstr, [str ptr]) : (int) thisstr ;
}

- (int) comparePtr: (const utf8_char *) src length: (u_int) len 
{
	const utf8_char * thisstr = [this_memory ptr] ;
	return src ? strncmp(thisstr, src, len) : (int) thisstr ;
}

- (int) comparePtr: (const utf8_char *) src
{
	const utf8_char * thisstr = [this_memory ptr] ;
	return src ? strcmp(thisstr, src) : (int) thisstr ;
}

- (int) comparePtrWoCase: (const utf8_char *) src length: (u_int) len
{
	const utf8_char * thisstr = [this_memory ptr] ;
	return src ? strncasecmp(thisstr, src, len) : (int) thisstr ;
}

- (int) comparePtrWoCase: (const utf8_char *) src
{
	const utf8_char * thisstr = [this_memory ptr] ;
	return src ? strcasecmp(thisstr, src) : (int) thisstr ;
}

- toUpper
{
	utf8_char *	p ;
	utf8_char 	c ;
	for(p = [this_memory ptr] ; (c = *p) != '\0' ; p++)
		*p = toupper(c) ;
	return nil ;
}

- toLower
{
	utf8_char *	p ;
	utf8_char 	c ;

	for(p = [this_memory ptr] ; (c = *p) != '\0' ; p++)
		*p = tolower(c) ;
	return nil ;
}

- replaceChar: (utf8_char) src by: (utf8_char) dst
{
	utf8_char 	c ;
	utf8_char *	p ;

	for(p = [this_memory ptr] ; (c = *p) != '\0' ; p++){
		if(c == src) *p = dst ;
	}
	return nil ;
}

- replaceCharByStr: (utf8_char) src by: (const utf8_char *) dst length: (u_int) dstlen 
{
	u_int		buflen ;
	size_t		thissize ;
	utf8_char *	buf ;
	utf8_char *	p ;
	utf8_char *	b ;
	utf8_char	c ;

	if(src == '\0' || dst == NULL)
		return nil ;
	if(dstlen == 0){
		[self removeChar: src] ;
		return nil ;
	}

	buflen = (thissize = [this_memory size]) * dstlen ;
	buf = alloca(buflen+1) ;
	
	p = [this_memory ptr] ; b = buf ;
	for( ; (c = *p) != '\0' ; p++ ){
		if(c == src){
			memmove(b, dst, dstlen) ; b += dstlen ;
		} else {
			*b = c ; b++ ;
		}
	}
	*(b++) = '\0' ;
	return [this_memory copy: buf size: b - buf] ;
}

- replaceStrByStr: (const utf8_char *) src length: (u_int) srclen
    by: (const utf8_char *) dst length: (u_int) dstlen 
{
	u_int		buflen ;
	u_int		thissize ;
	utf8_char *	buf ;
	utf8_char *	p ;
	utf8_char *	b ;

	if(srclen == 0 || src == NULL)
		return nil ;
	if(dstlen == 0 || dst == NULL){
		[self removeStr: src length: srclen] ;
		return nil ;
	}
	thissize = [this_memory size] ;
	if(srclen < dstlen){
		buflen = (thissize + srclen - 1) / srclen ;
		buflen *= dstlen - srclen ;
		buflen += thissize ;
	} else
		buflen = thissize ;
	buf = alloca(buflen + 1) ;
	p = [this_memory ptr] ; b = buf ;
	while(*p != '\0'){	
		if(strncmp(p, src, srclen) == 0){
			memmove(b, dst, dstlen) ;
			p += srclen ; b += dstlen ;
		} else {
			*(b++) = *(p++) ;
		}
	}
	*(b++) = '\0' ;
	return [this_memory copy: buf size: b - buf] ;
}

- expandEscape
{
	utf8_char *		newptr ;
	utf8_char *		orgptr ;
	utf8_char *		ptr ;
	const utf8_char *	result ;
	size_t			size ;
	utf8_char		c ;
	utf8_char		escape ;

	orgptr = [this_memory ptr] ;
	size = [this_memory size] * 2 ;
	ptr = newptr = (utf8_char *) alloca(size+1) ;
	for( ; (c = *orgptr) != '\0' ; orgptr++, ptr++){
		if(c == '\\'){
			c = *(++orgptr) ;
			escape = [CString decodeEscapeChar: c] ;
			if(escape != '\0')
				*(ptr) = escape ;
			else {
				*(ptr++) = '\\' ; *(ptr) = c ;
			}
		} else
			*(ptr) = c ;
	}
	*(ptr) = '\0' ;
	result = [self setPtr: newptr] ;
	g_assert(result != NULL) ;
	return nil ;
}

- removeChar: (utf8_char) targ
{
	utf8_char *	src  ;
	utf8_char *	dst  ;
	utf8_char *	head  ;
	utf8_char	c ;

	src = dst = head = [this_memory ptr] ;
	for( ; (c = *src) != '\0' ; src++)
		if(c != targ)
			*(dst++) = c ;
	*(dst++) = '\0' ;
	[this_memory changeSize: dst - head] ;
	return nil ;
}

- removeStr: (const utf8_char *) str length: (u_int) len
{
	utf8_char *	src  ;
	utf8_char *	dst  ;
	utf8_char *	head  ;
	utf8_char	c ;
	utf8_char	first ;
	u_int		lenm1 ;

	if(str == NULL || len == 0)
		return nil ;
	if((first = *str) == '\0')
		return nil ;
	src = dst = head = [this_memory ptr] ; lenm1 = len - 1 ;
	for( ; (c = *src) != '\0' ; src++){
		if(c == first){
			if(strncmp(src, str, len) == 0){
				src += lenm1 ;
				continue ;
			}
		}
		*(dst++) = c ;
	}
	*(dst++) = '\0' ;
	[this_memory changeSize: dst - head] ;
	return nil ;
}

- removeHeadSpaces
{
	utf8_char * 	first ;
	utf8_char * 	p ;
	int		ch ;
	u_int		diff ;

	first = [this_memory ptr] ;
	for(p=first ; (ch = *p) != '\0' ; p++){
		if(!isspace(ch)){
			break ;
		}
	}
	if((diff = p - first) > 0)
		[self remove: 0 length: diff] ;
	return nil ;
}

- removeTailSpaces
{
	utf8_char * 	first ;
	utf8_char * 	last ;
	utf8_char * 	p ;
	u_int		diff ;
	size_t		length ;

	length = [this_memory size] - 1 ;
	first = [this_memory ptr] ;
	last = &first[length-1] ;
	for(p = last ; first<=p ; p--){
		if(!isspace(INT(*p))){
			break ;
		}
	}
	if((diff = last - p) > 0){
		[self remove: length - diff length: diff] ;
	}
	return nil ;
}

- removeSideSpaces
{
	[self removeHeadSpaces] ;
	return [self removeTailSpaces] ;
}

- removeMultipleSpaces: (boolean) withnewline
{
	size_t			size ;
	utf8_char *		newptr ;
	utf8_char *		np ;
	const utf8_char *	curptr ;
	const utf8_char *	cp ;
	utf8_char		c ;

	size = [this_memory size] ; curptr = [this_memory ptr] ;
	/* size+1 means the str size and last newline */
	newptr = (utf8_char *) alloca(sizeof(utf8_char) * (size+1)) ;

	/* skip first spaces */
	for(cp = curptr ; (c = *cp) != '\0' ; cp++)
		if(!isspace(INT(c)))
			break ;
	/* remove multiple spaces */
	np = newptr ;
	while((c = *cp) != '\0'){
		if(!isspace(INT(c))){
			*(np++) = c ; cp++ ;
		} else {
			*(np++) = ' ' ;
			/* skip following spaces */
			while((c = *(++cp)) != '\0'){
				if(!isspace(INT(c)))
					break ;
			}
		}
	}
	/* remove last space */
	if(newptr < np && isspace(INT(*(np-1))))
		np-- ;
	/* add last newline */
	if(withnewline)
		*(np++) = '\n' ;
	/* copy string */
	[self setPtr: newptr length: np - newptr] ;
	return nil ;
}

- (const utf8_char *) appendStr: (id <PConstStr>) str
{
	return str ? [self appendPtr: [str ptr] length: [str length]] : NULL ;
}

- (const utf8_char *) appendPtr: (const utf8_char *) str
{
	return str ? [self appendPtr: str length: strlen(str)] : NULL ;
}

- (const utf8_char *) appendPtr: (const utf8_char *) str length: (u_int) len
{
	utf8_char *	newstr ;
	size_t		oldsize ;
	size_t		newsize ;

	if(str == NULL || len == 0)
		return [this_memory ptr] ;
	oldsize = [this_memory size] ;
	newstr = [this_memory changeSize: newsize = oldsize + len];
	memmove(&newstr[oldsize-1], str, len) ;
	newstr[newsize-1] = '\0' ;
	return newstr ;
}

- (const utf8_char *) appendChar: (utf8_char) c
{
	return [self appendPtr: &c length: 1] ;
}

- (const utf8_char *) prependStr: (id <PConstStr>) str
{
	size_t	size ;
	if(str == nil)
		return NULL ;
	size = [str length] ; 
	return [this_memory prepend: [str ptr] size: size] ;
}

- (const utf8_char *) prependPtr: (const utf8_char *) str
{
	return str ? [self prependPtr: str length: strlen(str)] : NULL ;
}

- (const utf8_char *) prependPtr: (const utf8_char *) str length: (u_int) len
{
	utf8_char * newstr ;

	newstr = [this_memory prepend: str size: len] ;
	newstr[[this_memory size] - 1] = '\0' ;
	return newstr ;
}

- (const utf8_char *) prependChar: (utf8_char) c
{
	return [this_memory prepend: &c size: sizeof(utf8_char)] ;
}

- (const utf8_char *) insertStr: (u_int) pos str: (id <PConstStr>) str
{
	if(str == nil)
		return NULL ;
	return [this_memory insert: pos src: [str ptr] 
	  size: [str length]] ;
}

- (const utf8_char *) insertPtr: (u_int) pos str:(const utf8_char *) str 
{
	return str ? [self insertPtr: pos str: str length: strlen(str)] : NULL ;
}

- (const utf8_char *) insertPtr: (u_int) pos str:(const utf8_char *) str 
    length:(u_int) len
{
	utf8_char *	newstr ;
	size_t		oldsize ;
	size_t		addsize ;

	oldsize = [this_memory size] ;
	if(str == NULL || len == 0 || oldsize <= pos)
		return [this_memory ptr] ;
	addsize = len ; 
	newstr = [this_memory increaseSize: addsize];
	memmove(&newstr[pos+addsize], &newstr[pos], oldsize - pos);
	memmove(&newstr[pos], str, addsize) ;
	return newstr ;
}

- (const utf8_char *) insertChar: (u_int) pos char:(utf8_char) c
{
	return [self insertPtr: pos str: &c length: 1] ;
}

- indent: (const utf8_char *) spaces
{
	[self removeHeadSpaces] ;
	[self removeTailSpaces] ;
	[self prependPtr: spaces] ;
	return nil ;
}

- changeLength: (u_int) len
{
	size_t		size = len + 1 ;
	utf8_char *	ptr ;

	if(size < [this_memory size]){
		ptr = [this_memory changeSize: size] ;
		ptr[len] = '\0' ;
	}
	return nil ;
}

- (const utf8_char *) remove: (u_int) pos length: (u_int) len 
{
	utf8_char * 	str ;
	size_t		oldsize ;
	u_int		maxlen ;

	/* 012 ... length 3, size 4
	   the position must be : 0 .. 2
	   the length must be   : pos + len <= length */
	oldsize = [this_memory size] ;
	if(oldsize <= pos)
		return NULL ;
	if(len > (maxlen = oldsize - pos - 1))
		len = maxlen ;
	str = [this_memory remove: pos size: len] ;
	str[oldsize - len -1] = '\0' ;
	return str ;
}

- (id <PString>) splitFirstWord
{
	utf8_char *	head ;
	utf8_char *	wordstart ;
	utf8_char *	ptr ;
	utf8_char 	c ;
	id <PString>	newstr ;

	/* skip the initial spaces */
	head = [this_memory ptr] ;
	for(ptr = head ; (c = *ptr) != '\0' ; ptr++)
		if(!isspace(INT(c)))
			break ;
	wordstart = ptr ;
	for( ; (c = *ptr) != '\0' ; ptr++)
		if(isspace(INT(c)))
			break ;
	newstr = [[CString alloc] initStringWithPtr: wordstart 
	  length: ptr - wordstart] ;

	/* skip the rest of spaces */
	for( ; (c = *ptr) != '\0' ; ptr++)
		if(!isspace(INT(c)))
			break ;

	[self remove: 0 length: ptr - head] ;
	return newstr ;
}

- (id <PString>) splitLastWord
{	
	id <PString>	newstr ;
	utf8_char *	resthead ;
	utf8_char *	restlast ;
	utf8_char *	wordhead ;
	utf8_char *	wordlast ;
	u_int		len ;

	resthead = (utf8_char *) [self ptr] ; 
	wordlast = resthead + [self length] - 1 ;

	/* skip last spaces 
	   ex) h e l l o , w o r l d \n
	                           ^word-last */
	for( ; wordlast >= resthead ; wordlast--)
		if(!isspace(INT(*wordlast)))
			break ;

	/* skip non space chars 
	   ex) h e l l o ,   w o r l d \n
	                   ^word-head */
	for(wordhead = wordlast ; wordhead >= resthead ; wordhead--)
		if(isspace(INT(*wordhead)))
			break ;

	/* setup last string
	   ex) h e l l o ,   w o r l d \n
	                   ^head     ^word-last */
	len = wordlast >= wordhead ? wordlast - wordhead : 0 ;
	newstr = [[CString alloc] initStringWithPtr: wordhead + 1 length: len];

	/* skip last spaces of rest string 
	   ex) h e l l o ,   w o r l d \n
	                  ^rest-last */
	for(restlast = wordhead ; restlast >= resthead ; restlast--)
		if(isspace(INT(*restlast)))
			break ;

	len = restlast > resthead ? restlast - resthead : 0 ; 
	[self changeLength: len] ;
	return newstr ;
}

- (id <PString>) duplicate
{
	id <PString>	newstr ;
	id <PMemory>	newmem ;

	newmem = [this_memory duplicate] ;

	newstr = [[CString alloc] initString: newmem str: [this_memory ptr]
	  length: [this_memory size]] ;
	g_return_val_if_fail(newstr != nil, nil) ;
	return newstr ;
}

- (u_int) hashkey
{
	return g_str_hash([this_memory ptr]) ;
}

#define	STREAM	((id <PIndentStream>) stream)
- print: (id) stream
{
	io_status_t	stat ;
	stat = [STREAM putPtr: [this_memory ptr]] ;
	return stat == io_status_normal ? nil : [CError can_not_happen] ;
}

@end

