#ifndef CORE_RINGBUFFER_H
#define CORE_RINGBUFFER_H

#include "utils/ArrayString.h"

namespace Core {
    template<typename T, int N>
    class RingBuffer final {
        static_assert(N > 0, "RingBuffer size must be positive");

        struct alignas(T) Aligned final {
            char data[sizeof(T)];
        };

        Aligned data[static_cast<unsigned int>(N)];
        int writeIndex;
        int readIndex;
        int values;

    public:
        RingBuffer() : writeIndex(0), readIndex(0), values(0) {
        }

        RingBuffer(const RingBuffer& other) : RingBuffer() {
            copy(other);
        }

        RingBuffer(RingBuffer&& other) : RingBuffer() {
            move(Core::move(other));
            other.clear();
        }

        ~RingBuffer() {
            clear();
        }

        RingBuffer& operator=(const RingBuffer& other) {
            if(&other != this) {
                clear();
                copy(other);
            }
            return *this;
        }

        RingBuffer& operator=(RingBuffer&& other) {
            if(&other != this) {
                clear();
                move(Core::move(other));
                other.clear();
            }
            return *this;
        }

        // returns a nullptr on error and calls the error callback
        template<typename... Args>
        check_return T* add(Args&&... args) {
            if(getLength() >= N) {
                CORE_ERROR(Error::CAPACITY_REACHED);
                return nullptr;
            }
            T* t = new(data + writeIndex) T(Core::forward<Args>(args)...);
            writeIndex = (writeIndex + 1) % N;
            values++;
            return t;
        }

        T& operator[](int index) {
            return reinterpret_cast<T*>(data)[(index + readIndex) % N];
        }

        const T& operator[](int index) const {
            return reinterpret_cast<const T*>(data)[(index + readIndex) % N];
        }

        int getLength() const {
            return values;
        }

        bool canRemove() const {
            return getLength() > 0;
        }

        void clear() {
            for(int i = 0; i < getLength(); i++) {
                (*this)[i].~T();
            }
            writeIndex = 0;
            readIndex = 0;
            values = 0;
        }

        // returns true on error and calls the error callback
        check_return bool remove() {
            if(!canRemove()) {
                return CORE_ERROR(Error::NOT_FOUND);
            }
            values--;
            (*this)[0].~T();
            readIndex = (readIndex + 1) % N;
            return false;
        }

        // returns true on error and calls the error callback
        template<int L>
        check_return bool toString(ArrayString<L>& s) const {
            if(s.append("[")) {
                return true;
            }
            for(int i = 0; i < getLength() - 1; i++) {
                if(s.append((*this)[i]) || s.append(", ")) {
                    return true;
                }
            }
            if(getLength() > 0 && s.append((*this)[getLength() - 1])) {
                return true;
            }
            return s.append("]");
        }

    private:
        void copy(const RingBuffer& other) {
            for(int i = 0; i < other.getLength(); i++) {
                (void)add(other[i]);
            }
        }

        void move(RingBuffer&& other) {
            for(int i = 0; i < other.getLength(); i++) {
                (void)add(Core::move(other[i]));
            }
        }
    };
}

#endif