/*
   Copyright (C) 1999-2006 Id Software, Inc. and contributors.
   For a list of contributors, see the accompanying CONTRIBUTORS file.

   This file is part of GtkRadiant.

   GtkRadiant is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   GtkRadiant is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with GtkRadiant; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

//need to rewrite this

#ifndef __UTIL_STR_H__
#define __UTIL_STR_H__

#include <assert.h>
#include <string.h>
#include <stdio.h>

#if GDEF_COMPILER_MSVC
#pragma warning(disable : 4710)     // function 'blah' not inlined
#endif

void TestStringClass();

class strdata
{
public:
strdata () : len( 0 ), refcount( 0 ), data( NULL ), alloced( 0 ) {}
~strdata (){
	if ( data ) {
		delete [] data;
	}
}

void AddRef() { refcount++; }
bool DelRef(){       // True if killed
	refcount--;
	if ( refcount < 0 ) {
		delete this;
		return true;
	}

	return false;
}

int len;
int refcount;
char *data;
int alloced;
};

class idStr {
protected:
strdata *m_data;
void EnsureAlloced( int, bool keepold = true );
void EnsureDataWritable();

public:
~idStr();
idStr();
idStr( const char *text );
idStr( const idStr& string );
idStr( const idStr string, int start, int end );
idStr( const char ch );
idStr( const int num );
idStr( const float num );
idStr( const unsigned num );
int length( void ) const;
int allocated( void ) const;
const char * c_str( void ) const;

void        append( const char *text );
void        append( const idStr& text );
char operator[]( int index ) const;
char&       operator[]( int index );

void operator=( const idStr& text );
void operator=( const char *text );

friend  idStr operator+( const idStr& a, const idStr& b );
friend  idStr operator+( const idStr& a, const char *b );
friend  idStr operator+( const char *a, const idStr& b );

friend  idStr operator+( const idStr& a, const float b );
friend  idStr operator+( const idStr& a, const int b );
friend  idStr operator+( const idStr& a, const unsigned b );
friend  idStr operator+( const idStr& a, const bool b );
friend  idStr operator+( const idStr& a, const char b );

idStr&      operator+=( const idStr& a );
idStr&      operator+=( const char *a );
idStr&      operator+=( const float a );
idStr&      operator+=( const char a );
idStr&      operator+=( const int a );
idStr&      operator+=( const unsigned a );
idStr&      operator+=( const bool a );

friend  bool operator==( const idStr& a, const idStr& b );
friend  bool operator==( const idStr& a, const char *b );
friend  bool operator==( const char *a, const idStr& b );

friend  bool operator!=( const idStr& a, const idStr& b );
friend  bool operator!=( const idStr& a, const char *b );
friend  bool operator!=( const char *a, const idStr& b );

operator const char *() const;
operator const char *();

int      icmpn( const char *text, int n ) const;
int      icmpn( const idStr& text, int n ) const;
int      icmp( const char *text ) const;
int      icmp( const idStr& text ) const;
int      cmpn( const char *text, int n ) const;
int      cmpn( const idStr& text, int n ) const;
int      cmp( const char *text ) const;
int      cmp( const idStr& text ) const;

void     tolower( void );
void     toupper( void );

static char     *tolower( char *s1 );
static char     *toupper( char *s1 );

static int      icmpn( const char *s1, const char *s2, int n );
static int      icmp( const char *s1, const char *s2 );
static int      cmpn( const char *s1, const char *s2, int n );
static int      cmp( const char *s1, const char *s2 );

static void     snprintf( char *dst, int size, const char *fmt, ... );

static bool      isNumeric( const char *str );
bool    isNumeric( void ) const;

void     CapLength( int );

void     BackSlashesToSlashes();

};

inline idStr::~idStr(){
	if ( m_data ) {
		m_data->DelRef();
		m_data = NULL;
	}
}

inline idStr::idStr() : m_data( NULL ){
	EnsureAlloced( 1 );
	m_data->data[ 0 ] = 0;
}

inline idStr::idStr
(
	const char *text
) : m_data( NULL ){
	int len;

	assert( text );

	if ( text ) {
		len = strlen( text );
		EnsureAlloced( len + 1 );
		strcpy( m_data->data, text );
		m_data->len = len;
	}
	else
	{
		EnsureAlloced( 1 );
		m_data->data[ 0 ] = 0;
		m_data->len = 0;
	}
}

inline idStr::idStr
(
	const idStr& text
) : m_data( NULL ){
	m_data = text.m_data;
	m_data->AddRef();
}

inline idStr::idStr
(
	const idStr text,
	int start,
	int end
) : m_data( NULL ){
	int i;
	int len;

	if ( end > text.length() ) {
		end = text.length();
	}

	if ( start > text.length() ) {
		start = text.length();
	}

	len = end - start;
	if ( len < 0 ) {
		len = 0;
	}

	EnsureAlloced( len + 1 );

	for ( i = 0; i < len; i++ )
	{
		m_data->data[ i ] = text[ start + i ];
	}

	m_data->data[ len ] = 0;
	m_data->len = len;
}

inline idStr::idStr
(
	const char ch
) : m_data( NULL ){
	EnsureAlloced( 2 );

	m_data->data[ 0 ] = ch;
	m_data->data[ 1 ] = 0;
	m_data->len = 1;
}

inline idStr::idStr
(
	const float num
) : m_data( NULL ){
	char text[ 32 ];
	int len;

	sprintf( text, "%.3f", num );
	len = strlen( text );
	EnsureAlloced( len + 1 );
	strcpy( m_data->data, text );
	m_data->len = len;
}

inline idStr::idStr
(
	const int num
) : m_data( NULL ){
	char text[ 32 ];
	int len;

	sprintf( text, "%d", num );
	len = strlen( text );
	EnsureAlloced( len + 1 );
	strcpy( m_data->data, text );
	m_data->len = len;
}

inline idStr::idStr
(
	const unsigned num
) : m_data( NULL ){
	char text[ 32 ];
	int len;

	sprintf( text, "%u", num );
	len = strlen( text );
	EnsureAlloced( len + 1 );
	strcpy( m_data->data, text );
	m_data->len = len;
}

inline int idStr::length( void ) const {
	return ( m_data != NULL ) ? m_data->len : 0;
}

inline int idStr::allocated( void ) const {
	return ( m_data != NULL ) ? m_data->alloced + sizeof( *m_data ) : 0;
}

inline const char *idStr::c_str( void ) const {
	assert( m_data );

	return m_data->data;
}

inline void idStr::append
(
	const char *text
){
	int len;

	assert( text );

	if ( text ) {
		len = length() + strlen( text );
		EnsureAlloced( len + 1 );

		strcat( m_data->data, text );
		m_data->len = len;
	}
}

inline void idStr::append
(
	const idStr& text
){
	int len;

	len = length() + text.length();
	EnsureAlloced( len + 1 );

	strcat( m_data->data, text.c_str() );
	m_data->len = len;
}

inline char idStr::operator[]( int index ) const {
	assert( m_data );

	if ( !m_data ) {
		return 0;
	}

	// don't include the '/0' in the test, because technically, it's out of bounds
	assert( ( index >= 0 ) && ( index < m_data->len ) );

	// In release mode, give them a null character
	// don't include the '/0' in the test, because technically, it's out of bounds
	if ( ( index < 0 ) || ( index >= m_data->len ) ) {
		return 0;
	}

	return m_data->data[ index ];
}

inline char& idStr::operator[]
(
	int index
){
	// Used for result for invalid indices
	static char dummy = 0;
	assert( m_data );

	// We don't know if they'll write to it or not
	// if it's not a const object
	EnsureDataWritable();

	if ( !m_data ) {
		return dummy;
	}

	// don't include the '/0' in the test, because technically, it's out of bounds
	assert( ( index >= 0 ) && ( index < m_data->len ) );

	// In release mode, let them change a safe variable
	// don't include the '/0' in the test, because technically, it's out of bounds
	if ( ( index < 0 ) || ( index >= m_data->len ) ) {
		return dummy;
	}

	return m_data->data[ index ];
}

inline void idStr::operator=
(
	const idStr& text
){
	// adding the reference before deleting our current reference prevents
	// us from deleting our string if we are copying from ourself
	text.m_data->AddRef();
	m_data->DelRef();
	m_data = text.m_data;
}

inline void idStr::operator=
(
	const char *text
){
	int len;

	assert( text );

	if ( !text ) {
		// safe behaviour if NULL
		EnsureAlloced( 1, false );
		m_data->data[0] = 0;
		m_data->len = 0;
		return;
	}

	if ( !m_data ) {
		len = strlen( text );
		EnsureAlloced( len + 1, false );
		strcpy( m_data->data, text );
		m_data->len = len;
		return;
	}

	if ( text == m_data->data ) {
		return; // Copying same thing.  Punt.

	}
	// If we alias and I don't do this, I could corrupt other strings...  This
	// will get called with EnsureAlloced anyway
	EnsureDataWritable();

	// Now we need to check if we're aliasing..
	if ( text >= m_data->data && text <= m_data->data + m_data->len ) {
		// Great, we're aliasing.  We're copying from inside ourselves.
		// This means that I don't have to ensure that anything is alloced,
		// though I'll assert just in case.
		int diff = text - m_data->data;
		int i;

		assert( strlen( text ) < (unsigned) m_data->len );

		for ( i = 0; text[i]; i++ )
		{
			m_data->data[i] = text[i];
		}

		m_data->data[i] = 0;

		m_data->len -= diff;

		return;
	}

	len = strlen( text );
	EnsureAlloced( len + 1, false );
	strcpy( m_data->data, text );
	m_data->len = len;
}

inline idStr operator+
(
	const idStr& a,
	const idStr& b
){
	idStr result( a );

	result.append( b );

	return result;
}

inline idStr operator+
(
	const idStr& a,
	const char *b
){
	idStr result( a );

	result.append( b );

	return result;
}

inline idStr operator+
(
	const char *a,
	const idStr& b
){
	idStr result( a );

	result.append( b );

	return result;
}

inline idStr operator+
(
	const idStr& a,
	const bool b
){
	idStr result( a );

	result.append( b ? "true" : "false" );

	return result;
}

inline idStr operator+
(
	const idStr& a,
	const char b
){
	char text[ 2 ];

	text[ 0 ] = b;
	text[ 1 ] = 0;

	return a + text;
}

inline idStr& idStr::operator+=
(
	const idStr& a
){
	append( a );
	return *this;
}

inline idStr& idStr::operator+=
(
	const char *a
){
	append( a );
	return *this;
}

inline idStr& idStr::operator+=
(
	const char a
){
	char text[ 2 ];

	text[ 0 ] = a;
	text[ 1 ] = 0;
	append( text );

	return *this;
}

inline idStr& idStr::operator+=
(
	const bool a
){
	append( a ? "true" : "false" );
	return *this;
}

inline bool operator==
(
	const idStr& a,
	const idStr& b
){
	return ( !strcmp( a.c_str(), b.c_str() ) );
}

inline bool operator==
(
	const idStr& a,
	const char *b
){
	assert( b );
	if ( !b ) {
		return false;
	}
	return ( !strcmp( a.c_str(), b ) );
}

inline bool operator==
(
	const char *a,
	const idStr& b
){
	assert( a );
	if ( !a ) {
		return false;
	}
	return ( !strcmp( a, b.c_str() ) );
}

inline bool operator!=
(
	const idStr& a,
	const idStr& b
){
	return !( a == b );
}

inline bool operator!=
(
	const idStr& a,
	const char *b
){
	return !( a == b );
}

inline bool operator!=
(
	const char *a,
	const idStr& b
){
	return !( a == b );
}

inline int idStr::icmpn
(
	const char *text,
	int n
) const {
	assert( m_data );
	assert( text );

	return idStr::icmpn( m_data->data, text, n );
}

inline int idStr::icmpn
(
	const idStr& text,
	int n
) const {
	assert( m_data );
	assert( text.m_data );

	return idStr::icmpn( m_data->data, text.m_data->data, n );
}

inline int idStr::icmp
(
	const char *text
) const {
	assert( m_data );
	assert( text );

	return idStr::icmp( m_data->data, text );
}

inline int idStr::icmp
(
	const idStr& text
) const {
	assert( c_str() );
	assert( text.c_str() );

	return idStr::icmp( c_str(), text.c_str() );
}

inline int idStr::cmp
(
	const char *text
) const {
	assert( m_data );
	assert( text );

	return idStr::cmp( m_data->data, text );
}

inline int idStr::cmp
(
	const idStr& text
) const {
	assert( c_str() );
	assert( text.c_str() );

	return idStr::cmp( c_str(), text.c_str() );
}

inline int idStr::cmpn
(
	const char *text,
	int n
) const {
	assert( c_str() );
	assert( text );

	return idStr::cmpn( c_str(), text, n );
}

inline int idStr::cmpn
(
	const idStr& text,
	int n
) const {
	assert( c_str() );
	assert( text.c_str()  );

	return idStr::cmpn( c_str(), text.c_str(), n );
}

inline void idStr::tolower
(
	void
){
	assert( m_data );

	EnsureDataWritable();

	idStr::tolower( m_data->data );
}

inline void idStr::toupper
(
	void
){
	assert( m_data );

	EnsureDataWritable();

	idStr::toupper( m_data->data );
}

inline bool idStr::isNumeric
(
	void
) const {
	assert( m_data );
	return idStr::isNumeric( m_data->data );
}

inline idStr::operator const char *() {
	return c_str();
}

inline idStr::operator const char *
(
	void
) const {
	return c_str();
}

#endif
