## File: String
## String handling functions.

#ifdef BLENDOT
import "print_string.h"
#endif

## Create a new empty string of a certain size.
def tp_string_t(TP, int n) ->tp_obj:
	tp_obj r
	r.type.type_id = TP_STRING
	r.type.magic = TP_STRING_NONE
	r.string.info = (tpd_string*)tp_malloc(tp, sizeof(tpd_string))
	r.string.info->len = n
	r.string.info->s = (char*)tp_malloc(tp, n)
	return tp_track(tp, r)


##def tp_string_atom(TP, const char * v) ->tp_obj:
##	static tpd_string info = {0};
##	tp_obj r
##	r.type.type_id = TP_STRING
##	r.type.magic = TP_STRING_ATOM
##	r.string.info = &info
##	r.string.val = v
##	return r

def tp_string_tiny(const char * v, int len) ->tp_obj:
	tp_obj r
	r.type.type_id = TP_STRING_TINY
	## note magic must be set, malloc/realloc will not always clear all the bytes and set them to zero
	r.type.magic = TP_STRING_NONE
	for i in range(len):
		r.str.val[i] = v[i]
	if len < 12:
		r.str.val[len]='\0'
	return r

def tp_string_tiny(const char * v) ->tp_obj:
	return tp_string_tiny(v, strlen(v))

def tp_string_tiny(const char c) ->tp_obj:
	tp_obj r
	r.type.type_id = TP_STRING_TINY
	## note magic must be set, malloc/realloc will not always clear all the bytes and set them to zero
	r.type.magic = TP_STRING_NONE
	r.str.val[0] = c
	r.str.val[1] = '\0'
	return r

def tp_string_tiny(const char * v, int len, char minihash) ->tp_obj:
	tp_obj r
	r.type.type_id = TP_STRING_TINY
	r.type.magic = TP_STRING_MAGIC_HASH
	for i in range(len):
		r.str.val[i] = v[i]
		if i > 10:
			std::cout << v << std::endl;
			throw "tiny string with minihash must be less than 11 bytes"
	r.str.val[len]='\0'
	## 8bit hash (interned index) is the last byte
	r.str.val[11] = minihash
	return r

static int __num_tiny_string_atoms__ = 0;
static std::unordered_map<std::string,int> __tiny_string_atoms__ = {};

def tp_string_tiny_atom(const char * v) ->tp_obj:
	tp_obj r
	int len = strlen(v)
	r.type.type_id = TP_STRING_TINY
	r.type.magic = TP_STRING_MAGIC_HASH
	for i in range(len):
		r.str.val[i] = v[i]
		if i > 10:
			std::cout << v << std::endl;
			throw "tiny string with minihash must be less than 11 bytes"
	r.str.val[len]='\0'
	if __tiny_string_atoms__.count( std::string(v) )==1:
		r.str.val[11] = __tiny_string_atoms__[std::string(v)]
	else:
		if defined(DEBUG):
			std::cout << "'" << v << "', ## 8bit hash = " << __num_tiny_string_atoms__ << std::endl;
		## 8bit hash (interned index) is the last byte
		r.str.val[11] = __num_tiny_string_atoms__
		__tiny_string_atoms__[std::string(v)] = __num_tiny_string_atoms__
		__num_tiny_string_atoms__ ++
	return r

def tp_string_atom(TP, const char * v) ->tp_obj:
	int n = strlen(v)
	if n <= 12:
		if n <= 10:
			return tp_string_tiny_atom(v)
		else:
			return tp_string_tiny(v, n)
	else:
		tp_obj r
		r.type.type_id = TP_STRING_ATOMIC
		## note magic must be set, malloc/realloc will not always clear all the bytes and set them to zero
		r.type.magic = TP_STRING_NONE
		r.atomstr.val = v
		return r


## Return a untracked string object from external memory.
def tp_string_from_const(TP, const char *s, int n) ->tp_obj:
	#if DEBUG > 2
	  std::cout << "tp_string.cpp tp_string_from_const: " << n << std::endl;
	  std::cout << s << std::endl;
	#endif
	tp_obj r
	if n < 0:
		n = strlen(s)
	r.type.type_id = TP_STRING
	r.type.magic = TP_STRING_EXTERN
	r.string.info = (tpd_string*) tp_malloc(tp, sizeof(tpd_string))
	##r.string.info->base = tp_None
	r.string.info->s = (char*) s
	r.string.info->len = n
	return r


## return a tracked string object from external memory
def tp_string_t_from_const(TP, const char *s, int n) ->tp_obj:
	#ifdef DEBUG
	  std::cout << "tp_string.cpp tp_string_tracked_from_const: " << n << std::endl;
	#endif
	return tp_track(tp, tp_string_from_const(tp, s, n))


## Create a new string which is a copy of some memory.
def tp_string_from_buffer(TP, const char *s, int n) ->tp_obj:
	#ifdef DEBUG
	  std::cout << "tp_string.cpp tp_string_from_buffer: " << n << std::endl;
	#endif
	if n < 0:
		n = strlen(s)
	tp_obj r = tp_string_t(tp, n)
	memcpy(tp_string_getptr(r), s, n)
	return r

