/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */

/*
 * ----------------------------------------------------------------------------------
 * The code in this file is copied (with a few modifications) from:
 *     WString.cpp - string library for Wiring & Arduino
 * ----------------------------------------------------------------------------------
 *
 * ... mostly rewritten by Paul Stoffregen ...
 *
 * Copyright (C) 2009-2010 Hernando Barragan. All rights reserved.
 * Copyright (C) 2011      Paul Stoffregen (paul@pjrc.com)
 *
 * Modified 2014 by Ivan Grokhotkov   - esp8266 support
 * Modified 2015 by Michael C. Miller - esp8266 progmem support
 *
 * This library is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any later
 * version.
 *
 * This library 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this library; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 * ----------------------------------------------------------------------------------
 */


#include <stdlib.h>

#include "CoreUtil.h"
#include "WString.h"


namespace eCxx {

namespace NodeMCU {


WString::WString(const char* str)
{
    init();
    if(str) copy( str, strlen(str) );
}


WString::WString(const __FlashStringHelper* str)
{
    init();
    *this = str;
}


WString::WString(PGMCharPointer str)
{
    init();
    *this = str;
}


WString::WString(const WString& value)
: SafeOperatorBool<WString>()
{
    init();
    *this = value;
}


WString::WString(WString&& rval) noexcept
: SafeOperatorBool<WString>()
{
    init();
    move(rval);
}


WString::WString(WStringSumHelper&& rval) noexcept
{
    init();
    move(rval);
}


WString::WString(unsigned char value, uint8_t base)
{
    init();

    char buf[ 1 + 8 * sizeof(value) ];
    utoa(value, buf, base);

    *this = buf;
}


WString::WString(int value, uint8_t base)
{
    init();

    char buf[ 2 + 8 * sizeof(value) ];
    if(base == 10) sprintf(buf, "%d", value);
    else           itoa(value, buf, base);

    *this = buf;
}


WString::WString(unsigned int value, uint8_t base)
{
    init();

    char buf[ 1 + 8 * sizeof(value) ];
    utoa(value, buf, base);

    *this = buf;
}


WString::WString(long value, uint8_t base)
{
    init();

    char buf[ 2 + 8 * sizeof(value) ];
    if(base == 10) sprintf(buf, "%ld", value);
    else           ltoa(value, buf, base);

    *this = buf;
}


WString::WString(unsigned long value, uint8_t base)
{
    init();

    char buf[ 1 + 8 * sizeof(value) ];
    ultoa(value, buf, base);

    *this = buf;
}


WString::WString(float value, uint8_t decimalPlaces)
{
    init();

    char buf[33];
    *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf);
}


WString::WString(double value, uint8_t decimalPlaces)
{
    init();

    char buf[33];
    *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf);
}


bool WString::reserve(size_t size)
{
    if( buffer() && capacity() >= size ) return true;

    if( changeBuffer(size) ) {
        if(len() == 0) wbuffer()[0] = false;
        return true;
    }

    return false;
}


void WString::invalidate()
{
    if( !isSSO() && wbuffer() ) free( wbuffer() );

    init();
}


bool WString::changeBuffer(size_t maxStrLen)
{
    // Use SSO here to avoid allocation?
    if( maxStrLen < sizeof(sso.buff) - 1 ) {
        if( isSSO() || !buffer() ) {
            // Already using SSO, nothing to do
            const uint16_t oldLen = len();
            setSSO(true);
            setLen(oldLen);
        }
        else {
            // Convert to SSO
            const char*    temp   = buffer();
            const uint16_t oldLen = len();
            setSSO(true);
            setLen(oldLen);
            memcpy(wbuffer(), temp, maxStrLen);
            free( const_cast<char*>(temp) );
        }
        return true;
    }

    //
    // Fallthrough to normal allocator
    //

    // Calculate and check the size
    const size_t newSize = (maxStrLen + 16) & (~0x0F);

    if(newSize > CAPACITY_MAX) return false;

    // Allocate memory
    const uint16_t oldLen    = len();
          char*    newbuffer = reinterpret_cast<char*>( realloc(isSSO() ? nullptr : wbuffer(), newSize) );

    if(newbuffer) {
        const size_t oldSize = capacity() + 1;  // Include null terminator

        if( isSSO() ) {
            // Copy the SSO buffer into allocated space
            memmove( newbuffer, sso.buff, sizeof(sso.buff) );
        }

        if(newSize > oldSize) {
            memset(newbuffer + oldSize, 0, newSize - oldSize);
        }

        setSSO(false);
        setCapacity(newSize - 1);
        setLen(oldLen); // Needed in case of SSO where len() never existed
        setBuffer(newbuffer);
        return true;
    }

    return false;
}


