/** \file
 *
 *  Contains the Str class implementation.
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */

#include "Str.h"

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <math.h>
#include <ctype.h>
//#include "AuvMath.h"

const size_t Str::NO_POS( 0xFFFFFFFF );
const size_t Str::MAX_LEN( 1024 * 1024 );
const Str Str::EMPTY_STR;
char Str::NoChars_[1] = "";

Str::Str( const char* str, size_t length )
    : length_( 0 ),
      chars_( NoChars_ )
{
    set( str, length );
}

Str::Str( const Str& str, const size_t offset, size_t count )
    : length_( 0 ),
      chars_( NoChars_ )
{
    if( offset > str.length_ )
    {
        return;
    }
    if( count == NO_POS )
    {
        count = str.length_ - offset;
    }
    if( offset + count > str.length_ )
    {
        count = str.length_ - offset;
    }
    if( count != 0 )
    {
        length_ = count;
        chars_ = new char[( ( length_ + 4 ) / 4 ) * 4 ];
        assert( chars_ != 0 );
        memcpy( chars_, str.chars_ + offset, length_ );
        chars_[ length_ ] = 0;
        //printf("New chars_ \"%s\" at 0x%08X\n", chars_, (int)chars_);
    }
}

/// single char constructor
/// If expandUtf8 == TRUE, and var > 0x7f, preped 0xc2;
Str::Str( const char var, bool expandUtf8 )
    : length_( expandUtf8 && var > 0x7f ? 2 : 1 ),
      chars_( new char[4] )
{
    if( length_ == 2 )
    {
        chars_[0] = 0xc2;
    }
    chars_[ length_ - 1 ] = var;
}

Str::Str( const bool var )
    : length_( 1 ),
      chars_( new char[4] )
{
    chars_[0] = var ? '1' : '0';
    chars_[1] = 0;
}

Str::~Str()
{
    if( chars_ != NoChars_ )
    {
        //printf("Deleting chars_ \"%s\" at 0x%08X\n", chars_, (int)chars_);
        delete[] chars_;
        chars_ = NoChars_;
    }
}

Str Str::substr( const size_t offset, size_t count ) const
{
    if( offset > length_ )
    {
        return Str();
    }
    if( count == NO_POS )
    {
        count = length_ - offset;
    }
    if( offset + count > length_ )
    {
        count = length_ - offset;
    }
    return Str( chars_ + offset, count );
}

char Str::operator[]( const size_t index ) const
{
    if( index < length_ )
    {
        return chars_[ index ];
    }
    return 0;
}

Str operator +( const char* var, const Str& str )
{
    Str svar = var;
    return svar += str;
}

Str operator +( const Str& var, const Str& str )
{
    Str svar = var;
    return svar += str;
}

Str& Str::operator +=( const Str & str )
{
    size_t newLength = length_ + str.length_;
    char* pNew = new char[( ( newLength + 4 ) / 4 ) * 4 ];
    assert( pNew != 0 );
    if( chars_ )
    {
        memcpy( pNew, chars_, length_ );
    }
    pNew[ length_ ] = 0;
    if( str.chars_ )
    {
        memcpy( pNew + length_, str.chars_, str.length_ );
        //strcat(pNew, str.chars_);
        //printf("length_=%d, strlen(pNew)=%d\n", length_, strlen(pNew));
    }
    if( chars_ != NoChars_ )
    {
        //printf("Deleting += chars_ \"%s\" at 0x%08X\n", chars_, (int)chars_);
        delete[] chars_;
    }
    chars_ = pNew;
    length_ = newLength;
    chars_[ length_ ] = 0;
    //printf("New += chars_ \"%s\" at 0x%08X\n", chars_, (int)chars_);

    return *this;
}

int Str::compare( const Str& str ) const
{
    if( chars_ == NoChars_ && str.chars_ == NoChars_ )
        return 0;
    if( chars_ == NoChars_ )
        return -1;
    if( str.chars_ == NoChars_ )
        return 1;
    int comp = memcmp( chars_, str.chars_, length_ < str.length_ ? length_ : str.length_ );
    return comp == 0 ? length_ - str.length_ : comp;
}

