#include <cassert>
#include <iostream>
#include <vector>
#include <new>

// All implementations in this class use ints insteads of size_t because size_t
// is interely slow compared to ints. C++20 also adds new methods to call for
// signed container sizes. Several online resources also suggest to use unsigned
// types mostly for bit operations and nothing else.

// This struct is used in the automated tests at the end of the file.
// It counts all instances with different numbers to ensure each object is
// constructed and destructed the same amount of times.
// A also has a non trivial constructor to test the vector
struct A {
    static int instances;
    int a;
    A(int a) : a(a) {
        // std::cout << "construct A " << a << "\n";
        instances += a;
    }

    A(const A& other) : a(other.a) {
        // std::cout << "copy construct A " << a << "\n";
        instances += a;
    }

    A(A&& other) : a(other.a) {
        // std::cout << "move construct A " << a << "\n";
        instances += a;
    }

    A& operator=(const A& other) {
        instances -= a;
        a = other.a;
        instances += a;
        // std::cout << "copy assignment A " << a << "\n";
        return *this;
    }

    A& operator=(A&& other) {
        instances -= a;
        a = other.a;
        instances += a;
        // std::cout << "move assignment A " << a << "\n";
        return *this;
    }

    ~A() {
        // std::cout << "destruct A " << a << "\n";
        instances -= a;
    }
};

int A::instances = 0;

template<typename T>
class Vector {
    T* data;
    int capacity;
    int elements;

public:
    Vector() : data(nullptr), capacity(0), elements(0) {
    }

    Vector(int n, const T& t) : Vector() {
        for(int i = 0; i < n; i++) {
            push_back(t);
        }
    }

    Vector(int n) : Vector(n, T()) {
    }

    ~Vector() {
        // placement new needs explicit destructor calling
        for(int i = 0; i < elements; i++) {
            data[i].~T();
        }
        // casting this pointer not back to its origin type yields a crash
        delete[] reinterpret_cast<char*>(data);
    }

    // allocate new storage for copies
    Vector(const Vector& other)
        : data(allocate(other.capacity)), capacity(other.capacity),
          elements(other.elements) {
        // copy data into new storage
        for(int i = 0; i < elements; i++) {
            new(data + i) T(other.data[i]);
        }
    }

    // this handles copy and move assigment
    // copy: replace current resources with other made by the copy constructor
    // move: replace current resources with other made by the move constructor
    // other gets the old data and is destroyed by the destructor after going
    // out of scope
    Vector& operator=(Vector other) {
        swap(*this, other);
        return *this;
    }

    // construct a default vector so the other vector gets valid values from the
    // swap
    Vector(Vector&& other) : Vector() {
        swap(*this, other);
    }

    void reserve(int size) {
        if(size <= capacity) {
            return;
        }
        Vector v;
        v.capacity = size;
        v.data = allocate(size);
        for(int i = 0; i < elements; i++) {
            v.push_back(std::move(data[i]));
        }
        swap(*this, v);
    }

    void resize(int size, const T& t) {
        // elements will be equal to size after this call but not the capacity
        if(size > elements) {
            // fill until the given size is reached
            for(int i = elements; i < size; i++) {
                push_back(t);
            }
        } else if(size < elements) {
            // remove objects until the size matches
            for(int i = size; i < elements; i++) {
                data[i].~T();
            }
            elements = size;
        }
    }

    void resize(int size) {
        resize(size, T());
    }

    void push_back(const T& t) {
        ensureCapacity();
        new(data + elements++) T(t);
    }

    void push_back(T&& t) {
        ensureCapacity();
        // && would be lost without std::move
        new(data + elements++) T(std::move(t));
    }

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

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

    T& at(int index) {
        // std states "at" is [] with range check
        assert(index >= 0 && index < elements);
        return (*this)[index];
    }

    const T& at(int index) const {
        // std states "at" is [] with range check
        assert(index >= 0 && index < elements);
        return (*this)[index];
    }

    int size() const {
        return elements;
    }

    T* begin() {
        return data;
    }

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

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

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