const WString& WString::copy(const char* str, size_t length)
{
    if( !reserve(length) ) {
        invalidate();
        return *this;
    }

    setLen(length);
    memmove(wbuffer(), str, length + 1);

    return *this;
}


const WString& WString::copy(const __FlashStringHelper* str, size_t length)
{
    if( !reserve(length) ) {
        invalidate();
        return *this;
    }

    setLen(length);
    memcpy_P(wbuffer(), str, length + 1);

    return *this;
}


const WString& WString::copy(PGMCharPointer str, size_t length)
{ return copy( reinterpret_cast<const __FlashStringHelper*>(str.p), length ); }


void WString::move(WString& str) noexcept
{
    invalidate();
    sso = str.sso;

    str.init();
}


const WString& WString::operator=(const char* str)
{
    if(str) copy( str, strlen(str) );
    else    invalidate();

    return *this;
}


const WString& WString::operator=(const __FlashStringHelper* str)
{
    if(str) copy( str, strlen_P( reinterpret_cast<const char*>(str) ) );
    else    invalidate();

    return *this;
}


const WString& WString::operator=(PGMCharPointer str)
{
    if(str.p) copy( str, strlen_P(str.p) );
    else      invalidate();

    return *this;
}


const WString& WString::operator=(const WString& str)
{
    if(this == &str) return *this;

    if( str.buffer() ) copy( str.buffer(), str.len() );
    else               invalidate();

    return *this;
}


const WString& WString::operator=(WString&& str) noexcept
{
    if(this != &str) move(str);

    return *this;
}


const WString& WString::operator=(WStringSumHelper&& str) noexcept
{
    if(this != &str) move(str);

    return *this;
}


bool WString::concat(const char* str, size_t length)
{
    const size_t newlen = len() + length;

    if( !str             ) return false;
    if( !length          ) return true;
    if(! reserve(newlen) ) return false;

    memmove(wbuffer() + len(), str, length + 1);
    setLen(newlen);
    wbuffer()[newlen] = 0;
    return true;
}


bool WString::concat(const __FlashStringHelper* str, size_t length)
{
    const size_t newlen = len() + length;

    if( !str             ) return false;
    if( !length          ) return true;
    if(! reserve(newlen) ) return false;

    memcpy_P(wbuffer() + len(), str, length + 1);
    setLen(newlen);
    wbuffer()[newlen] = 0;
    return true;
}


bool WString::concat(PGMCharPointer str, size_t length)
{ return concat( reinterpret_cast<const __FlashStringHelper*>(str.p), length ); }


bool WString::concat(const char* str)
{
    if(!str) return false;

    return concat( str, strlen(str) );
}


bool WString::concat(const __FlashStringHelper* str)
{
    if(!str) return false;

    return concat( str, strlen_P( reinterpret_cast<const char*>(str) ) );
}


bool WString::concat(PGMCharPointer str)
{ return concat( reinterpret_cast<const __FlashStringHelper*>(str.p) ); }


bool WString::concat(const WString& s)
{
    if(&s == this) {
        const size_t newlen = 2 * len();

        if( !s.buffer()      ) return false;
        if( !s.len()         ) return true;
        if( !reserve(newlen) ) return false;

        memmove( wbuffer() + len(), buffer(), len() );
        setLen(newlen);
        wbuffer()[newlen] = 0;
        return true;
    }
    else {
        return concat( s.buffer(), s.len() );
    }
}


bool WString::concat(char c)
{ return concat(&c, 1); }


bool WString::concat(unsigned char num)
{
    char buf[1 + 3 * sizeof(num)];

    return concat( buf, sprintf(buf, "%d", num) );
}


bool WString::concat(int num)
{
    char buf[2 + 3 * sizeof(num)];

    return concat( buf, sprintf(buf, "%d", num) );
}


bool WString::concat(unsigned int num)
{
    char buf[1 + 3 * sizeof(num)];
    utoa(num, buf, 10);

    return concat( buf, strlen(buf) );
}


bool WString::concat(long num)
{
    char buf[2 + 3 * sizeof(num)];

    return concat( buf, sprintf(buf, "%ld", num) );
}


