#include <iostream>
#include <vector>

#include "utils/HashMap.h"
#include "utils/List.h"

constexpr int MAP_MIN_CAPACITY = 5;
typedef HashMap<int, int, MAP_MIN_CAPACITY> IntMap;
typedef List<int, 20> IntList;

const char* RED = "\033[0;31m";
const char* GREEN = "\033[0;32m";

int tests = 0;
int successTests = 0;

void finalizeTest() {
    std::cout << ((successTests == tests) ? GREEN : RED);
    std::cout << successTests << " / " << tests << " succeeded\n\n";
    tests = 0;
    successTests = 0;
}

template<typename T>
void checkEqual(const T& wanted, const T& actual, const char* text) {
    if(wanted == actual) {
        tests++;
        successTests++;
        std::cout << GREEN << tests << ": " << text << "\n";
    } else {
        tests++;
        std::cout << RED << tests << ": " << text << " - ";
        std::cout << RED << "expected '" << wanted << "' got '" << actual << "'\n";
    }
}

namespace Map {

    void testAdd() {
        IntMap map;
        map.add(5, 4);
        checkEqual(true, map.contains(5), "map contains added value");
        checkEqual(true, map.search(5, -1) == 4, "map search finds added value");
    }

    void testMultipleAdd() {
        IntMap map;
        map.add(5, 4);
        map.add(10, 3);
        map.add(15, 2);
        checkEqual(true, map.contains(5), "map contains added value 1");
        checkEqual(true, map.contains(10), "map contains added value 2");
        checkEqual(true, map.contains(15), "map contains added value 3");
        checkEqual(true, map.search(5, -1) == 4, "map search finds added value 1");
        checkEqual(true, map.search(10, -1) == 3, "map search finds added value 2");
        checkEqual(true, map.search(15, -1) == 2, "map search finds added value 3");
    }

    void testAddReplace() {
        IntMap map;
        map.add(5, 4);
        map.add(5, 10);
        checkEqual(true, map.contains(5), "map contains replaced value");
        checkEqual(true, map.search(5, -1) == 10, "map search finds replaced value");
    }

    void testClear() {
        IntMap map;
        map.add(5, 4);
        map.add(4, 10);
        map.clear();

        checkEqual(false, map.contains(5), "map does not contain cleared values");
        checkEqual(false, map.contains(4), "map does not contain cleared values");
    }

    void testOverflow() {
        IntMap map;
        for(int i = 0; i < 1000000; i++) {
            map.add(i, i);
        }
        for(int i = 0; i < MAP_MIN_CAPACITY; i++) {
            checkEqual(true, map.contains(i), "map still contains values after overflow");
        }
        checkEqual(true, true, "map survives overflow");
    }

    void test() {
        testAdd();
        testMultipleAdd();
        testAddReplace();
        testClear();
        testOverflow();
        finalizeTest();
    }
}

namespace TestList {

    void testAdd() {
        IntList list;
        list.add(5);
        
        checkEqual(5, list[0], "list contains added value");
        checkEqual(1, list.getLength(), "list sizes is increased by add");
    }

    void testMultipleAdd() {
        IntList list;
        list.add(4);
        list.add(3);
        list.add(2);
        checkEqual(4, list[0], "list contains added value");
        checkEqual(3, list[1], "list contains added value");
        checkEqual(2, list[2], "list contains added value");
        checkEqual(3, list.getLength(), "list sizes is increased by add");
    }

    void testAddReplace() {
        IntList list;
        list.add(5);
        list[0] = 3;
        checkEqual(3, list[0], "list value is overwritten");
    }

    void testClear() {
        IntList list;
        list.add(5);
        list.add(4);
        list.clear();

        checkEqual(0, list.getLength(), "list length is 0 after clear");
    }

    void testOverflow() {
        IntList list;
        for(int i = 0; i < 1000000; i++) {
            list.add(i);
        }
        for(int i = 0; i < list.getLength(); i++) {
            checkEqual(i, list[i], "list still contains values after overflow");
        }
        checkEqual(true, true, "list survives overflow");
    }
    
    void testCopy() {
        IntList list;
        list.add(1);
        list.add(2);
        list.add(3);
        
        IntList copy(list);
        checkEqual(list.getLength(), copy.getLength(), "list copy has same length");
        for(int i = 0; i < copy.getLength() && i < list.getLength(); i++) {
            checkEqual(list[i], copy[i], "list copy has same values");
        }
    }
    
    void testCopyAssignment() {
        IntList list;
        list.add(1);
        list.add(2);
        list.add(3);
        
        IntList copy;
        copy = list;
        checkEqual(list.getLength(), copy.getLength(), "list copy assignment has same length");
        for(int i = 0; i < copy.getLength() && i < list.getLength(); i++) {
            checkEqual(list[i], copy[i], "list copy assignment has same values");
        }
    }
    
    void testMove() {
        IntList list;
        list.add(1);
        list.add(2);
        list.add(3);
        
        IntList move(std::move(list));
        checkEqual(0, list.getLength(), "moved list has length 0");
        checkEqual(3, move.getLength(), "moved list passes length");
        checkEqual(1, move[0], "moved list passes values");
        checkEqual(2, move[1], "moved list passes values");
        checkEqual(3, move[2], "moved list passes values");
    }
    
    void testMoveAssignment() {
        IntList list;
        list.add(1);
        list.add(2);
        list.add(3);
        
        IntList move(std::move(list));
        checkEqual(0, list.getLength(), "assignment moved list has length 0");
        checkEqual(3, move.getLength(), "assignment moved list passes length");
        checkEqual(1, move[0], "assignment moved list passes values");
        checkEqual(2, move[1], "assignment moved list passes values");
        checkEqual(3, move[2], "assignment moved list passes values");
    }

    void test() {
        testAdd();
        testMultipleAdd();
        testAddReplace();
        testClear();
        testOverflow();
        testCopy();
        testCopyAssignment();
        testMove();
        testMoveAssignment();
        finalizeTest();
    }
}

int main() {
    Map::test();
    TestList::test();
    return 0;
}