#if !defined(__STR_H__)
#define __STR_H__

#include <string.h>

template <typename T> class FlexArray;

/**
 *  Replacement for standard template class string.
 *  \todo share strings to reduce mallocs
 *
 *  \ingroup utils
 */
class Str
{
public:

    /// static constants
    static const size_t NO_POS;
    static const size_t MAX_LEN;
    static const Str EMPTY_STR;
    static char NoChars_[1];

    /// char* constructor
    /// %Unit tests in Str_Test.testConstructorCharStar
    /// Don't use Str( NULL, length) - this will confuse the target.
    /// Use Str( ( char* )NULL, length ); -- or --  Str foo(); foo.set( NULL, length );
    Str( const char* str = NULL, size_t length = NO_POS );

    /// copy constructor
    /// %Unit tests in Str_Test.testCopyConstructor
    Str( const Str& str, const size_t offset = 0, size_t count = NO_POS );

    /// single char constructor
    /// If expandUtf8 == TRUE, and var > 0x7f, preped 0xc2;
    Str( const char var, bool expandUtf8 = false );

    /// bool constructor
    /// %Unit tests in Str_Test.testBoolConstructor
    Str( const bool var );

    /// double constructor
    /// %Unit tests in Str_Test.testDoubleConstructor
    Str( const double var, unsigned int precision = 6 )
        : length_( 0 ),
          chars_( NoChars_ )
    {
        intoString( var, precision );
    }

    /// int constructor
    /// %Unit tests in Str_Test.testIntConstructor
    Str( const int var, unsigned int radix = 10, unsigned int radixOffset = 0 )
        : length_( 0 ),
          chars_( NoChars_ )
    {
        intoString( var, radix, radixOffset );
    }

    /// size_t constructor
    /// %Unit tests in Str_Test.testUnsignedIntConstructor
    Str( const size_t var, unsigned int radix = 16 )
        : length_( 0 ),
          chars_( NoChars_ )
    {
        intoString( ( int )var, radix, 0 );
    }

    /// implode/join constructor
    Str( const Str** arr, int num, const Str& joint = Str::EMPTY_STR )
        : length_( 0 ),
          chars_( NoChars_ )
    {
        appendJoin( arr, num, joint );
    }

    /// destructor
    virtual ~Str();

    // operator overloading helper
    friend Str operator +( const char* var, const Str& str );
    friend Str operator +( const Str& var, const Str& str );

    // set the value
    // This sets the length of the string to length
    // If str is non-null, it will be copied to the string.
    //   If str is null, the string will be 0-padded.
    // If maxCopyLength is specified, that many characters of str will be copied.
    //   The rest will be 0-padded.
    Str& set( const char* str = NULL, size_t length = NO_POS, size_t maxCopyLength = NO_POS );

    // Set a string inside this value
    // If the data to be copied exceeds the length of this string, it will be truncated
    Str& setInside( const char* str, const size_t offset = 0, size_t copyLength = NO_POS );
    Str& setInside( const Str& str, const size_t offset = 0 )
    {
        return setInside( str.cStr(), offset, str.size() );
    }

    // operator overloading
    Str& operator =( const char* str );
    Str& operator =( const Str& str );
    Str& operator =( const double var )
    {
        intoString( var );
        return *this;
    }
    Str substr( const size_t offset, size_t count = NO_POS ) const;
    char operator[]( const size_t index ) const;

    Str& operator +=( const bool str )
    {
        return * this += ( Str ) str;
    }
    Str& operator +=( const unsigned char str )
    {
        return * this += Str( ( const char* ) & str, ( size_t )1 );
    }
    Str& operator +=( const double str )
    {
        return * this += ( Str ) str;
    }
    Str& operator +=( const int str )
    {
        return * this += ( Str ) str;
    }
    Str& operator +=( const size_t str )
    {
        return * this += ( Str ) str;
    }
    Str& operator +=( const char* str )
    {
        return * this += ( Str ) str;
    }
    Str& operator +=( const Str& str );
    Str& append( const Str& str )
    {
        return * this += ( Str ) str;
    }
    Str& append( const char* str, size_t length )
    {
        return * this += Str( str, length );
    }
    Str& pushBack( const unsigned char str )
    {
        return * this += str;
    }