bool WString::concat(unsigned long num)
{
    char buf[1 + 3 * sizeof(num)];
    ultoa(num, buf, 10);

    return concat( buf, strlen(buf) );
}


bool WString::concat(float num)
{
    char buf[20];
    char* string = dtostrf(num, 4, 2, buf);

    return concat( string, strlen(string) );
}


bool WString::concat(double num)
{
    char buf[20];
    char* string = dtostrf(num, 4, 2, buf);

    return concat( string, strlen(string) );
}


WStringSumHelper& operator+(const WStringSumHelper& lhs, const WString& rhs)
{
    WStringSumHelper& a = const_cast<WStringSumHelper&>(lhs);

    if( !a.concat( rhs.buffer(), rhs.len() ) ) a.invalidate();

    return a;
}


WStringSumHelper& operator+(const WStringSumHelper& lhs, const char* rhs)
{
    WStringSumHelper& a = const_cast<WStringSumHelper&>(lhs);

    if( !rhs || !a.concat( rhs, strlen(rhs) ) ) a.invalidate();

    return a;
}


WStringSumHelper& operator+(const WStringSumHelper& lhs, char c)
{
    WStringSumHelper& a = const_cast<WStringSumHelper&>(lhs);

    if( !a.concat(c) ) a.invalidate();

    return a;
}


WStringSumHelper& operator+(const WStringSumHelper& lhs, unsigned char num)
{
    WStringSumHelper& a = const_cast<WStringSumHelper&>(lhs);

    if( !a.concat(num) ) a.invalidate();

    return a;
}


WStringSumHelper& operator+(const WStringSumHelper& lhs, int num)
{
    WStringSumHelper& a = const_cast<WStringSumHelper&>(lhs);

    if( !a.concat(num) ) a.invalidate();

    return a;
}


WStringSumHelper& operator+(const WStringSumHelper& lhs, unsigned int num)
{
    WStringSumHelper& a = const_cast<WStringSumHelper&>(lhs);

    if(!a.concat(num) ) a.invalidate();

    return a;
}


WStringSumHelper& operator+(const WStringSumHelper& lhs, long num)
{
    WStringSumHelper& a = const_cast<WStringSumHelper&>(lhs);

    if( !a.concat(num) ) a.invalidate();

    return a;
}


WStringSumHelper& operator+(const WStringSumHelper& lhs, unsigned long num)
{
    WStringSumHelper& a = const_cast<WStringSumHelper&>(lhs);

    if( !a.concat(num) ) a.invalidate();

    return a;
}


WStringSumHelper& operator+(const WStringSumHelper& lhs, float num)
{
    WStringSumHelper& a = const_cast<WStringSumHelper&>(lhs);

    if( !a.concat(num) ) a.invalidate();

    return a;
}


WStringSumHelper& operator+(const WStringSumHelper& lhs, double num) {
    WStringSumHelper& a = const_cast<WStringSumHelper&>(lhs);

    if( !a.concat(num) ) a.invalidate();

    return a;
}


WStringSumHelper& operator+(const WStringSumHelper& lhs, const __FlashStringHelper* rhs)
{
    WStringSumHelper& a = const_cast<WStringSumHelper&>(lhs);

    if( !a.concat(rhs) ) a.invalidate();

    return a;
}


WStringSumHelper& operator+(const WStringSumHelper& lhs, PGMCharPointer rhs)
{
    WStringSumHelper& a = const_cast<WStringSumHelper&>(lhs);

    if( !a.concat(rhs) ) a.invalidate();

    return a;
}


int32_t WString::compareTo(const WString& s) const
{
    if( !buffer() || !s.buffer() ) {
        if( s.buffer() && s.len() > 0 ) return 0 - *reinterpret_cast<const unsigned char*>( s.buffer() );
        if(   buffer() &&   len() > 0 ) return     *reinterpret_cast<const unsigned char*>(   buffer() );
        return 0;
    }

    return strcmp( buffer(), s.buffer() );
}


bool WString::equals(const char* str) const
{
    if( len() == 0 ) return str == nullptr || *str == 0;

    if( str == nullptr ) return buffer()[0] == 0;

    return strcmp( buffer(), str ) == 0;
}


bool WString::equals(const WString& str) const
{ return len() == str.len() && compareTo(str) == 0; }


