// Copyright (C) 2002-2005 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine" and the "irrXML" project.
// For conditions of distribution and use, see copyright notice in irrlicht.h and irrXML.h

#ifndef __IRR_STRING_H_INCLUDED__
#define __IRR_STRING_H_INCLUDED__

#include "irrTypes.h"

namespace irr
{
namespace core
{

//!    Very simple string class with some useful features.
/**    string<c8> and string<wchar_t> work both with unicode AND ascii,
so you can assign unicode to string<c8> and ascii to string<wchar_t>
(and the other way round) if your ever would want to.
Note that the conversation between both is not done using an encoding.

Known bugs:
Special characters like '', '' and '' are ignored in the
methods make_upper, make_lower and equals_ignore_case.
*/
template <class T>
class string
{
public:

    //! Default constructor
    string()
    : array(0), allocated(1), used(1)
    {
        array = new T[1];
        array[0] = 0x0;
    }



    //! Constructor
    string(const string<T>& other)
    : array(0), allocated(0), used(0)
    {
        *this = other;
    }


    //! Constructs a string from an int
    string(int number)
    : array(0), allocated(0), used(0)
    {
        // store if negative and make positive

        bool negative = false;
        if (number < 0)
        {
            number *= -1;
            negative = true;
        }

        // temporary buffer for 16 numbers

        c8 tmpbuf[16];
        tmpbuf[15] = 0;
        s32 idx = 15;

        // special case '0'

        if (!number)
        {
            tmpbuf[14] = '0';
            *this = &tmpbuf[14];
            return;
        }

        // add numbers

        while (number && idx)
        {
            idx--;
            tmpbuf[idx] = (c8)('0' + (number % 10));
            number = number / 10;
        }

        // add sign

        if (negative)
        {
            idx--;
            tmpbuf[idx] = '-';
        }

        *this = &tmpbuf[idx];
    }



    //! Constructor for copying a string from a pointer with a given lenght
    template <class B>
    string(const B* c, s32 lenght)
    : array(0), allocated(0), used(0)
    {
        if (!c)
            return;

        allocated = used = lenght+1;
        array = new T[used];

        for (s32 l = 0; l<lenght; ++l)
            array[l] = (T)c[l];

        array[lenght] = 0;
    }



    //! Constructor for unicode and ascii strings
    template <class B>
    string(const B* c)
    : array(0),allocated(0), used(0)
    {
        *this = c;
    }



    //! destructor
    ~string()
    {
        delete [] array;
    }



    //! Assignment operator
    string<T>& operator=(const string<T>& other)
    {
        if (this == &other)
            return *this;

        delete [] array;
        allocated = used = other.size()+1;
        array = new T[used];

        const T* p = other.c_str();
        for (s32 i=0; i<used; ++i, ++p)
            array[i] = *p;

        return *this;
    }



    //! Assignment operator for strings, ascii and unicode
    template <class B>
    string<T>& operator=(const B* c)
    {
        if (!c)
        {
            if (!array)
            {
                array = new T[1];
                allocated = 1;
                used = 1;
            }
            array[0] = 0x0;
            return *this;
        }

        if ((void*)c == (void*)array)
            return *this;

        s32 len = 0;
        const B* p = c;
        while (*p)
        {
            ++len;
            ++p;
        }

        // we'll take the old string for a while, because the new string could be
        // a part of the current string.
        T* oldArray = array;

        allocated = used = len+1;
        array = new T[used];

        for (s32 l = 0; l<len+1; ++l)
            array[l] = (T)c[l];

        delete [] oldArray;
        return *this;
    }

    //! Add operator for other strings
    string<T> operator+(const string<T>& other)
    {
        string<T> str(*this);
        str.append(other);

        return str;
    }

    //! Add operator for strings, ascii and unicode
    template <class B>
    string<T> operator+(const B* c)
    {
        string<T> str(*this);
        str.append(c);

        return str;
    }



    //! Direct access operator
    T& operator [](const s32 index)  const
    {
        _IRR_DEBUG_BREAK_IF(index>=used) // bad index

        return array[index];
    }


    //! Comparison operator
    bool operator ==(const T* str) const
    {
        int i;
        for (i=0; array[i] && str[i]; ++i)
            if (array[i] != str[i])
                return false;

        return !array[i] && !str[i];
    }



    //! Comparison operator
    bool operator ==(const string<T>& other) const
    {
        for (s32 i=0; array[i] && other.array[i]; ++i)
            if (array[i] != other.array[i])
                return false;

        return used == other.used;
    }