## TODO check where and how this is used, maybe deprecate, tp could be NULL - this is bad.
def tp_string_from_stdstring(TP, std::string s) ->tp_obj:
	char *dst = new char[s.size()+1]
	std::strcpy(dst, s.c_str())
	if tp:
		return tp_track(tp, tp_string_from_const(tp, dst, s.size()))
	else:
		return tp_string_from_const(tp, dst, s.size())


def tp_string_getptr(tp_obj s) -> char* :
	if s.type.type_id == TP_STRING_ATOMIC:
		return (char*) s.atomstr.val
	elif s.type.type_id == TP_STRING_TINY:
		## this will not work :(
		##return (char*)&s.str.val
		throw "can not call tp_string_getptr on a tiny string"
	else:
		return s.string.info->s


def tp_string_len(tp_obj s)->int:
	if s.type.type_id == TP_STRING_ATOMIC:
		return strlen(s.atomstr.val)
	elif s.type.type_id == TP_STRING_TINY:
		int n = 0
		for i in range(12):
			if s.str.val[i]=='\0':
				break
			n ++
		return n
	else:
		return s.string.info->len

def tp_string_tiny_len(tp_obj s)->int:
	int n = 0
	for i in range(12):
		if s.str.val[i]=='\0':
			break
		n ++
	return n


## Create a new string which is a substring slice (view) of another STRING.
## the returned object does not allocate new memory. It refers to the same
## memory object to the original string.

def tp_string_view(TP, tp_obj s, int a, int b) ->tp_obj:
	int l = tp_string_len(s)
	a = _tp_max(0,(a<0?l+a:a))
	b = _tp_min(l,(b<0?l+b:b))
	if s.type.type_id==TP_STRING_TINY:
		throw "TODO string view on a tiny string"
	tp_obj r = tp_string_from_const(tp, tp_string_getptr(s) + a, b - a)
	##r.string.info->base = s  ## this was used by gc_follow
	r.type.magic = TP_STRING_VIEW
	return tp_track(tp, r)


def tp_printf(TP, char const *fmt,...) ->tp_obj:
	int l
	tp_obj r
	char *s
	va_list arg
	va_start(arg, fmt)
	l = vsnprintf(NULL, 0, fmt,arg)
	r = tp_string_t(tp, l + 1)
	s = tp_string_getptr(r)
	va_end(arg)
	va_start(arg, fmt)
	vsnprintf(s, l + 1, fmt, arg)
	va_end(arg)
	// printing to the Blendot console is done from tp_echo
	//#ifdef BLENDOT
	//  print_line(s)
	//#endif
	return r


def tp_str_index(tp_obj s, tp_obj k) ->int:
	int i=0
	while (tp_string_len(s) - i) >= tp_string_len(k):
		if memcmp(tp_string_getptr(s) + i, tp_string_getptr(k), tp_string_len(k)) == 0:
			return i
		i += 1
	return -1



def tp_string_cmp(tp_obj a, tp_obj b) ->int:
	if a.type.type_id == TP_STRING_TINY:
		if b.type.type_id != TP_STRING_TINY:
			throw "can only compare a tiny string another other tiny string"
		int na = len(a.str.val)
		int nb = len(b.str.val)
		if na==1 and nb==1:
			return (int)(a.str.val[0] - b.str.val[0])
		elif na < nb:
			return -1
		elif na > nb:
			return 1
		elif na == nb:
			## TODO check how standard python deals with `'foo' < 'bar'`
			if std::string(a) == std::string(b):
				return 0
			return -1
	elif a.type.type_id == TP_STRING and b.type.type_id==TP_STRING_TINY:
		int na = len(a)
		int nb = len(b.str.val)
		if na==1 and nb==1:
			return (int)(tp_string_getptr(a)[0] - b.str.val[0])
		elif na < nb:
			return -1
		elif na > nb:
			return 1
		elif na == nb:
			## TODO check how standard python deals with `'foo' < 'bar'`
			if std::string(a) == std::string(b):
				return 0
			return -1
	else:
		int l = _tp_min(tp_string_len(a), tp_string_len(b))
		int v = memcmp(tp_string_getptr(a), tp_string_getptr(b), l)
		if v == 0:
			v = tp_string_len(a) - tp_string_len(b)
		return v


def tp_string_add(TP, tp_obj a, tp_obj b) ->tp_obj:
	int al = tp_string_len(a), bl = tp_string_len(b)
	tp_obj r = tp_string_t(tp, al+bl)
	char *s = tp_string_getptr(r)
	memcpy(s, tp_string_getptr(a), al)
	memcpy(s + al, tp_string_getptr(b), bl)
	return r


def tp_string_mul(TP, tp_obj a, int n) ->tp_obj:
	int al = tp_string_len(a)
	if n <= 0:
		tp_obj r = tp_string_t(tp, 0)
		return r
	else:
		tp_obj r = tp_string_t(tp, al*n)
		char *s = tp_string_getptr(r)
		if a.type.type_id == TP_STRING_TINY:
			for i in range(n):
				memcpy(s+al*i, (char*)&a.str.val, al)
		else:
			for i in range(n):
				memcpy(s+al*i, tp_string_getptr(a), al)
		return r

