#ifndef CORE_ARRAYLIST_HPP
#define CORE_ARRAYLIST_HPP

#include <assert.h>

#include "core/utils/AlignedData.hpp"
#include "core/utils/ArrayString.hpp"

namespace Core {
    template<typename T, size_t N>
    class ArrayList final {
        AlignedType<T> data[N];
        size_t length;

    public:
        ArrayList() : length(0) {
        }

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

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

        ~ArrayList() {
            clear();
        }

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

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

        T* begin() {
            return reinterpret_cast<T*>(data);
        }

        T* end() {
            return begin() + length;
        }

        const T* begin() const {
            return reinterpret_cast<const T*>(data);
        }

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

        template<typename... Args>
        T* put(Args&&... args) {
            if(length < N) {
                return new(begin() + length++) T(Core::forward<Args>(args)...);
            }
            return nullptr;
        }

        template<typename... Args>
        ArrayList& add(Args&&... args) {
            put(Core::forward<Args>(args)...);
            return *this;
        }

        T& operator[](size_t index) {
            return begin()[index];
        }

        const T& operator[](size_t index) const {
            return begin()[index];
        }

        size_t getLength() const {
            return length;
        }

        void clear() {
            for(size_t i = 0; i < length; i++) {
                begin()[i].~T();
            }
            length = 0;
        }

        void removeBySwap(size_t index) {
            assert(index < length);
            length--;
            if(index != length) {
                begin()[index] = Core::move(begin()[length]);
            }
            begin()[length].~T();
        }

        void removeLast() {
            removeBySwap(length - 1);
        }

        void toString(BufferString& s) const {
            Core::toString(s, *this);
        }

    private:
        void copy(const ArrayList& other) {
            for(size_t i = 0; i < other.length; i++) {
                add(other[i]);
            }
        }

        void move(ArrayList&& other) {
            for(size_t i = 0; i < other.length; i++) {
                add(Core::move(other[i]));
            }
        }
    };
}

#endif