#include "chdtypes.h"
// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************

    astring.c

    Allocated string manipulation functions.

****************************************************************************/

#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <ctype.h>
#include <new>

#include "astring.h"
#include "osdcore.h"


/***************************************************************************
    GLOBAL VARIABLES
***************************************************************************/

static const astring dummy_astring;



//**************************************************************************
//  INLINE FUNCTIONS
//**************************************************************************

//-------------------------------------------------
//  ensure_room - ensure we have room for a
//  given string, or else reallocate our buffer
//-------------------------------------------------

bool astring::ensure_room(int length)
{
	// always fail to expand the dummy
	if (this == &dummy_astring)
		return false;

	// if we have the room, do nothing
	if (m_alloclen >= length + 1)
		return true;

	// allocate a new buffer with some slop
	int alloclen = length + 256;
	char *newbuf = new char[alloclen];

	// swap in the new buffer and free the old one
	char *oldbuf = (m_text == m_smallbuf) ? NULL : m_text;
	m_text = strcpy(newbuf, m_text);
	m_len = strlen(m_text);
	m_alloclen = alloclen;
	delete[] oldbuf;

	return true;
}


//-------------------------------------------------
//  safe_string_base - return a "safe" string
//  base for a given start index
//-------------------------------------------------

inline char *astring::safe_string_base(int start) const
{
	int max = len();
	return (start >= 0 && start < max) ? m_text + start : m_text + max;
}


//-------------------------------------------------
//  normalize_substr - normalize substr parameters
//-------------------------------------------------

inline void astring::normalize_substr(int &start, int &count, int length) const
{
	// limit start
	if (start < 0)
		start = 0;
	else if (start > length)
		start = length;

	// update count
	if (count == -1 || start + count > length)
		count = length - start;
}



//**************************************************************************
//  ASTRING ALLOCATION
//**************************************************************************

//-------------------------------------------------
//  init - constructor helper
//-------------------------------------------------

astring &astring::init()
{
	// initialize ourselves to point to the internal buffer
	m_text = m_smallbuf;
	m_alloclen = ARRAY_LENGTH(m_smallbuf);
	m_smallbuf[0] = 0;
	m_len = 0;
	return *this;
}


//-------------------------------------------------
//  ~astring - destructor
//-------------------------------------------------

astring::~astring()
{
	if (m_text != m_smallbuf)
		delete[] m_text;
}



/***************************************************************************
    INLINE ASTRING CHANGES
***************************************************************************/

//-------------------------------------------------
//  cpy - copy a character array into an astring
//-------------------------------------------------

astring &astring::cpy(const char *src, int count)
{
	// make room; if we fail or if we are the dummy, do nothing
	if (!ensure_room(count))
		return *this;

	// copy the raw data and NULL-terminate
	if (count > 0 && m_text != src)
		memcpy(m_text, src, count);
	m_text[count] = 0;
	m_len = count;
	return *this;
}


//-------------------------------------------------
//  cpysubstr - copy a substring of one string to
//  another
//-------------------------------------------------

astring &astring::cpysubstr(const astring &src, int start, int count)
{
	normalize_substr(start, count, src.len());
	return cpy(src.m_text + start, count);
}


//-------------------------------------------------
//  ins - insert a character array into an astring
//-------------------------------------------------

astring &astring::ins(int insbefore, const char *src, int count)
{
	// make room; if we fail or if we are the dummy, do nothing
	int dstlength = len();
	if (!ensure_room(dstlength + count))
		return *this;

	// adjust insbefore to be logical
	if (insbefore < 0 || insbefore > dstlength)
		insbefore = dstlength;

	// copy the data an NULL-terminate
	if (insbefore < dstlength)
		memmove(m_text + insbefore + count, m_text + insbefore, dstlength - insbefore);
	memcpy(m_text + insbefore, src, count);
	m_text[dstlength + count] = 0;
	m_len = dstlength + count;
	return *this;
}


//-------------------------------------------------
//  inssubstr - insert a substring of one string
//  into another
//-------------------------------------------------

astring &astring::inssubstr(int insbefore, const astring &src, int start, int count)
{
	normalize_substr(start, count, src.len());
	return ins(insbefore, src.m_text + start, count);
}


//-------------------------------------------------
//  substr - extract a substring of ourself,
//  removing everything else
//-------------------------------------------------

astring &astring::substr(int start, int count)
{
	// ignore attempts to do this on the dummy
	if (this == &dummy_astring)
		return *this;

	// normalize parameters
	normalize_substr(start, count, len());

	// move the data and NULL-terminate
	if (count > 0 && start > 0)
		memmove(m_text, m_text + start, count);
	m_text[count] = 0;
	m_len = count;
	return *this;
}


//-------------------------------------------------
//  del - delete a substring of ourself, keeping
//  everything else
//-------------------------------------------------

astring &astring::del(int start, int count)
{
	// ignore attempts to do this on the dummy
	if (this == &dummy_astring)
		return *this;

	// normalize parameters
	int strlength = len();
	normalize_substr(start, count, strlength);

	// move the data and NULL-terminate
	if (count > 0)
		memmove(m_text + start, m_text + start + count, strlength - (start + count));
	m_text[strlength - count] = 0;
	m_len = strlength - count;
	return *this;
}


//-------------------------------------------------
//  vprintf - vprintf text into an astring
//-------------------------------------------------*/

