/*
 * 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.h - 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)
 *
 * 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
 * ----------------------------------------------------------------------------------
 */


#ifndef ECXX_PLATFORM_NODEMCU_WSTRING_H
#define ECXX_PLATFORM_NODEMCU_WSTRING_H


#include "CoreUtil.h"


//
// For compatibility with the Arduino library part
//
#define FPSTR(X) ( reinterpret_cast<const __FlashStringHelper*>(X) )


namespace eCxx {

namespace NodeMCU {


// An inherited class for holding the result of a concatenation;
// these result objects are assumed to be writable by subsequent concatenations
class WStringSumHelper;


class WString : public SafeOperatorBool<WString> {
    private:

    public:
        inline WString() __ECXX_ATTR_ALWAYS_INLINE
        { init(); }

        WString(const char* str);
        WString(const __FlashStringHelper* str);
        WString(PGMCharPointer str);
        WString(const WString& str);
        WString(WString&& rval) noexcept;
        WString(WStringSumHelper&& rval) noexcept;

        inline explicit WString(char c)
        {
            sso.buff[0] = c;
            sso.buff[1] = 0;
            sso.len     = 1;
            sso.isHeap  = 0;
        }

        explicit WString(unsigned char value, uint8_t base = 10);
        explicit WString(         int  value, uint8_t base = 10);
        explicit WString(unsigned int  value, uint8_t base = 10);
        explicit WString(         long value, uint8_t base = 10);
        explicit WString(unsigned long value, uint8_t base = 10);
        explicit WString(float         value, uint8_t decimalPlaces = 2);
        explicit WString(double        value, uint8_t decimalPlaces = 2);

        inline ~WString() __ECXX_ATTR_ALWAYS_INLINE
        { invalidate(); }

        bool reserve(size_t size);

        inline size_t length() const
        { return buffer() ? len() : 0; }

        inline void clear()
        { setLen(0); }

        inline bool isEmpty() const
        { return length() == 0; }

        const WString& operator=(const char* str);
        const WString& operator=(const __FlashStringHelper* str);
        const WString& operator=(PGMCharPointer str);
        const WString& operator=(const WString& str);
        const WString& operator=(WString&& str) noexcept;
        const WString& operator=(WStringSumHelper&& str) noexcept;

        bool concat(char           c  );
        bool concat(unsigned char  c  );
        bool concat(int            num);
        bool concat(unsigned int   num);
        bool concat(long           num);
        bool concat(unsigned long  num);
        bool concat(float          num);
        bool concat(double         num);

        bool concat(const char* str, size_t length);
        bool concat(const __FlashStringHelper* str, size_t length);
        bool concat(PGMCharPointer str, size_t length);

        bool concat(const char* str);
        bool concat(const __FlashStringHelper* str);
        bool concat(PGMCharPointer str);

        bool concat(const WString& str);

        inline WString& operator+=(const WString& str) { concat(str); return *this; }
        inline WString& operator+=(const char*    str) { concat(str); return *this; }
        inline WString& operator+=(char           c  ) { concat(c  ); return *this; }
        inline WString& operator+=(unsigned char  num) { concat(num); return *this; }
        inline WString& operator+=(int            num) { concat(num); return *this; }
        inline WString& operator+=(unsigned int   num) { concat(num); return *this; }
        inline WString& operator+=(long           num) { concat(num); return *this; }
        inline WString& operator+=(unsigned long  num) { concat(num); return *this; }
        inline WString& operator+=(float          num) { concat(num); return *this; }
        inline WString& operator+=(double         num) { concat(num); return *this; }

        inline WString& operator+=(const __FlashStringHelper* str) { concat(str); return *this; }
        inline WString& operator+=(PGMCharPointer             str) { concat(str); return *this; }

        friend WStringSumHelper& operator+(const WStringSumHelper &lhs, const WString& rhs);
        friend WStringSumHelper& operator+(const WStringSumHelper &lhs, const char*    rhs);
        friend WStringSumHelper& operator+(const WStringSumHelper &lhs, char           c  );
        friend WStringSumHelper& operator+(const WStringSumHelper &lhs, unsigned char  num);
        friend WStringSumHelper& operator+(const WStringSumHelper &lhs, int            num);
        friend WStringSumHelper& operator+(const WStringSumHelper &lhs, unsigned int   num);
        friend WStringSumHelper& operator+(const WStringSumHelper &lhs, long           num);
        friend WStringSumHelper& operator+(const WStringSumHelper &lhs, unsigned long  num);
        friend WStringSumHelper& operator+(const WStringSumHelper &lhs, float          num);
        friend WStringSumHelper& operator+(const WStringSumHelper &lhs, double         num);

        friend WStringSumHelper& operator+(const WStringSumHelper &lhs, const __FlashStringHelper* rhs);
        friend WStringSumHelper& operator+(const WStringSumHelper &lhs, PGMCharPointer rhs);

        int32_t compareTo(const WString &s) const;

        // Used by SafeOperatorBool<T>
        inline bool operatorBool_impl() const
        { return !!buffer(); }