    //! Is smaller operator
    bool operator <(const string<T>& other) const
    {
        for (s32 i=0; array[i] && other.array[i]; ++i)
            if (array[i] != other.array[i])
                return (array[i] < other.array[i]);

        return used < other.used;
    }



    //! Equals not operator
    bool operator !=(const string<T>& other) const
    {
        return !(*this == other);
    }



    //! Returns length of string
    /** \return Returns length of the string in characters. */
    s32 size() const
    {
        return used-1;
    }



    //! Returns character string
    /** \return Returns pointer to C-style zero terminated string. */
    const T* c_str() const
    {
        return array;
    }



    //! Makes the string lower case.
    void make_lower()
    {
        const T A = (T)'A';
        const T Z = (T)'Z';
        const T diff = (T)'a' - A;

        for (s32 i=0; i<used; ++i)
        {
            if (array[i]>=A && array[i]<=Z)
                array[i] += diff;
        }
    }



    //! Makes the string upper case.
    void make_upper()
    {
        const T a = (T)'a';
        const T z = (T)'z';
        const T diff = (T)'A' - a;

        for (s32 i=0; i<used; ++i)
        {
            if (array[i]>=a && array[i]<=z)
                array[i] += diff;
        }
    }



    //! Compares the string ignoring case.
    /** \param other: Other string to compare.
    \return Returns true if the string are equal ignoring case. */
    bool equals_ignore_case(const string<T>& other) const
    {
        for (s32 i=0; array[i] && other[i]; ++i)
            if (toLower(array[i]) != toLower(other[i]))
                return false;

        return used == other.used;
    }


    //! compares the first n characters of the strings
    bool equalsn(const string<T>& other, int len)
    {
        int i;
        for (i=0; array[i] && other[i] && i < len; ++i)
            if (array[i] != other[i])
                return false;

        // if one (or both) of the strings was smaller then they
        // are only equal if they have the same lenght
        return (i == len) || (used == other.used);
    }


    //! compares the first n characters of the strings
    bool equalsn(const T* str, int len)
    {
        int i;
        for (i=0; array[i] && str[i] && i < len; ++i)
            if (array[i] != str[i])
                return false;

        // if one (or both) of the strings was smaller then they
        // are only equal if they have the same lenght
        return (i == len) || (array[i] == 0 && str[i] == 0);
    }


    //! Appends a character to this string
    /** \param character: Character to append. */
    void append(T character)
    {
        if (used + 1 > allocated)
            reallocate((s32)used + 1);

        used += 1;

        array[used-2] = character;
        array[used-1] = 0;
    }

    //! Appends a string to this string
    /** \param other: String to append. */
    void append(const string<T>& other)
    {
        --used;

        s32 len = other.size();

        if (used + len + 1 > allocated)
            reallocate((s32)used + (s32)len + 1);

        for (s32 l=0; l<len+1; ++l)
            array[l+used] = other[l];

        used = used + len + 1;
    }


    //! Appends a string of the length l to this string.
    /** \param other: other String to append to this string.
     \param length: How much characters of the other string to add to this one. */
    void append(const string<T>& other, s32 length)
    {
        s32 len = other.size();

        if (len < length)
        {
            append(other);
            return;
        }

        len = length;
        --used;

        if (used + len > allocated)
            reallocate((s32)used + (s32)len);

        for (s32 l=0; l<len; ++l)
            array[l+used] = other[l];

        used = used + len;
    }


    //! Reserves some memory.
    /** \param count: Amount of characters to reserve. */
    void reserve(s32 count)
    {
        if (count < allocated)
            return;

        reallocate(count);
    }


    //! finds first occurrence of character in string
    /** \param c: Character to search for.
    \return Returns position where the character has been found,
    or -1 if not found. */
    s32 findFirst(T c) const
    {
        for (s32 i=0; i<used; ++i)
            if (array[i] == c)
                return i;

        return -1;
    }

    //! finds first occurrence of a character of a list in string
    /** \param c: List of strings to find. For example if the method
    should find the first occurance of 'a' or 'b', this parameter should be "ab".
    \param count: Amount of characters in the list. Ususally,
    this should be strlen(ofParameter1)
    \return Returns position where one of the character has been found,
    or -1 if not found. */
    s32 findFirstChar(T* c, int count) const
    {
        for (s32 i=0; i<used; ++i)
            for (int j=0; j<count; ++j)
                if (array[i] == c[j])
                    return i;

        return -1;
    }