int Str::compare( const char* str ) const
{
    if( chars_ == NoChars_ && ( str == 0 || *str == *NoChars_ ) )
        return 0;
    if( chars_ == NoChars_ )
        return -1;
    if( str == 0 || *str == 0 )
        return 1;
    int comp = strncmp( chars_, str, length_ );
    return comp == 0 ? length_ - strlen( str ) : comp;
}

Str& Str::set( const char * str, size_t length, size_t maxCopyLength )
{
    if( length == NO_POS )
    {
        if( NULL == str )
        {
            length = 0;
        }
        else
        {
            length = strlen( str );
        }
    }

    int padLenOld = length_ == 0 ? 0 : ( ( length_ + 4 ) / 4 ) * 4;
    int padLen = length == 0 ? 0 : ( ( length + 4 ) / 4 ) * 4;

    if( padLen != padLenOld )
    {
        if( padLenOld > 0 )
        {
            delete[] chars_;
        }
        if( padLen > 0 )
        {
            chars_ = new char[padLen];
        }
        else
        {
            chars_ = NoChars_;
        }
    }

    if( NULL == str )
    {
        for( unsigned int i = 0; i <= length; ++i )
        {
            chars_[ i ] = 0;
        }
    }
    else
    {
        if( maxCopyLength == NO_POS || maxCopyLength > length )
        {
            maxCopyLength = length;
        }
        memmove( chars_, str, maxCopyLength );
        if( maxCopyLength < length )
        {
            for( size_t i = maxCopyLength; i <= length; ++i )
            {
                chars_[ i ] = 0;
            }
        }
        else
        {
            chars_[ length ] = 0;
        }
    }

    length_ = length;

    return *this;

}

Str& Str::setInside( const char* str, const size_t offset, size_t copyLength )
{
    if( offset >= length_ )
    {
        return *this;
    }
    if( copyLength == NO_POS )
    {
        copyLength = strlen( str );
    }
    if( offset + copyLength > length_ )
    {
        copyLength = length_ - offset;
    }
    // memmove not working on the target, so we loop...
    for( size_t i = 0; i < copyLength; ++i )
    {
        chars_[offset + i] = str[i];
    }

    return *this;

}

Str& Str::operator =( const char * str )
{
    return set( str );
}

Str& Str::operator =( const Str & str )
{
    if( chars_ != NoChars_ )
    {
        delete[] chars_;
    }
    length_ = str.length_;
    if( str.chars_ == NoChars_ )
    {
        chars_ = NoChars_;
    }
    else
    {
        chars_ = new char[( ( length_ + 4 ) / 4 ) * 4 ];
        memcpy( chars_, str.chars_, length_ + 1 );
    }
    return *this;
}

/**
 *  returns the position of the specified character in the string,
 *  looking from the end of the string towards the beginning.
 *  \param str the character to look for
 *  \returns the index of the character, or NO_POS if not found
 */
size_t Str::findLastOf( const unsigned char str ) const
{
    size_t i = length_;
    for( ; i < NO_POS; --i )
    {
        if( str == chars_[ i ] )
        {
            break;
        }
    }
    return i;
}

/**
 *  returns the position of the specified character string in the string,
 *  looking from the end of the string towards the beginning.
 *  \param str the character string to look for
 *  \returns the index of the character, or NO_POS if not found
 */
size_t Str::findLastOf( const char* str ) const
{
    size_t strLen = strlen( str );
    size_t offset = length_ - strLen;
    for( ; strLen <= length_ && offset < NO_POS; --offset )
    {
        if( strncmp( str, chars_ + offset, strLen ) == 0 )
        {
            return offset;
        }
    }
    return NO_POS;
}

/**
 *  returns the position of the specified character string in the string,
 *  looking from the end of the string towards the beginning.
 *  \param str the character string to look for
 *  \returns the index of the character, or NO_POS if not found
 */
size_t Str::findLastOf( const Str& str ) const
{
    size_t offset = length_ - str.length_;
    for( ; str.length_ <= length_ && offset < NO_POS; --offset )
    {
        if( strncmp( str.cStr(), chars_ + offset, str.length_ ) == 0 )
        {
            return offset;
        }
    }
    return NO_POS;
}

size_t Str::find( const unsigned char str, size_t offset ) const
{
    for( ; offset < length_; ++offset )
    {
        if( str == chars_[ offset ] )
        {
            return offset;
        }
    }
    return NO_POS;
}