        bool equals(const char*    str) const;
        bool equals(const WString& str) const;

        inline bool operator==(const char*    str) const { return  equals(str); }
        inline bool operator==(const WString& str) const { return  equals(str); }
        inline bool operator!=(const char*    str) const { return !equals(str); }
        inline bool operator!=(const WString& str) const { return !equals(str); }

        inline bool operator< (const WString &str) const { return compareTo(str) <  0; }
        inline bool operator> (const WString &str) const { return compareTo(str) >  0; }
        inline bool operator<=(const WString &str) const { return compareTo(str) <= 0; }
        inline bool operator>=(const WString &str) const { return compareTo(str) >= 0; }

        bool equalsIgnoreCase(const WString &s) const;
        bool equalsConstantTime(const WString &s) const;

        bool startsWith(const WString &prefix) const;
        bool startsWith(const WString &prefix, size_t offset) const;

        inline bool startsWith(const char*                prefix) const { return this->startsWith( WString(prefix) ); }
        inline bool startsWith(const __FlashStringHelper* prefix) const { return this->startsWith( WString(prefix) ); }
        inline bool startsWith(PGMCharPointer             prefix) const { return this->startsWith( WString(prefix) ); }

        bool endsWith(const WString &suffix) const;

        inline bool endsWith(const char*                suffix) const { return this->endsWith( WString(suffix) ); }
        inline bool endsWith(const __FlashStringHelper* suffix) const { return this->endsWith( WString(suffix) ); }
        inline bool endsWith(PGMCharPointer             suffix) const { return this->endsWith( WString(suffix) ); }

        // Character access
        inline char charAt(size_t index) const
        { return operator[](index); }

        inline void setCharAt(size_t index, char c)
        { if( index < len() ) wbuffer()[index] = c; }

        char operator[](size_t index) const;
        char& operator[](size_t index);

        void getBytes(uint8_t* buf, size_t bufsize, size_t index = 0) const;

        inline void toCharArray(char* buf, size_t bufsize, size_t index = 0) const
        { getBytes(reinterpret_cast<uint8_t*>(buf), bufsize, index); }

        const char* c_str() const { return buffer(); }

        char* begin() { return wbuffer();            }
        char* end()   { return wbuffer() + length(); }

        const char* begin() const { return c_str();            }
        const char* end()   const { return c_str() + length(); }

        // Search
        int32_t indexOf(char                       c,   size_t fromIndex) const;
        int32_t indexOf(const char*                str, size_t fromIndex) const;
        int32_t indexOf(const __FlashStringHelper* str, size_t fromIndex) const;
        int32_t indexOf(PGMCharPointer             str, size_t fromIndex) const;
        int32_t indexOf(const WString&             str, size_t fromIndex) const;

        inline int32_t indexOf(char                       c  ) const { return indexOf(c,   0); }
        inline int32_t indexOf(const char*                str) const { return indexOf(str, 0); }
        inline int32_t indexOf(const __FlashStringHelper* str) const { return indexOf(str, 0); }
        inline int32_t indexOf(PGMCharPointer             str) const { return indexOf(str, 0); }
        inline int32_t indexOf(const WString&             str) const { return indexOf(str, 0); }

        int32_t lastIndexOf(char           c,   size_t fromIndex) const;
        int32_t lastIndexOf(const WString& str, size_t fromIndex) const;

        inline int32_t lastIndexOf(char           c  ) const { return lastIndexOf( c,   len() - 1         ); }
        inline int32_t lastIndexOf(const WString& str) const { return lastIndexOf( str, len() - str.len() ); }

        const WString substring(size_t beginIndex, size_t endIndex) const;

        inline const WString substring(size_t beginIndex) const
        { return substring( beginIndex, len() ); }

        // Modification
        void replace(char find, char replace);
        bool replace(const WString& find, const WString& replace);

        inline bool replace(const char*    find, const WString& replace) { return this->replace( WString(find),         replace  ); }
        inline bool replace(const WString& find, const char*    replace) { return this->replace(         find,  WString(replace) ); }
        inline bool replace(const char*    find, const char*    replace) { return this->replace( WString(find), WString(replace) ); }

        inline bool replace(const __FlashStringHelper* find, const WString&             replace) { return this->replace( WString(find),         replace  ); }
        inline bool replace(const WString&             find, const __FlashStringHelper* replace) { return this->replace(         find,  WString(replace) ); }
        inline bool replace(const __FlashStringHelper* find, const __FlashStringHelper* replace) { return this->replace( WString(find), WString(replace) ); }

        inline bool replace(PGMCharPointer find, const WString& replace) { return this->replace( WString(find),         replace  ); }
        inline bool replace(const WString& find, PGMCharPointer replace) { return this->replace(         find,  WString(replace) ); }
        inline bool replace(PGMCharPointer find, PGMCharPointer replace) { return this->replace( WString(find), WString(replace) ); }

        void remove(size_t index, size_t count);

        inline void remove(size_t index)
        { remove( index, static_cast<size_t>(-1) ); }

