/*************************************************
* ADS Praktikum 5
* Unit-Testdatei
* Stand: 01.06.2021
*
*************************************************
* Änderungen untersagt!
*************************************************/
#include "catch.hpp"
#include <string>
#include <iostream>
#include "EdgeWeightedGraph.h"
#include "EdgeWeightedDigraph.h"
#include "DijkstraSP.h"
#include "KruskalMST.h"
#include "PrimMST.h"

namespace Graphsearch {
	bool DFS(const EdgeWeightedGraph& G, int v, std::vector<bool>& marked, std::vector<int>& edgeTo);
	bool BFS(const EdgeWeightedGraph& G, int v, std::vector<bool>& marked, std::vector<int>& edgeTo);
}

/***********************************************
 * Testroutine des Graphen:
	- Adjazenzlisten
	- Tiefensuche mit zusammenhängenden Graphen
	- Tiefensuche mit nicht zusammenhängenden Graphen
	- Breitensuche mit zusammenhängenden Graphen
	- Breitensuche mit nicht zusammenhängenden Graphen
	- Prim mit bestimmten Ergebnissen
	- Kruskal mit bestimmten Ergebnissen
	- Dijkstra
 */
TEST_CASE("Graph Test: Adjazenzliste") {
	EdgeWeightedGraph G1("graph1.txt");
	EdgeWeightedGraph G2("graph2.txt");
	EdgeWeightedGraph G3("graph3.txt");

	SECTION("Adjazenzliste des ersten Graphen (graph1.txt)") {
		REQUIRE(G1.getV() == 7);
		REQUIRE(G1.getE() == 11);

		auto adj = G1.getAdj();
		REQUIRE(adj[0][0].either() == 0);
		REQUIRE(adj[0][0].other(0) == 1);
		REQUIRE(adj[0][0].weight() == 7.0);

		REQUIRE(adj[4][3].either() == 4);
		REQUIRE(adj[4][3].other(4) == 5);
		REQUIRE(adj[4][3].weight() == 8.0);
	}

	SECTION("Adjazenzliste des zweiten Graphen (graph2.txt)") {
		REQUIRE(G2.getV() == 20);
		REQUIRE(G2.getE() == 60);

		auto adj = G2.getAdj();
		REQUIRE(adj[0][0].either() == 0);
		REQUIRE(adj[0][0].other(0) == 1);
		REQUIRE(adj[0][0].weight() == 6.0);

		REQUIRE(adj[13][5].either() == 13);
		REQUIRE(adj[13][5].other(13) == 19);
		REQUIRE(adj[13][5].weight() == 25.0);
	}

	SECTION("Adjazenzliste des dritten Graphen (graph3.txt)") {
		REQUIRE(G2.getV() == 20);
		REQUIRE(G2.getE() == 60);

		auto adj = G2.getAdj();
		REQUIRE(adj[0][0].either() == 0);
		REQUIRE(adj[0][0].other(0) == 1);
		REQUIRE(adj[0][0].weight() == 6.0);

		REQUIRE(adj[13][5].either() == 13);
		REQUIRE(adj[13][5].other(13) == 19);
		REQUIRE(adj[13][5].weight() == 25.0);
	}
}

