#ifndef LIST_H
#define LIST_H

#include <new>
#include <utility>

#include "utils/StringBuffer.h"

template<typename T>
class List final {
    int length;
    int capacity;
    T* data;

public:
    List() : length(0), capacity(0), data(nullptr) {
    }

    List(const List& other)
        : length(0), capacity(other.capacity), data(allocate(capacity)) {
        for(int i = 0; i < other.length; i++) {
            add(other[i]);
        }
    }

    List(List&& other) : List() {
        swap(other);
    }

    ~List() {
        clear();
        delete[] reinterpret_cast<char*>(data);
    }

    List& operator=(List other) {
        swap(other);
        return *this;
    }

    T* begin() {
        return data;
    }

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

    const T* begin() const {
        return data;
    }

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

    void reserve(int n) {
        if(n <= capacity) {
            return;
        }
        List copy;
        copy.capacity = n;
        copy.data = allocate(n);
        for(int i = 0; i < length; i++) {
            copy.add(std::move(data[i]));
        }
        swap(copy);
    }

    void resize(int n, const T& t) {
        if(length < n) {
            reserve(n);
            for(int i = length; i < n; i++) {
                add(t);
            }
        } else if(length > n) {
            for(int i = n; i < length; i++) {
                data[i].~T();
            }
            length = n;
        }
    }

    void resize(int n) {
        if(length < n) {
            reserve(n);
            for(int i = length; i < n; i++) {
                add(T());
            }
        } else if(length > n) {
            for(int i = n; i < length; i++) {
                data[i].~T();
            }
            length = n;
        }
    }

    List& add(const T& t) {
        ensureCapacity();
        new(data + length++) T(t);
        return *this;
    }

    List& add(T&& t) {
        ensureCapacity();
        new(data + length++) T(std::move(t));
        return *this;
    }

    template<typename... Args>
    List& add(Args&&... args) {
        ensureCapacity();
        new(data + length++) T(std::forward<Args>(args)...);
        return *this;
    }

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

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

    int getLength() const {
        return length;
    }

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

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

    void remove(int index) {
        length--;
        for(int i = index; i < length; i++) {
            data[i] = std::move(data[i + 1]);
        }
        data[length].~T();
    }

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

private:
    static T* allocate(int n) {
        return reinterpret_cast<T*>(new char[sizeof(T) * n]);
    }

    void swap(List& other) {
        std::swap(length, other.length);
        std::swap(capacity, other.capacity);
        std::swap(data, other.data);
    }

    void ensureCapacity() {
        if(length >= capacity) {
            reserve(capacity == 0 ? 8 : capacity * 2);
        }
    }
};

#endif