        void toLowerCase();
        void toUpperCase();
        void trim();

        // Parsing/conversion
        long toInt() const;
        float toFloat() const;
        double toDouble() const;

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

    protected:
        // Contains the string info when we're not in SSO mode
        struct _ptr {
            char*    buff;
            uint16_t cap;
            uint16_t len;
        };

        // This allows strings up up to 11 (10 + \0 termination) without any extra space
        enum {
            SSOSIZE = sizeof(struct _ptr) + 4 - 1
        }; // Characters to allocate space for SSO, must be 12 or more

        struct _sso {
            char          buff[SSOSIZE];
            unsigned char len    : 7; // Ensure only one byte is allocated by GCC for the bitfields
            unsigned char isHeap : 1;
        } __ECXX_ATTR_PACKED; // Ensure that GCC doesn't expand the flag byte to a 32-bit word for alignment issues

        // If typeof(cap) changed from uint16_t, be sure to update this enum to the max value storable in the type
        enum { CAPACITY_MAX = 65535 };

        union {
            struct _ptr ptr;
            struct _sso sso;
        };

        // Accessor functions
        inline bool isSSO() const
        { return !sso.isHeap; }

        inline size_t len() const
        { return isSSO() ? sso.len : ptr.len; }

        inline size_t capacity() const
        { return isSSO() ? (SSOSIZE - 1) : ptr.cap; } // Size of maximum string not including null terminator

        inline void setSSO(bool set)
        { sso.isHeap = !set; }

        inline void setLen(size_t len)
        {
            if( isSSO() ) {
                setSSO(true); // Avoid emitting of bitwise EXTRACT-AND-OR ops (store-merging optimization)
                sso.len = len;
            }
            else {
                ptr.len = len;
            }
        }

        inline void setCapacity(size_t cap)
        { if( !isSSO() ) ptr.cap = cap; }

        inline void setBuffer(char* buff)
        { if( !isSSO() ) ptr.buff = buff; }

        // Buffer accessor functions
        inline char* wbuffer() const
        { return isSSO() ? const_cast<char*>(sso.buff) : ptr.buff; } // Writable version of buffer

        inline const char* buffer() const
        { return wbuffer(); }

    protected:
        inline void init() __ECXX_ATTR_ALWAYS_INLINE
        {
            sso.buff[0] = 0;
            sso.len     = 0;
            sso.isHeap  = 0;

            /*
             * Without these 6 statements, GCC simply emits such as:
             *     MOVI.N aX,0
             *     S8I    aX,a2,0
             *     S8I    aX,a2,11
             * (8 bytes in total)
             *
             * With these 6 statements and thanks to store-merging,
             * GCC can use the narrow form of 32-bit store and emits:
             *     MOVI.N aX,0
             *     S32I.N aX,a2,0
             *     S32I.N aX,a2,8
             * (6 bytes in total)
             */
            sso.buff[ 1] = 0;
            sso.buff[ 2] = 0;
            sso.buff[ 3] = 0;
            sso.buff[ 8] = 0;
            sso.buff[ 9] = 0;
            sso.buff[10] = 0;
        }

        void invalidate(void);
        bool changeBuffer(size_t maxStrLen);

        const WString& copy(const char* str, size_t length);
        const WString& copy(const __FlashStringHelper* str, size_t length);
        const WString& copy(PGMCharPointer str, size_t length);

        void move(WString &str);
};


class WStringSumHelper: public WString {
    public:
        inline WStringSumHelper(const WString&             s  ) : WString(s  ) {}
        inline WStringSumHelper(const char*                p  ) : WString(p  ) {}
        inline WStringSumHelper(const __FlashStringHelper* p  ) : WString(p  ) {}
        inline WStringSumHelper(PGMCharPointer             p  ) : WString(p  ) {}
        inline WStringSumHelper(         char              c  ) : WString(c  ) {}
        inline WStringSumHelper(unsigned char              num) : WString(num) {}
        inline WStringSumHelper(         int               num) : WString(num) {}
        inline WStringSumHelper(unsigned int               num) : WString(num) {}
        inline WStringSumHelper(         long              num) : WString(num) {}
        inline WStringSumHelper(unsigned long              num) : WString(num) {}
        inline WStringSumHelper(float                      num) : WString(num) {}
        inline WStringSumHelper(double                     num) : WString(num) {}
};


} // namespace NodeMCU

} // namespace eCxx


//
// BasicOutputWriter<ImplementationT> member function implementation for NodeMCU::WString
//
template <typename ImplementationT>
inline void eCxx::BasicOutputWriter<ImplementationT>::writeString(const NodeMCU::WString& str)
{ writeString( str.c_str() ); }


template <typename ImplementationT>
inline void eCxx::BasicOutputWriter<ImplementationT>::writeStringln(const NodeMCU::WString& str)
{ writeStringln( str.c_str() ); }


//
// For compatibility with the Arduino library part
//
typedef eCxx::NodeMCU::WString String;

// Declaration of the global instance for the empty string object
extern const String emptyString;


#endif