bool WString::equalsIgnoreCase(const WString& str) const
{
    if(this == &str) return true;
    if( len() != str.len() ) return false;
    if( len() == 0 ) return true;

    const char *p1 = buffer();
    const char *p2 = str.buffer();

    while(*p1) {
        if( tolower(*p1++) != tolower(*p2++) ) return false;
    }

    return true;
}


bool WString::equalsConstantTime(const WString& str) const
 {
    // To avoid possible time-based attacks present function compares given strings in a constant time
    if( len() != str.len() ) return false;

    // At this point lengths are the same
    if( len() == 0 ) return true;

    // At this point lenghts are the same and non-zero
    const char* p1         = buffer();
    const char* p2         = str.buffer();
    uint32_t    equalchars = 0;
    uint32_t    diffchars  = 0;

    while(*p1) {
        if(*p1 == *p2) ++equalchars;
        else           ++diffchars;
        ++p1;
        ++p2;
    }

    // The following should force a constant time eval of the condition without a compiler logical shortcut
    const bool equalcond = ( equalchars == len() );
    const bool diffcond  = ( diffchars  == 0     );

    return (equalcond & diffcond); // Bitwise AND
}


bool WString::startsWith(const WString& str) const
{
    if( len() < str.len() ) return false;

    return startsWith(str, 0);
}


bool WString::startsWith(const WString& str, size_t offset) const
{
    if( offset > ( len() - str.len() ) || !buffer() || !str.buffer() ) return false;

    return strncmp( &buffer()[offset], str.buffer(), str.len() ) == 0;
}


bool WString::endsWith(const WString& str) const
{
    if( len() < str.len() || !buffer() || !str.buffer() ) return false;

    return strcmp( &buffer()[len() - str.len()], str.buffer() ) == 0;
}


char& WString::operator[](size_t index)
{
    static char dummyWritableChar;

    if( index >= len() || !buffer() ) {
        dummyWritableChar = 0;
        return dummyWritableChar;
    }

    return wbuffer()[index];
}


char WString::operator[](size_t index) const
{
    if( index >= len() || !buffer() ) return 0;

    return buffer()[index];
}


void WString::getBytes(uint8_t* buf, size_t bufsize, size_t index) const
{
    if( !bufsize || !buf ) return;

    if( index >= len() ) {
        buf[0] = 0;
        return;
    }

    size_t n = bufsize - 1;
    if(n > len() - index) n = len() - index;

    strncpy( reinterpret_cast<char*>(buf), buffer() + index, n );
    buf[n] = 0;
}


int32_t WString::indexOf(char c, size_t fromIndex) const
 {
    if( fromIndex >= len() ) return -1;

    const char* temp = strchr( buffer() + fromIndex, c );
    if(temp == nullptr) return -1;

    return temp - buffer();
}


int32_t WString::indexOf(const char* str, size_t fromIndex) const
{
    if( fromIndex >= len() ) return -1;

    const char* found = strstr( buffer() + fromIndex, str );
    if(found == nullptr) return -1;

    return found - buffer();
}


int32_t WString::indexOf(const __FlashStringHelper* str, size_t fromIndex) const
{
    if( fromIndex >= len() ) return -1;

    const char* found = strstr_P( buffer() + fromIndex, reinterpret_cast<const char*>(str) );
    if(found == nullptr) return -1;

    return found - buffer();
}


int32_t WString::indexOf(PGMCharPointer str, size_t fromIndex) const
{ return indexOf( reinterpret_cast<const __FlashStringHelper*>(str.p), fromIndex ); }


int32_t WString::indexOf(const WString& str, size_t fromIndex) const
{ return indexOf( str.c_str(), fromIndex ); }


int32_t WString::lastIndexOf(char c, size_t fromIndex) const
{
    if( fromIndex >= len() ) return -1;

    // Save the replaced character
    char*      writeTo  = wbuffer();
    const char tempchar = writeTo[fromIndex + 1];

    // Replace the character with null terminator
    writeTo[fromIndex + 1] = '\0';

    // Restore the replaced character
    char* temp = strrchr(writeTo, c);
    writeTo[fromIndex + 1] = tempchar;

    // Return the index
    if(temp == nullptr) return -1;

    return temp - writeTo;
}


int32_t WString::lastIndexOf(const WString& str, size_t fromIndex) const
{
    if( str.len() == 0 || len() == 0 || str.len() > len() ) return -1;

    if( fromIndex >= len() ) fromIndex = len() - 1;

    int32_t found = -1;

    for(const char *p = buffer(); p <= buffer() + fromIndex; ++p) {
        p = strstr( p, str.buffer() );
        if( !p ) break;
        if( static_cast<size_t>( p - buffer() ) <= fromIndex ) found = p - buffer();
    }

    return found;
}