TEST_CASE("Graph Test: Tiefensuche (DFS)") {

	EdgeWeightedGraph G1("graph1.txt");
	EdgeWeightedGraph G2("graph2.txt");
	EdgeWeightedGraph G3("graph3.txt");

	std::vector<bool> marked;
	std::vector<int>  edgeTo;

	SECTION("Tiefensuche mit erstem Graphen (graph1.txt") {
		marked.resize(42, false);	// Dummy values, damit die unit-Tests mit 
		edgeTo.resize(42, -1);		// der Vorlage durchlaufen ohne Programmabsturz

		for (int i = 0; i < G1.getV(); i++) {
			bool connected = Graphsearch::DFS(G1, i, marked, edgeTo);
			REQUIRE(connected == true);
		}

		// edgeTo-Array der Tiefensuche mit Startknoten 0 testen 
		Graphsearch::DFS(G1, 0, marked, edgeTo);
		REQUIRE(edgeTo.size() == G1.getV());
		REQUIRE(marked.size() == G1.getV());
		REQUIRE(edgeTo[0] == -1);
		REQUIRE(edgeTo[1] == 0);
		REQUIRE(edgeTo[2] == 1);
		REQUIRE(edgeTo[3] == 4);
		REQUIRE(edgeTo[4] == 2);
		REQUIRE(edgeTo[5] == 3);
		REQUIRE(edgeTo[6] == 5);
	}

	SECTION("Tiefensuche mit zweiten Graphen (graph2.txt)") {
		marked.resize(42, false);	// Dummy values, damit die unit-Tests mit 
		edgeTo.resize(42, -1);		// der Vorlage durchlaufen ohne Programmabsturz

		for (int i = 0; i < G2.getV(); i++) {
			bool connected = Graphsearch::DFS(G2, i, marked, edgeTo);
			REQUIRE(connected == true);
		}

		Graphsearch::DFS(G2, 0, marked, edgeTo);
		REQUIRE(edgeTo.size() == G2.getV());
		REQUIRE(marked.size() == G2.getV());
		REQUIRE(edgeTo[0] == -1);
		REQUIRE(edgeTo[1] == 0);
		REQUIRE(edgeTo[7] == 4);
		REQUIRE(edgeTo[8] == 9);
		REQUIRE(edgeTo[19] == 13);
	}

	SECTION("Tiefensuche mit dritten Graphen (graph3.txt)") {
		marked.resize(42, false);	// Dummy values, damit die unit-Tests mit 
		edgeTo.resize(42, -1);		// der Vorlage durchlaufen ohne Programmabsturz

		for (int i = 0; i < G3.getV(); i++) {
			bool connected = Graphsearch::DFS(G3, i, marked, edgeTo);
			REQUIRE(connected == false);
		}

		Graphsearch::DFS(G3, 0, marked, edgeTo);
		REQUIRE(edgeTo.size() == G3.getV());
		REQUIRE(marked.size() == G3.getV());
		REQUIRE(marked[0] == true);
		REQUIRE(marked[4] == true);
		REQUIRE(marked[18] == true);

		Graphsearch::DFS(G3, 1, marked, edgeTo);
		REQUIRE(edgeTo.size() == G3.getV());
		REQUIRE(marked.size() == G3.getV());
		REQUIRE(marked[0] == false);
		REQUIRE(marked[4] == false);
		REQUIRE(marked[18] == false);
	}
}

TEST_CASE("Graph Test: Breitensuche (BFS)") {

	EdgeWeightedGraph G1("graph1.txt");
	EdgeWeightedGraph G2("graph2.txt");
	EdgeWeightedGraph G3("graph3.txt");

	std::vector<bool> marked;
	std::vector<int>  edgeTo;

	SECTION("Breitensuche mit erstem Graphen (graph1.txt") {
		marked.resize(42, false);	// Dummy values, damit die unit-Tests mit 
		edgeTo.resize(42, -1);		// der Vorlage durchlaufen ohne Programmabsturz

		for (int i = 0; i < G1.getV(); i++) {
			bool connected = Graphsearch::BFS(G1, i, marked, edgeTo);
			REQUIRE(connected == true);
		}

		// edgeTo-Array der Breitensuche mit Startknoten 0 testen 
		Graphsearch::BFS(G1, 0, marked, edgeTo);
		REQUIRE(edgeTo.size() == G1.getV());
		REQUIRE(marked.size() == G1.getV());
		REQUIRE(edgeTo[0] == -1);
		REQUIRE(edgeTo[1] == 0);
		REQUIRE(edgeTo[2] == 1);
		REQUIRE(edgeTo[3] == 0);
		REQUIRE(edgeTo[4] == 1);
		REQUIRE(edgeTo[5] == 3);
		REQUIRE(edgeTo[6] == 4);
	}

	SECTION("Breitensuche mit zweiten Graphen (graph2.txt)") {
		marked.resize(42, false);	// Dummy values, damit die unit-Tests mit 
		edgeTo.resize(42, -1);		// der Vorlage durchlaufen ohne Programmabsturz

		for (int i = 0; i < G2.getV(); i++) {
			bool connected = Graphsearch::BFS(G2, i, marked, edgeTo);
			REQUIRE(connected == true);
		}

		// edgeTo-Array der Breitensuche mit Startknoten 0 testen 
		Graphsearch::BFS(G2, 0, marked, edgeTo);
		REQUIRE(edgeTo.size() == G2.getV());
		REQUIRE(marked.size() == G2.getV());
		REQUIRE(edgeTo[0] == -1);
		REQUIRE(edgeTo[1] == 0);
		REQUIRE(edgeTo[2] == 3);
		REQUIRE(edgeTo[6] == 16);
		REQUIRE(edgeTo[11] == 4);
		REQUIRE(edgeTo[15] == 4);
		REQUIRE(edgeTo[19] == 0);
	}

	SECTION("Breitensuche mit dritten Graphen (graph3.txt)") {
		marked.resize(42, false);	// Dummy values, damit die unit-Tests mit 
		edgeTo.resize(42, -1);		// der Vorlage durchlaufen ohne Programmabsturz

		for (int i = 0; i < G3.getV(); i++) {
			bool connected = Graphsearch::BFS(G3, i, marked, edgeTo);
			REQUIRE(edgeTo.size() == G3.getV());
			REQUIRE(marked.size() == G3.getV());
			REQUIRE(connected == false);

			// Hinweis: Die Knoten {0,4,18} bilden einen Teilgraphen
			if (i == 0 || i == 4 || i == 18) {
				REQUIRE(marked[0]  == true);
				REQUIRE(marked[4]  == true);
				REQUIRE(marked[18] == true);
			}
			else {
				REQUIRE(marked[0]  == false);
				REQUIRE(marked[4]  == false);
				REQUIRE(marked[18] == false);
			}
		}
	}
}