size_t Str::find( const char* str, size_t offset ) const
{
    size_t strLen = strlen( str );
    for( ; strLen <= length_ && offset <= length_ - strLen; ++offset )
    {
        if( strncmp( str, chars_ + offset, strLen ) == 0 )
        {
            return offset;
        }
    }
    return NO_POS;
}

size_t Str::find( const Str& str, size_t offset ) const
{
    if( str.length_ <= length_ )
    {
        for( ; offset <= length_ - str.length_; ++offset )
        {
            if( strncmp( str.chars_, chars_ + offset, str.length_ ) == 0 )
            {
                return offset;
            }
        }
    }
    return NO_POS;
}

size_t Str::FindLastOf( const char* str, const unsigned char theChar, size_t length )
{
    if( length ==  NO_POS )
    {
        length = strlen( str );
    }
    for( ; length > 0; --length )
    {
        if( str[ length - 1 ] == theChar )
        {
            return length - 1;
        }
    }
    return NO_POS;
}

size_t Str::Find( const char* str, const unsigned char theChar, size_t length )
{
    if( length == NO_POS )
    {
        length = strlen( str );
    }
    for( size_t offset = 0; offset < length; ++offset )
    {
        if( str[ offset ] == theChar )
        {
            return offset;
        }
    }
    return NO_POS;
}

bool Str::startsWith( const char* str, size_t length, bool ignoreCase ) const
{
    int match( -1 );
    if( NO_POS == length )
    {
        length = strlen( str );
    }

    match = ignoreCase ? strncasecmp( str, chars_, length ) : strncmp( str, chars_, length );

    return length <= length_ && 0 == match;
}

bool Str::setChar( size_t index, const unsigned char theChar )
{
    if( index >= length_ )
    {
        return false;
    }
    chars_[ index ] = theChar;
    return true;
}

unsigned int Str::IntToAscii( int value, char* str, unsigned int base, unsigned int bufSize, unsigned int baseOffset )
{
    unsigned int i = 0;
    if( 0 == base )
    {
        base = 10;
    }
    if( 1 == base )
    {
        base = 2;
    }
    if( value >= 0 && ( unsigned int )value < base )
    {
        if( bufSize > 1 )
        {
            str[i] = "0123456789ABCDEFGHIJKLMNOPQURSTUVXYZ"[value + baseOffset];
            ++i;
        }
        if( bufSize > 0 )
        {
            str[i] = 0;
        }
    }
    else
    {
        bool neg = value < 0;
        if( neg )
        {
            value = 0 - value;
        }
        for( ; 0 != value && i < bufSize - 1 ; ++i, value /= base )
        {
            str[i] = "0123456789ABCDEFGHIJKLMNOPQURSTUVXYZ"[( value % base ) + baseOffset];
        }
        if( neg && i < bufSize - 1 )
        {
            str[i] = '-';
            ++i;
        }
        str[i] = 0;
        for( unsigned int j = 0; i > 0 && j <= ( ( i - 1 ) >> 1 ) ; ++j )
        {
            unsigned char temp = str[j];
            str[j] = str[ i - j - 1 ];
            str[ i - j - 1 ] = temp;
        }
    }
    return i;
}