const WString WString::substring(size_t left, size_t right) const
{
    if(left > right) eCxx::swap(left, right);

    WString out;
    if( left >= len() ) return out;

    if( right > len() ) right = len();

    // Save the replaced character
    char*      writeTo  = wbuffer();
    const char tempchar = writeTo[right];

    // Replace the character with null terminator
    writeTo[right] = '\0';

    // Generate the result string
    out = writeTo + left;

    // Restore the replaced character
    writeTo[right] = tempchar;

    // Return the result string
    return out;
}


void WString::replace(char find, char replace)
{
    if( !buffer() ) return;

    for(char *p = wbuffer(); *p; ++p) {
        if(*p == find) *p = replace;
    }
}


bool WString::replace(const WString& find, const WString& replace)
{
    if( len() == 0 || find.len() == 0 ) return false;

    int32_t diff     = replace.len() - find.len();
    char*   readFrom = wbuffer();
    char*   foundAt;

    if(diff == 0) {
        while( ( foundAt = strstr( readFrom, find.buffer() ) ) != nullptr ) {
            memmove( foundAt, replace.buffer(), replace.len() );
            readFrom = foundAt + replace.len();
        }
    }
    else if(diff < 0) {
        char* writeTo = wbuffer();
        while( ( foundAt = strstr( readFrom, find.buffer() ) ) != nullptr ) {
            const size_t n = foundAt - readFrom;

            memmove(writeTo, readFrom, n);
            writeTo += n;

            memmove( writeTo, replace.buffer(), replace.len() );
            writeTo += replace.len();

            readFrom = foundAt + find.len();
            setLen(len() + diff);
        }
        memmove( writeTo, readFrom, strlen(readFrom) + 1 );
    }
    else {
        size_t size = len(); // Compute size needed for result
        while( ( foundAt = strstr( readFrom, find.buffer() ) ) != nullptr ) {
            readFrom = foundAt + find.len();
            size += diff;
        }
        if( size == len() ) return false;
        if( size > capacity() && !changeBuffer(size) ) return false;

        int32_t index = len() - 1;

        while( index >= 0 && ( index = lastIndexOf(find, index) ) >= 0 ) {
            readFrom = wbuffer() + index + find.len();
            memmove( readFrom + diff, readFrom, len() - ( readFrom - buffer() ) );

            const int32_t newLen = len() + diff;
            memmove( wbuffer() + index, replace.buffer(), replace.len() );

            setLen(newLen);
            wbuffer()[newLen] = 0;
            --index;
        }
    }

    return true;
}


void WString::remove(size_t index, size_t count)
{
    if( index >= len() ) return;

    if( count <= 0 ) return;

    if(count > len() - index) count = len() - index;

    char* writeTo = wbuffer() + index;

    const size_t newlen = len() - count;

    setLen(newlen);
    memmove(writeTo, wbuffer() + index + count, newlen - index);
    wbuffer()[newlen] = 0;
}


void WString::toLowerCase()
{
    if( !buffer() ) return;

    for(char* p = wbuffer(); *p; ++p) *p = tolower(*p);
}


void WString::toUpperCase()
{
    if( !buffer() ) return;

    for(char *p = wbuffer(); *p; ++p)  *p = toupper(*p);
}


void WString::trim()
{
    if( !buffer() || len() == 0 ) return;

    char* begin = wbuffer();
    while( isspace(*begin) ) ++begin;

    char* end = wbuffer() + len() - 1;
    while( isspace(*end) && end >= begin ) --end;

    const size_t newlen = end + 1 - begin;

    setLen(newlen);
    if( begin > buffer() ) memmove(wbuffer(), begin, newlen);
    wbuffer()[newlen] = 0;
}


long WString::toInt() const
{
    if( buffer() ) return atol( buffer() );

    return 0;
}


float WString::toFloat() const
{
    if( buffer() ) return atof( buffer() );

    return 0.0f;
}


double WString::toDouble() const
{
    if( buffer() ) return atof( buffer() );

    return 0.0;
}


} // namespace NodeMCU

} // namespace eCxx


//
// Create a global instance for the empty string object in the global namespace
// for compatibility with the Arduino library part
//
const eCxx::NodeMCU::WString emptyString;
