#include "Vector.h"
#include <iostream>
#include <vector>

// 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;

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);
}