double* Str::ToDoubleArray( const char* cstr, size_t length, int& rank, int& m, int& n, int& o )
{
    size_t ii;
    int i, j, k, idx, depth, mm = 1, nn = 1, oo = 1, nlen = 0;
    const char* nptr;
    char ch;
    double* retVal;
    rank = m = n = o = 0;

    // If not an array return m single value;
    if( length == 0 || ( Str::Find( cstr, ',', length ) == Str::NO_POS && cstr[0] != '[' ) )
    {
        retVal = new double[1];
        retVal[0] = length == 0 ? nanf( "" ) : ToDouble( cstr, length );
        return retVal;
    }

    // Account for 1-d arrays that can omit the wrapping "[]"
    rank = depth = ( cstr[0] == '[' ? 0 : 1 );

    // Figure out the size of the array
    for( ii = 0; ii < length; ++ii )
    {
        ch = cstr[ii];
        switch( ch )
        {
        case '[' :
            if( depth < 3 ) ++ depth;
            if( depth > rank ) rank = depth;
            switch( depth )
            {
            case 1:
                mm = 1;
                if( mm > m ) m = nn;
                break;
            case 2:
                nn = 1;
                if( nn > n ) n = nn;
                break;
            case 3:
                oo = 1;
                if( oo > o ) o = oo;
                break;
            }
            break;
        case ']' :
            if( depth > 0 ) -- depth;
            break;
        case ',' :
            switch( depth )
            {
            case 1:
                ++mm;
                if( mm > m ) m = mm;
                break;
            case 2:
                ++nn;
                if( nn > n ) n = nn;
                break;
            case 3:
            default:
                ++oo;
                if( oo > o ) o = oo;
                break;
            }
            break;
        }
    }

    // Init the array
    retVal = new double[ m * ( n > 0 ? n : 1 ) * ( o > 0 ? o : 1 )];
    for( i = 0; i < m || i == 0; ++i )
    {
        for( j = 0; j < n || j == 0; ++j )
        {
            for( k = 0; k < o || k == 0; ++k )
            {
                switch( rank )
                {
                case 1:
                    idx = i;
                    break;
                case 2:
                    idx = i * n + j;
                    break;
                case 3:
                default:
                    idx = i * n * o + j * o + k;
                    break;
                }
                retVal[ idx ] = nanf( "" );
            }
        }
    }

    // Fill the array, ignoring blanks which will be left NaN.
    depth = ( cstr[0] == '[' ? 0 : 1 );
    mm = 0;
    nn = oo = 0;
    nptr = cstr;
    nlen = 0;
    for( ii = 0; ii < length; ++ii )
    {
        // Recalculate the index of the array pointer at each step.
        switch( rank )
        {
        case 1:
            idx = mm;
            break;
        case 2:
            idx = mm * n + nn;
            break;
        case 3:
        default:
            idx = mm * n * o + nn * o + oo;
            break;
        }
        ch = cstr[ii];
        switch( ch )
        {
        case '[' :
            ++ depth;
            switch( depth )
            {
            case 2:
                nn = 0;
                break;
            case 3:
            default:
                oo = 0;
                break;
            }
            nptr = cstr + ii + 1;
            nlen = 0;
            break;
        case ']' :
            if( ii > 0 && nlen > 0 && cstr[ii - 1] != ']' )
            {
                retVal[ idx ] = ToDouble( nptr, nlen );
            }
            nlen = 0;
            -- depth;
            break;
        case ',' :
            if( ii > 0 && nlen > 0 && cstr[ii - 1] != ']' )
            {
                retVal[ idx ] = ToDouble( nptr, nlen );
            }
            switch( depth )
            {
            case 1:
                ++mm;
                break;
            case 2:
                ++nn;
                break;
            case 3:
            default:
                ++oo;
                break;
            }
            nptr = cstr + ii + 1;
            nlen = 0;
            break;
        default:
            ++nlen;
        }
        if( ii == length - 1 && nlen > 0 )
        {
            retVal[ idx ] = ToDouble( nptr, nlen );
        }
    }
    return retVal;
}

double Str::ToDouble( const char* str, size_t len )
{
    if( len == NO_POS )
    {
        len = strlen( str );
    }

    if( len == 0 || str[0] == 'n' )
    {
        return nanf( "" );
    }
    else
    {
        return strtof( str, NULL );
    }
}

void Str::replaceChar( const char replace, const char replaceWith )
{
    for( unsigned int i = 0; i < length_; ++i )
    {
        if( chars_[i] == replace ) chars_[i] = replaceWith;
    }
}

Str& Str::toLower()
{
    for( size_t i = 0; i < length_; ++i )
    {
        chars_[i] = tolower( chars_[i] );
    }
    return *this;
}

Str& Str::toUpper()
{
    for( size_t i = 0; i < length_; ++i )
    {
        chars_[i] = toupper( chars_[i] );
    }
    return *this;
}

Str* Str::split( int& num, const char* str, size_t length ) const
{
    if( NO_POS == length )
    {
        length = strlen( str );
    }
    num = 1;
    size_t splitAt = 0;
    while( NO_POS != ( splitAt = find( str, splitAt ) ) )
    {
        ++num;
        splitAt += length;
    }
    Str* parts = new Str[num];

    int index = 0;
    size_t splitFrom = 0;
    size_t splitTo;
    while( true )
    {
        splitTo = find( str, splitFrom );
        if( NO_POS == splitTo )
        {
            splitTo = length_;
        }
        parts[index] = Str( chars_, splitFrom, splitTo - splitFrom );
        if( length_ == splitTo )
        {
            break;
        }
        ++index;
        splitFrom = splitTo + length;
    }

    return parts;
}