    //! Finds first position of a character not in a given list.
    /** \param c: List of characters not to find. For example if the method
     should find the first occurance of a character not 'a' or 'b', this parameter should be "ab".
    \param count: Amount of characters in the list. Ususally,
    this should be strlen(ofParameter1)
    \return Returns position where the character has been found,
    or -1 if not found. */
    template <class B>
    s32 findFirstCharNotInList(B* c, int count) const
    {
        for (int i=0; i<used; ++i)
        {
            int j;
            for (j=0; j<count; ++j)
                if (array[i] == c[j])
                    break;

            if (j==count)
                return i;
        }

        return -1;
    }

    //! Finds last position of a character not in a given list.
    /** \param c: List of characters not to find. For example if the method
     should find the first occurance of a character not 'a' or 'b', this parameter should be "ab".
    \param count: Amount of characters in the list. Ususally,
    this should be strlen(ofParameter1)
    \return Returns position where the character has been found,
    or -1 if not found. */
    template <class B>
    s32 findLastCharNotInList(B* c, int count) const
    {
        for (int i=used-2; i>=0; --i)
        {
            int j;
            for (j=0; j<count; ++j)
                if (array[i] == c[j])
                    break;

            if (j==count)
                return i;
        }

        return -1;
    }

    //! finds next occurrence of character in string
    /** \param c: Character to search for.
    \param startPos: Position in string to start searching.
    \return Returns position where the character has been found,
    or -1 if not found. */
    s32 findNext(T c, s32 startPos) const
    {
        for (s32 i=startPos; i<used; ++i)
            if (array[i] == c)
                return i;

        return -1;
    }


    //! finds last occurrence of character in string
    //! \param c: Character to search for.
    //! \return Returns position where the character has been found,
    //! or -1 if not found.
    s32 findLast(T c) const
    {
        for (s32 i=used-1; i>=0; --i)
            if (array[i] == c)
                return i;

        return -1;
    }


    //! Returns a substring
    //! \param begin: Start of substring.
    //! \param length: Length of substring.
    string<T> subString(s32 begin, s32 length)
    {
        if (length <= 0)
            return string<T>("");

        string<T> o;
        o.reserve(length+1);

        for (s32 i=0; i<length; ++i)
            o.array[i] = array[i+begin];

        o.array[length] = 0;
        o.used = o.allocated;

        return o;
    }


    void operator += (T c)
    {
        append(c);
    }

    void operator += (const string<T>& other)
    {
        append(other);
    }

    void operator += (int i)
    {
        append(string<T>(i));
    }

    //! replaces all characters of a special type with another one
    void replace(T toReplace, T replaceWith)
    {
        for (s32 i=0; i<used; ++i)
            if (array[i] == toReplace)
                array[i] = replaceWith;
    }

    //! trims the string.
    /** Removes whitespace from begin and end of the string. */
    void trim()
    {
        const char whitespace[] = " \t\n";
        const int whitespacecount = 3;

        // find start and end of real string without whitespace
        int begin = findFirstCharNotInList(whitespace, whitespacecount);
        if (begin == -1)
            return;

        int end = findLastCharNotInList(whitespace, whitespacecount);
        if (end == -1)
            return;

        *this = subString(begin, (end +1) - begin);
    }


    //! Erases a character from the string. May be slow, because all elements
    //! following after the erased element have to be copied.
    //! \param index: Index of element to be erased.
    void erase(int index)
    {
        _IRR_DEBUG_BREAK_IF(index>=used || index<0) // access violation

        for (int i=index+1; i<used; ++i)
            array[i-1] = array[i];

        --used;
    }



private:

    //! Returns a character converted to lower case
    T toLower(const T& t) const
    {
        if (t>=(T)'A' && t<=(T)'Z')
            return t + ((T)'a' - (T)'A');
        else
            return t;
    }

    //! Reallocate the array, make it bigger or smaler
    void reallocate(s32 new_size)
    {
        T* old_array = array;

        array = new T[new_size];
        allocated = new_size;

        s32 amount = used < new_size ? used : new_size;
        for (s32 i=0; i<amount; ++i)
            array[i] = old_array[i];

        if (allocated < used)
            used = allocated;

        delete [] old_array;
    }


    //--- member variables

    T* array;
    s32 allocated;
    s32 used;
};


//! Typedef for character strings
typedef string<irr::c8> stringc;

//! Typedef for wide character strings
typedef string<wchar_t> stringw;

} // end namespace core
} // end namespace irr

#endif