    Str& operator << ( const bool str )
    {
        return * this += str;
    }
    Str& operator << ( const unsigned char str )
    {
        return * this += str;
    }
    Str& operator << ( const double str )
    {
        return * this += str;
    }
    Str& operator << ( const int str )
    {
        return * this += str;
    }
    Str& operator << ( const size_t str )
    {
        return * this += str;
    }
    Str& operator << ( const char* str )
    {
        return * this += str;
    }
    Str& operator << ( const Str& str )
    {
        return * this += str;
    }


    // add more logic comparison operators as following, for example, although not efficient
    bool operator !=( const Str& str ) const
    {
        return compare( str ) != 0;
    }

    bool operator !=( const char* str ) const
    {
        return compare( str ) != 0;
    }

    int compare( const Str& str ) const;
    int compare( const char* str ) const;

    bool operator ==( const Str& str ) const
    {
        return compare( str ) == 0;
    }

    bool operator ==( const char* str ) const
    {
        return compare( str ) == 0;
    }

    bool operator<( const Str& str ) const
    {
        return compare( str ) < 0;
    }

    // c type string conversion
    //operator char*()
    //{
    //    return chars_;
    //}
    //operator const char*() const
    //{
    //    return chars_;
    //}
    const char* cStr() const
    {
        return chars_;
    }
    const char* data() const
    {
        return chars_;
    }
    size_t length() const
    {
        return length_;
    }
    size_t size() const
    {
        return length_;
    }

    size_t findLastOf( const unsigned char str ) const;

    size_t findLastOf( const char* str ) const;

    size_t findLastOf( const Str& str ) const;

    size_t find( const unsigned char str, size_t offset = 0 ) const;

    size_t find( const char* str, size_t offset = 0 ) const;

    size_t find( const Str& str, size_t offset = 0 ) const;

    static size_t FindLastOf( const char* str, const unsigned char theChar, size_t length = NO_POS );

    static size_t Find( const char* str, const unsigned char theChar, size_t length = NO_POS );

    bool startsWith( const char* str, size_t length = NO_POS, bool ignoreCase = true ) const;

    bool startsWith( const Str& str ) const
    {
        return startsWith( str.cStr(), str.length() );
    }

    bool endsWith( const Str& str ) const
    {
        return length_ >= str.length_ && findLastOf( str ) == length_ - str.length_;
    }

    bool setChar( size_t index, const unsigned char theChar );

    void replaceChar( const char replace, const char replaceWith );

    Str& toLower();

    Str& toUpper();

    Str* split( int& num, const char* splitSpec, size_t length = NO_POS ) const;

    Str* split( int& num, const Str str ) const
    {
        return split( num, str.chars_, str.length_ );
    }

    /// Return a new Str, with a hex representation of each character of this string
    Str asHex() const;

    /// implode/join onto exisitng string
    Str& appendJoin( const Str** arr, int num, const Str& joint = Str::EMPTY_STR );

    /// Add string to running checksum
    static void Checksum( unsigned char& csum, const char *chars, size_t length = NO_POS );
    void checksum( unsigned char& csum )
    {
        Checksum( csum, chars_, length_ );
    }

    /// Sets checksum of the string, suitable for use as a NMEA checksum
    static bool NmeaChecksum( unsigned char& csum, const char *chars, size_t length = NO_POS );
    bool nmeaChecksum( unsigned char& csum )
    {
        return NmeaChecksum( csum, chars_, length_ );
    }

    /// Retuns true if string has valid NMEA checksum
    static bool NmeaValidate( char *chars, size_t length = NO_POS );
    bool nmeaValidate()
    {
        return NmeaValidate( chars_, length_ );
    }

    /// convert an integer to a string, returns the length of the string
    /// %Unit tests in Str_Test.testIntToAscii
    static unsigned int IntToAscii( int value, char* str, unsigned int base, unsigned int bufSize, unsigned int baseOffset = 0 );

    static double* ToDoubleArray( const char* cstr, size_t length, int& rank, int& m, int& n, int& o );
    double* toDoubleArray( int& rank, int& m, int& n, int& o ) const
    {
        return Str::ToDoubleArray( this->cStr(), this->length(), rank, m, n, o );
    }
    static double ToDouble( const char* str, size_t len = NO_POS );
    double toDouble() const
    {
        return Str::ToDouble( this->cStr(), this->length() );
    }

protected:

    // have any good conversion method ?
    virtual void intoString( const double var, unsigned int precision = 10 );
    virtual void intoString( int var, unsigned int radix, unsigned int radixOffset = 0 );

    // data block
    size_t length_;
    char* chars_;

};

#endif
