#ifndef ARRAYLIST_H
#define ARRAYLIST_H

#include <new>
#include <utility>

#include "utils/StringBuffer.h"

template<typename T, int N>
class ArrayList final {
    alignas(T) char data[sizeof(T) * N];
    int length;

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

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

    ArrayList(ArrayList&& other) : ArrayList() {
        move(std::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(std::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;
    }

    bool add(const T& t) {
        if(length >= N) {
            return true;
        }
        new(begin() + length++) T(t);
        return false;
    }

    bool add(T&& t) {
        if(length >= N) {
            return true;
        }
        new(begin() + length++) T(std::move(t));
        return false;
    }

    template<typename... Args>
    bool add(Args&&... args) {
        if(length >= N) {
            return true;
        }
        new(begin() + length++) T(std::forward<Args>(args)...);
        return false;
    }

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

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

    int getLength() const {
        return length;
    }

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

    void remove(int index) {
        length--;
        if(index != length) {
            begin()[index] = std::move(begin()[length]);
        }
        begin()[length].~T();
    }

    template<int L>
    void toString(StringBuffer<L>& s) const {
        s.append("[");
        for(int i = 0; i < length - 1; i++) {
            s.append(begin()[i]);
            s.append(", ");
        }
        if(length > 0) {
            s.append(begin()[length - 1]);
        }
        s.append("]");
    }

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

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

#endif