int astring::vprintf(const char *format, va_list args)
{
	// sprintf into the temporary buffer
	char tempbuf[4096];
	int result = vsprintf(tempbuf, format, args);

	// set the result
	cpy(tempbuf);
	return result;
}


//-------------------------------------------------
//  catprintf - formatted vprintf to the end of
//  an astring
//-------------------------------------------------

int astring::catvprintf(const char *format, va_list args)
{
	// sprintf into the temporary buffer
	char tempbuf[4096];
	int result = vsprintf(tempbuf, format, args);

	// append the result
	cat(tempbuf);
	return result;
}



/***************************************************************************
    ASTRING QUERIES
***************************************************************************/

//-------------------------------------------------
//  cmp - compare a character array to an astring
//-------------------------------------------------

int astring::cmp(const char *str2, int count) const
{
	// loop while equal until we hit the end of strings
	int index;
	for (index = 0; index < count; index++)
		if (m_text[index] == 0 || m_text[index] != str2[index])
			break;

	// determine the final result
	if (index < count)
		return m_text[index] - str2[index];
	if (m_text[index] == 0)
		return 0;
	return 1;
}


//-------------------------------------------------
//  cmpsubstr - compare a substring to an astring
//-------------------------------------------------

int astring::cmpsubstr(const astring &str2, int start, int count) const
{
	normalize_substr(start, count, str2.len());
	return cmp(str2.m_text + start, count);
}


//-------------------------------------------------
//  icmp - compare a character array to an astring
//-------------------------------------------------

int astring::icmp(const char *str2, int count) const
{
	// loop while equal until we hit the end of strings
	int index;
	for (index = 0; index < count; index++)
		if (m_text[index] == 0 || tolower(m_text[index]) != tolower(str2[index]))
			break;

	// determine the final result
	if (index < count)
		return tolower(m_text[index]) - tolower(str2[index]);
	if (m_text[index] == 0)
		return 0;
	return 1;
}


//-------------------------------------------------
//  icmpsubstr - compare a substring to an astring
//-------------------------------------------------

int astring::icmpsubstr(const astring &str2, int start, int count) const
{
	normalize_substr(start, count, str2.len());
	return icmp(str2.m_text + start, count);
}


//-------------------------------------------------
//  chr - return the index of a character in an
//  astring
//-------------------------------------------------

int astring::chr(int start, int ch) const
{
	char *result = strchr(safe_string_base(start), ch);
	return (result != NULL) ? (result - m_text) : -1;
}


//-------------------------------------------------
//  rchr - return the index of a character in an
//  astring, searching from the end
//-------------------------------------------------

int astring::rchr(int start, int ch) const
{
	char *result = strrchr(safe_string_base(start), ch);
	return (result != NULL) ? (result - m_text) : -1;
}


//-------------------------------------------------
//  find - find a C string in an astring
//-------------------------------------------------*/

int astring::find(int start, const char *search) const
{
	char *result = strstr(safe_string_base(start), search);
	return (result != NULL) ? (result - m_text) : -1;
}


//-------------------------------------------------
//  replacec - search in an astring for a C string,
//  replacing all instances with another C string
//  and returning the number of matches
//-------------------------------------------------

int astring::replace(int start, const char *search, const char *replace)
{
	int searchlen = strlen(search);
	int replacelen = strlen(replace);
	int matches = 0;

	for (int curindex = find(start, search); curindex != -1; curindex = find(curindex + replacelen, search))
	{
		matches++;
		del(curindex, searchlen).ins(curindex, replace);
	}
	return matches;
}



//**************************************************************************
//  ASTRING UTILITIES
//**************************************************************************

//-------------------------------------------------
//  delchr - delete all instances of 'ch'
//-------------------------------------------------

astring &astring::delchr(int ch)
{
	// simple deletion
	char *dst = m_text;
	for (char *src = m_text; *src != 0; src++)
		if (*src != ch)
			*dst++ = *src;
	*dst = 0;
	m_len = strlen(m_text);
	return *this;
}


//-------------------------------------------------
//  replacechr - replace all instances of 'ch'
//  with 'newch'
//-------------------------------------------------

astring &astring::replacechr(int ch, int newch)
{
	// simple replacement
	for (char *text = m_text; *text != 0; text++)
		if (*text == ch)
			*text = newch;
	return *this;
}


//-------------------------------------------------
//  makeupper - convert string to all upper-case
//-------------------------------------------------

astring &astring::makeupper()
{
	// just makeupper() on all characters
	for (char *text = m_text; *text != 0; text++)
		*text = toupper((UINT8)*text);
	return *this;
}


//-------------------------------------------------
//  makelower - convert string to all lower-case
//-------------------------------------------------

astring &astring::makelower()
{
	// just tolower() on all characters
	for (char *text = m_text; *text != 0; text++)
		*text = tolower((UINT8)*text);
	return *this;
}


//-------------------------------------------------
//  trimspace - remove all space characters from
//  beginning/end
//-------------------------------------------------

astring &astring::trimspace()
{
	// first remove stuff from the end
	for (char *ptr = m_text + len() - 1; ptr >= m_text && (!(*ptr & 0x80) && isspace(UINT8(*ptr))); ptr--)
		*ptr = 0;

	// then count how much to remove from the beginning
	char *ptr;
	for (ptr = m_text; *ptr != 0 && (!(*ptr & 0x80) && isspace(UINT8(*ptr))); ptr++) ;
	if (ptr > m_text)
		substr(ptr - m_text);
	m_len = strlen(m_text);
	return *this;
}