TEST_CASE("Graph Test: Prim") {
	EdgeWeightedGraph G1("graph1.txt");
	EdgeWeightedGraph G2("graph2.txt");
	EdgeWeightedGraph G3("graph3.txt");

	SECTION("Prim mit erstem Graph (graph1.txt)") {
		for (int i = 0; i < G1.getV(); i++) {
			PrimMST prim(G1, i);
			REQUIRE(prim.weight() == 39.0);

			auto edges = prim.edges();
			REQUIRE(edges.size() == 6);
			std::vector<Edge> mst;
			mst.push_back(Edge(0, 3, 5.0));
			mst.push_back(Edge(2, 4, 5.0));
			mst.push_back(Edge(3, 5, 6.0));
			mst.push_back(Edge(1, 4, 7.0));
			mst.push_back(Edge(0, 1, 7.0));
			mst.push_back(Edge(4, 6, 9.0));

			// Testen ob alle Edes des MST exitieren
			for (auto const& e : edges) {
				REQUIRE(std::find(mst.begin(), mst.end(), e) != mst.end());
			}
		}
	}

	SECTION("Prim mit zweitem Graph (graph2.txt)") {
		for (int i = 0; i < G2.getV(); i++) {
			PrimMST prim(G2, i);
			REQUIRE(prim.weight() == 150.0);
			auto edges = prim.edges();
			REQUIRE(edges.size() == 19);
		}
	}

	SECTION("Prim mit drittem Graphen (graph3.txt)") {
		// Graph 3 mit Strartknoten 0
		PrimMST prim_0(G3, 0);
		REQUIRE(prim_0.weight() == 16.0);
		// Graph 3 mit Strartknoten 16
		PrimMST prim_16(G3, 16);
		REQUIRE(prim_16.weight() == 168.0);
	}
}

TEST_CASE("Graph Test: Kruskal") {
	EdgeWeightedGraph G1("graph1.txt");
	EdgeWeightedGraph G2("graph2.txt");
	EdgeWeightedGraph G3("graph3.txt");

	SECTION("Kruskal mit erstem Graph (graph1.txt)") {
		KruskalMST kruskal(G1);
		REQUIRE(kruskal.weight() == 39.0);

		auto edges = kruskal.edges();
		REQUIRE(edges.size() == 6);
		std::vector<Edge> mst;
		mst.push_back(Edge(0, 3, 5.0));
		mst.push_back(Edge(2, 4, 5.0));
		mst.push_back(Edge(3, 5, 6.0));
		mst.push_back(Edge(1, 4, 7.0));
		mst.push_back(Edge(0, 1, 7.0));
		mst.push_back(Edge(4, 6, 9.0));

		// Testen ob alle Edes des MST exitieren
		for (auto const& e : edges) {
			REQUIRE(std::find(mst.begin(), mst.end(), e) != mst.end());
		}
	}

	SECTION("Kruskal mit zweitem Graph (graph2.txt)") {
		KruskalMST kruskal(G2);
		REQUIRE(kruskal.weight() == 150.0);
		REQUIRE(kruskal.edges().size() == 19);
	}

	SECTION("Kruskal mit drittem Graph (graph3.txt)") {
		KruskalMST kruskal(G3);
		REQUIRE(kruskal.weight() == 184.0);
		REQUIRE(kruskal.edges().size() == 18);
	}
}

