#include <iostream>
#include <memory>
#include <cassert>
#include <deque>
#include <string>

#include "basic_testing.h"

#include "../roadmap.h"

using std::string;

TEST(basic_reachability) {
    std::unique_ptr<AbstractRoadMap> m(createRoadMap());

    assert_false(m->reachable("Sengkol", "Sakra"));
    assert_false(m->reachable("Sakra", "Sengkol"));

    m->addRoad("Sengkol", "Sakra");

    assert_true(m->reachable("Sengkol", "Sakra"));
    assert_true(m->reachable("Sakra", "Sengkol"));

    m->addRoad({"Mataram", "Mangsit", "Pamenang"});

    assert_true(m->reachable("Mataram", "Pamenang"));
    assert_true(m->reachable("Pamenang", "Mataram"));

    assert_false(m->reachable("Sakra", "Pamenang"));
    assert_false(m->reachable("Sengkol", "Mataram"));

    m->addRoad({"Mataram", "Sengkol", "Koeta"});

    assert_true(m->reachable("Sakra", "Pamenang"));
    assert_true(m->reachable("Sengkol", "Mataram"));

    TEST_PASSED;
}

TEST(clear) {
    std::unique_ptr<AbstractRoadMap> m(createRoadMap());

    assert_false(m->reachable("Sengkol", "Sakra"));
    assert_false(m->reachable("Sakra", "Sengkol"));

    m->addRoad("Sengkol", "Sakra");

    assert_true(m->reachable("Sengkol", "Sakra"));
    assert_true(m->reachable("Sakra", "Sengkol"));

    m->clear();

    assert_false(m->reachable("Sengkol", "Sakra"));
    assert_false(m->reachable("Sakra", "Sengkol"));

    m->addRoad("Sengkol", "Sakra");
    assert_true(m->reachable("Sengkol", "Sakra"));
    assert_true(m->reachable("Sakra", "Sengkol"));

    m->addRoad({"Mataram", "Mangsit", "Pamenang"});

    assert_true(m->reachable("Mataram", "Pamenang"));
    assert_true(m->reachable("Pamenang", "Mataram"));

    assert_false(m->reachable("Sakra", "Pamenang"));
    assert_false(m->reachable("Sengkol", "Mataram"));

    m->addRoad({"Mataram", "Sengkol", "Koeta"});

    assert_true(m->reachable("Sakra", "Pamenang"));
    assert_true(m->reachable("Sengkol", "Mataram"));

    m->clear();

    assert_false(m->reachable("Sakra", "Pamenang"));
    assert_false(m->reachable("Sengkol", "Mataram"));
    assert_false(m->reachable("Mataram", "Pamenang"));
    assert_false(m->reachable("Pamenang", "Mataram"));

    TEST_PASSED;
}

TEST(tree) {
    std::unique_ptr<AbstractRoadMap> m(createRoadMap());

    std::deque<string> Q;
    Q.push_back("0");
    while (!Q.empty()) {
	string s = Q.back();
	Q.pop_back();
	if (s.length() >= 8)
	    continue;
	string s0 = s + "0";
	string s1 = s + "1";
	m->addRoad(s, s0);
	m->addRoad(s, s1);
	Q.push_back(s0);
	Q.push_back(s1);
    }

    assert_int_eq(m->countIslands(), 1);
    assert_false(m->hasLoop());
    TEST_PASSED;
}

TEST(islands) {
    std::unique_ptr<AbstractRoadMap> m(createRoadMap());

    std::deque<string> Q;
    Q.push_back("0");
    while (!Q.empty()) {
	string s = Q.back();
	Q.pop_back();
	if (s.length() >= 8)
	    continue;
	string s0 = s + "0";
	string s1 = s + "1";
	m->addRoad(s1, s0);
	Q.push_back(s0);
	Q.push_back(s1);
    }

    assert_int_eq(m->countIslands(), 127);
    assert_false(m->hasLoop());
    TEST_PASSED;
}

TEST(big_loop) {
    std::unique_ptr<AbstractRoadMap> m(createRoadMap());

    std::deque<string> Q;
    Q.push_back("0");
    while (!Q.empty()) {
	string s = Q.back();
	Q.pop_back();
	if (s.length() >= 8) {
	    m->addRoad(s, "0");
	} else {
	    string s0 = s + "0";
	    string s1 = s + "1";
	    m->addRoad(s, s0);
	    m->addRoad(s, s1);
	    Q.push_back(s0);
	    Q.push_back(s1);
	}
    }
    assert_int_eq(m->countIslands(), 1);
    assert_true(m->hasLoop());
    TEST_PASSED;
}

TEST(big_island) {
    std::unique_ptr<AbstractRoadMap> m(createRoadMap());

    std::vector<string> cities = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J" };

    for (unsigned int i = 0; i < cities.size(); ++i)
	for (unsigned int j = i + 1; j < cities.size(); ++j)
	    m->addRoad(cities[i], cities[j]);

    assert_int_eq(m->countIslands(), 1);
    assert_true(m->hasLoop());
    for (unsigned int i = 0; i < cities.size(); ++i)
	assert_int_eq(m->neighborCountForCity(cities[i]), cities.size()-1);
    TEST_PASSED;
}

TEST(unknown_city) {
    std::unique_ptr<AbstractRoadMap> m(createRoadMap());

    std::vector<string> cities = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J" };

    for (unsigned int i = 0; i < cities.size(); ++i) 
	assert_int_eq(m->neighborCountForCity(cities[i]), -1);

    for (unsigned int i = 0; i < cities.size(); ++i)
	for (unsigned int j = i + 1; j < cities.size(); ++j)
	    m->addRoad(cities[i], cities[j]);

    for (unsigned int i = 0; i < cities.size(); ++i) {
	string s = cities[i] + "X";
	assert_int_eq(m->neighborCountForCity(s), -1);
    }
    TEST_PASSED;
}

int main() {
    RUN_TEST(basic_reachability);
    RUN_TEST(clear);
    RUN_TEST(tree);
    RUN_TEST(islands);
    RUN_TEST(big_loop);
    RUN_TEST(big_island);
    RUN_TEST(unknown_city);
    PRINT_TEST_RESULTS;
    assert(ALL_TESTS_PASSED);
}
