#ifndef CORE_ARRAY_STRING_HPP
#define CORE_ARRAY_STRING_HPP

#include <string.h>

#include "core/utils/Error.hpp"
#include "core/utils/Meta.hpp"
#include "core/utils/Types.hpp"
#include "core/utils/Utility.hpp"

namespace Core {
    class BufferString {
    protected:
        size_t length;
        size_t capacity;
        char* data;

    public:
        BufferString(char* buffer, size_t bufferSize);
        BufferString(const BufferString&) = delete;
        BufferString& operator=(const BufferString&);
        BufferString(BufferString&&) = delete;
        BufferString& operator=(BufferString&&) = delete;
        bool operator==(const char* s) const;
        bool operator==(const BufferString& other) const;
        bool operator!=(const char* s) const;
        bool operator!=(const BufferString& other) const;
        char operator[](size_t index) const;
        size_t getLength() const;
        size_t getCapacity() const;
        BufferString& append(char c);
        BufferString& append(signed char c);
        BufferString& append(unsigned char c);
        BufferString& append(wchar_t c);
        BufferString& append(c32 c);
        BufferString& append(const char* s);
        BufferString& append(const c32* s);
        BufferString& append(const signed char* s);
        BufferString& append(const unsigned char* s);
        BufferString& append(bool b);

        template<typename T>
        BufferString& append(const T& t) {
            if constexpr(requires { t.toString(*this); }) {
                (void)t.toString(*this);
            } else if constexpr(requires { static_cast<const char*>(t); }) {
                append(static_cast<const char*>(t));
            } else {
                char buffer[64];
                Core::toString(t, buffer, sizeof(buffer));
                append(static_cast<const char*>(buffer));
            }
            return *this;
        }

        void toString(BufferString& s) const;
        void clear();
        void print() const;
        void printLine() const;

        template<typename... Args>
        BufferString& format(BufferString& s, Args&&... args) {
            if constexpr(sizeof...(args) > 0) {
                formatR(s, 0, forward<Args>(args)...);
                *this = s;
            }
            return *this;
        }

        bool startsWith(const BufferString& other, size_t from = 0) const;
        size_t search(const BufferString& other, size_t from = 0) const;
        bool contains(const BufferString& other, size_t from = 0) const;
        size_t search(char u, size_t from = 0) const;
        bool contains(char u, size_t from = 0) const;
        void substring(BufferString& s, size_t from, size_t to) const;
        void substring(BufferString& s, size_t from = 0) const;
        void replace(BufferString& s, const BufferString& search,
                     const BufferString& replace);
        void replace(char search, char replace);
        operator const char*() const;

    private:
        template<typename T, typename... Args>
        void formatR(BufferString& s, size_t index, const T& t,
                     Args&&... args) const {
            size_t l = getLength();
            while(index < l) {
                char u = data[index++];
                if(u == '#') {
                    if(index >= l || data[index] != '#') {
                        break;
                    }
                    index++;
                }
                s.append(u);
            }
            s.append(t);
            if constexpr(sizeof...(args) > 0) {
                formatR(s, index, forward<Args>(args)...);
                return;
            }
            while(index < l) {
                s.append(data[index++]);
            }
        }
    };

    template<size_t N>
    class ArrayString final : public BufferString {
        char data[N];

    public:
        ArrayString() : BufferString(data, N) {
        }

        ArrayString(const ArrayString& other) : BufferString(data, N) {
            memcpy(data, other.data, sizeof(data));
            length = other.length;
        }

        ArrayString& operator=(const ArrayString& other) {
            if(this != &other) {
                memcpy(data, other.data, sizeof(data));
                length = other.length;
            }
            return *this;
        }

        template<typename... Args>
        ArrayString& format(Args&&... args) {
            ArrayString s;
            BufferString::format(s, Core::forward<Args>(args)...);
            return *this;
        }

        void replace(const BufferString& search, const BufferString& replace) {
            ArrayString s;
            BufferString::replace(s, search, replace);
        }

        using BufferString::replace;
    };

    template<typename String, typename Iterable>
    void toString(String& s, const Iterable& i) {
        s.append("[");
        auto current = i.begin();
        auto end = i.end();
        while(current != end) {
            s.append(*current);
            ++current;
            if(current != end) {
                s.append(", ");
            }
        }
        s.append("]");
    }
}

inline bool operator==(const char* cs, const Core::BufferString& s) {
    return s == cs;
}

inline bool operator!=(const char* cs, const Core::BufferString& s) {
    return s != cs;
}

#endif