TEST_CASE("Graph Test: Dijkstra") {
	EdgeWeightedDigraph G1_directed("graph1.txt");
	EdgeWeightedDigraph G2_directed("graph2.txt");
	EdgeWeightedDigraph G3_directed("graph3.txt");

	int start, target;

	SECTION("Dijkstra beim ersten Graph - 0 nach 6 (graph1.txt)") {
		start = 0;
		target = 6;
		DijkstraSP dsp(G1_directed, start);
		auto path = dsp.pathTo(target);

		REQUIRE(dsp.distTo(target) == 22.0);
		REQUIRE(path.size() == 3);
		REQUIRE(path[0] == DirectedEdge(0, 3, 5.0));
		REQUIRE(path[1] == DirectedEdge(3, 5, 6.0));
		REQUIRE(path[2] == DirectedEdge(5, 6, 11.0));
	}

	SECTION("Dijkstra beim ersten Graph - 2 nach 6 (graph1.txt)") {
		start = 2;
		target = 6;
		DijkstraSP dsp(G1_directed, start);
		auto path = dsp.pathTo(target);

		REQUIRE(dsp.distTo(target) == 14.0);
		REQUIRE(path.size() == 2);
		REQUIRE(path[0] == DirectedEdge(2, 4, 5.0));
		REQUIRE(path[1] == DirectedEdge(4, 6, 9.0));
	}

	SECTION("Dijkstra beim zweiten Graph - 0 nach 6 (graph2.txt)") {
		start = 0;
		target = 6;
		DijkstraSP dsp(G2_directed, start);
		auto path = dsp.pathTo(target);

		REQUIRE(dsp.distTo(target) == 91.0);
		REQUIRE(path.size() == 3);
		REQUIRE(path[0] == DirectedEdge(0, 4, 47.0));
		REQUIRE(path[1] == DirectedEdge(4, 5, 36.0));
		REQUIRE(path[2] == DirectedEdge(5, 6, 8.0));
	}

	SECTION("Dijkstra beim zweiten Graph - 3 nach 9 (graph2.txt)") {
		start = 3;
		target = 9;
		DijkstraSP dsp(G2_directed, start);
		auto path = dsp.pathTo(target);

		REQUIRE(dsp.distTo(target) == 122.0);
		REQUIRE(path.size() == 4);
		REQUIRE(path[0] == DirectedEdge(3, 4, 46.0));
		REQUIRE(path[1] == DirectedEdge(4, 5, 36.0));
		REQUIRE(path[2] == DirectedEdge(5, 6, 8.0));
		REQUIRE(path[3] == DirectedEdge(6, 9, 32.0));
	}

	SECTION("Dijkstra beim dritten Graph - 0 nach 4 (graph3.txt)") {
		start = 0;
		target = 4;
		DijkstraSP dsp(G3_directed, start);
		auto path = dsp.pathTo(target);

		REQUIRE(dsp.distTo(target) == 47.0);
		REQUIRE(path.size() == 1);
		REQUIRE(path[0] == DirectedEdge(0, 4, 47.0));
	}

	SECTION("Dijkstra beim dritten Graph - 1 nach 16 (graph3.txt)") {
		start = 1;
		target = 16;
		DijkstraSP dsp(G3_directed, start);
		auto path = dsp.pathTo(target);

		REQUIRE(dsp.distTo(target) == 35.0);
		REQUIRE(path.size() == 2);
		REQUIRE(path[0] == DirectedEdge(1, 7, 9.0));
		REQUIRE(path[1] == DirectedEdge(7, 16, 26.0));
	}

	SECTION("Dijkstra beim dritten Graph - 3 nach 7 (graph3.txt)") {
		start = 3;
		target = 7;
		DijkstraSP dsp(G3_directed, start);
		auto path = dsp.pathTo(target);

		REQUIRE(dsp.distTo(target) == 42.0);
		REQUIRE(path.size() == 3);
		REQUIRE(path[0] == DirectedEdge(3, 5, 27.0));
		REQUIRE(path[1] == DirectedEdge(5, 6, 8.0));
		REQUIRE(path[2] == DirectedEdge(6, 7, 7.0));
	}
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// http://www.hashemall.com/
// Zeile 1 - 413 hat den SHA 256 Hashwert: C812B068B2223D95AC8750EBBEB14261A7DFA84DF2DC4BC8F0026E7223C35A37