    void erase(T* from, T* to) {
        T* remove = from;
        T* move = to;
        T* stop = end();
        // fill the hole by moving following objects as long as possible
        while(move != stop) {
            *remove = std::move(*move);
            remove++;
            move++;
        }
        // remove left over objects
        while(remove != stop) {
            remove->~T();
            remove++;
            elements--;
        }
    }

    void erase(T* start) {
        erase(start, start + 1);
    }

    T* as_array() {
        return begin();
    }

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

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

    int length() const {
        return capacity;
    }

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

    static void swap(Vector& a, Vector& b) {
        std::swap(a.data, b.data);
        std::swap(a.capacity, b.capacity);
        std::swap(a.elements, b.elements);
    }

    void ensureCapacity() {
        if(elements >= capacity) {
            // doubling the size amortizes costs
            reserve(capacity == 0 ? 1 : capacity * 2);
        }
    }
};

void printError(int number) {
    std::cout << "\033[0;31mError " << number << "\033[0m\n";
}

// V is either std::vector or Vector to test for complete same behaviour in both
// implementations
template<typename V>
void test() {
    {
        const int elements = 2;
        V v;
        for(int i = 0; i < elements; i++) {
            v.push_back(A(i));
        }
        const V& cv = v;
        for(int i = 0; i < elements; i++) {
            if(v[i].a != i || cv[i].a != i || v.at(i).a != i ||
               cv.at(i).a != i) {
                printError(1);
            }
        }
        if(v.size() != elements) {
            printError(2);
        }
    }
    {
        V v1;
        v1.push_back(A(10));
        V v2 = v1;
        V v3;
        v3.push_back(A(20));
        v3 = v1;
        if(v1[0].a != 10 || v2[0].a != 10 || v3[0].a != 10) {
            printError(3);
        }

        V v4 = std::move(v1);
        V v5(std::move(v2));
        V v6;
        v6 = std::move(v3);

        if(v1.size() != 0 || v2.size() != 0 || v3.size() != 0 ||
           v4.size() != 1 || v5.size() != 1 || v6.size() != 1) {
            printError(4);
        }
    }
    {
        V v;
        v.resize(1, A(8));
        v.resize(3, A(9));
        if(v.size() != 3 || v[0].a != 8 || v[1].a != 9 || v[2].a != 9) {
            printError(5);
        }
        v.resize(1, A(10));
        if(v.size() != 1 || v[0].a != 8) {
            printError(6);
        }
    }
    {
        std::vector<A> v1;
        V v2;
        for(int i = 0; i < 200; i++) {
            v1.push_back(A(i));
            v2.push_back(A(i));
        }
        v1.erase(v1.begin());
        v1.erase(v1.begin() + 4, v1.begin() + 8);
        v1.erase(v1.begin() + 30, v1.begin() + 56);
        v2.erase(v2.begin());
        v2.erase(v2.begin() + 4, v2.begin() + 8);
        v2.erase(v2.begin() + 30, v2.begin() + 56);
        if(static_cast<int>(v1.size()) != static_cast<int>(v2.size())) {
            printError(100);
        } else {
            for(unsigned int i = 0; i < v1.size(); i++) {
                if(v1[i].a != v2[i].a) {
                    printError(200);
                }
            }
        }
    }
    if(A::instances != 0) {
        std::cout << "object counter is not 0: " << A::instances << "\n";
    }
}

void test() {
    {
        Vector<A> v;
        v.push_back(1);
        v.push_back(2);
        v.push_back(3);
        v.erase_by_swap(1);
        if(v.size() != 2 || v[0].a != 1 || v[1].a != 3) {
            printError(9);
        }
        v.erase_by_swap(1);
        if(v.size() != 1 || v[0].a != 1) {
            printError(10);
        }
        v.erase_by_swap(0);
        if(v.size() != 0) {
            printError(11);
        }
    }
    if(A::instances != 0) {
        std::cout << "object counter is not 0: " << A::instances << "\n";
    }
}

int main() {
    test<Vector<A>>();
    test();
    std::cout << "--------------------------\n";
    test<std::vector<A>>();

    Vector<int> test(3);
}