/// Return a new Str, with a hex representation of each character of this string
Str Str::asHex() const
{
    Str newStr( ( char* )NULL, length_ * 2 );
    for( size_t i = 0; i < length_; ++i )
    {
        newStr.chars_[i * 2] = "0123456789ABCDEF"[( chars_[i] & 0x00F0 ) >> 4];
        newStr.chars_[i * 2 + 1] = "0123456789ABCDEF"[chars_[i] & 0x000F];
    }
    return newStr;
}

void Str::Checksum( unsigned char& csum, const char *chars, size_t length )
{
    if( length == NO_POS )
    {
        length = strlen( chars );
    }
    for( size_t i = 0; i < length; ++i )
    {
        csum ^= ( unsigned char )chars[i];
    }
}

bool Str::NmeaChecksum( unsigned char& csum, const char *chars, size_t length )
{
    if( length == 0 )
    {
        return false;
    }
    if( length == NO_POS )
    {
        length = strlen( chars );
    }

    size_t star = Find( chars, '*', length );
    size_t dollar = Find( chars, '$', length );

    if( dollar == Str::NO_POS )
    {
        dollar = Find( chars, '!', length );
    }

    if( dollar == Str::NO_POS )
    {
        return false;
    }

    if( star == Str::NO_POS )
    {
        star = length;
    }

    for( size_t i = dollar + 1; i < star; ++i )
    {
        csum ^= ( unsigned char )chars[i];
    }
    return true;
}

bool Str::NmeaValidate( char *chars, size_t length )
{
    if( length == 0 )
    {
        return false;
    }
    if( length == NO_POS )
    {
        length = strlen( chars );
    }

    size_t star = Find( chars, '*', length );

    if( star == Str::NO_POS || star + 2 > length )
    {
        return false;
    }

    unsigned char csum = 0;
    if( !NmeaChecksum( csum, chars, length ) )
    {
        return false;
    }

    char const* hex = "0123456789ABCDEF";
    char c1 = hex[( csum >> 4 ) & 0xF];
    char c2 = hex[csum & 0xF];

    if( c1 != chars[star + 1] || c2 != chars[star + 2] )
    {
        return false;
    }
    return true;
}

void Str::intoString( const double var, unsigned int precision )
{
    char str[ 32 ];
    snprintf( str, 31, "%.*f", precision, var );
    size_t length( strlen( str ) );
    if( chars_ == NoChars_ || ( chars_ != NoChars_ && length_ != length ) )
    {
        if( chars_ != NoChars_ ) delete[] chars_;
        length_ = length;
        chars_ = new char[( ( length_ + 4 ) / 4 ) * 4 ];
    }
    strncpy( chars_, str, length_ + 1 );
}

void Str::intoString( int var, unsigned int radix, unsigned int radixOffset )
{
    char str[ 32 ];
    size_t length( IntToAscii( var, str, radix, 32, radixOffset ) );
    if( chars_ == NoChars_ || ( chars_ != NoChars_ && length_ != length ) )
    {
        if( chars_ != NoChars_ ) delete[] chars_;
        length_ = length;
        chars_ = new char[( ( length_ + 4 ) / 4 ) * 4 ];
    }
    strncpy( chars_, str, length_ + 1 );
}

/// implode/join
Str& Str::appendJoin( const Str** arr, int num, const Str& joint )
{
    int i;
    size_t idx = length_;
    char* prefix = NoChars_;
    if( chars_ != NoChars_ )
    {
        prefix = chars_;
    }
    for( i = 0; i < num; ++i )
    {
        length_ += arr[i]->length_;
        if( i + 1 < num )
        {
            length_ += joint.length_;
        }
    }
    chars_ = new char[( ( length_ + 4 ) / 4 ) * 4 ];
    if( prefix != NoChars_ )
    {
        memcpy( chars_, prefix, idx );
    }
    for( i = 0; i < num; ++i )
    {
        memcpy( chars_ + idx, arr[i]->chars_, arr[i]->length_ );
        idx += arr[i]->length_;
        if( i + 1 < num )
        {
            memcpy( chars_ + idx, joint.chars_, joint.length_ );
            idx += joint.length();
        }
    }
    return *this;
}
