// FordFulkersonTest.cpp
//
//     Created on: 29. 03. 2016
//         Author: Jan Broz
//    Modified by: Jan Uhlik
//
// Copyright (c) 2017 Czech Technical University in Prague | Faculty of Information Technology. All rights reserved.
// Git repository: https://gitlab.fit.cvut.cz/algorithms-library-toolkit/automata-library

#include <catch2/catch.hpp>
#include <minimum_cut/FordFulkerson.hpp>

#define NetworkDef(edgeCount) const struct { unsigned nodeCnt; unsigned edgeCnt; unsigned source; unsigned sink; struct { unsigned node1; unsigned node2; unsigned capacity; } edges [ edgeCount ]; }
struct NodePair {
	unsigned node1ID;
	unsigned node2ID;
};

NetworkDef(1) network1 = {
	2, 1,
	0, 1,
	{
		{0, 1, 5}
	}
};
NodePair dircut1[1] = {
	{0, 1}
};
NodePair undircut1[1] = {
	{0, 1}
};

//----------------------------------
NetworkDef(4) network2 = {
	4, 4,
	0, 3,
	{
		{0, 1, 1},
		{0, 2, 4},
		{1, 3, 2},
		{2, 3, 3}
	}
};
NodePair dircut2[2] = {
	{0, 1},
	{2, 3}
};
NodePair undircut2[2] = {
	{0, 1},
	{2, 3}
};

//----------------------------------
NetworkDef(5) network3 = {
	4, 5,
	0, 3,
	{
		{0, 1, 5},
		{0, 2, 6},
		{1, 3, 7},
		{2, 1, 2},
		{2, 3, 4}
	}
};
NodePair dircut3[2] = {
	{0, 1},
	{0, 2}
};
NodePair undircut3[2] = {
	{0, 1},
	{0, 2}
};

//----------------------------------
NetworkDef(10) network4 = {
	7, 10,
	0, 6,
	{
		{0, 3, 12},
		{0, 1, 6},
		{1, 3, 2},
		{1, 2, 8},
		{2, 4, 7},
		{3, 4, 1},
		{3, 5, 4},
		{3, 6, 6},
		{4, 6, 7},
		{5, 6, 3}
	}
};
NodePair dircut4[5] = {
	{0, 1},
	{1, 3},
	{3, 4},
	{3, 6},
	{5, 6}
};
NodePair undircut4[3] = {
	{5, 6},
	{3, 6},
	{4, 6}
};

// Old implementation without templates
using AdjacencyListUndirectedGraph = graph::UndirectedGraph<node::Node, edge::CapacityEdge<node::Node, int>>;
using AdjacencyListDirectedGraph = graph::DirectedGraph<node::Node, edge::CapacityEdge<node::Node, int>>;
using DirectedEdge = edge::CapacityEdge<node::Node, int>;
using UndirectedEdge = edge::CapacityEdge<node::Node, int>;

template<typename Type1, typename Type2>
void testDirNetwork(unsigned netID, const Type1 *netDef, const Type2 cutDef, unsigned cutSize) {
	AdjacencyListDirectedGraph graph;
	ext::vector<node::Node> id2node(netDef->nodeCnt);
	node::Node source, sink;
	unsigned i;

	for (i = 0; i < netDef->nodeCnt; i++) {
		node::Node node(i);
		graph.addNode(node);
		id2node[i] = node;
		if (i == netDef->source)
			source = node;
		if (i == netDef->sink)
			sink = node;
	}
	for (i = 0; i < netDef->edgeCnt; i++) {
		graph.addEdge(id2node[netDef->edges[i].node1], id2node[netDef->edges[i].node2], netDef->edges[i].capacity);
	}

	INFO ( "testing network #" << netID );

	graph::minimum_cut::Cut cut = graph::minimum_cut::FordFulkerson::findMinimumCut(graph, source, sink);

	CHECK(cutSize == (unsigned) cut.size());
	for (i = 0; i < cutSize; i++) {
		CHECK(cut.find(std::make_pair(id2node[cutDef[i].node1ID], id2node[cutDef[i].node2ID])) != cut.end());
	}
}

template<typename Type1, typename Type2>
void testUndirNetwork(unsigned netID, const Type1 *netDef, const Type2 cutDef, unsigned cutSize) {
	AdjacencyListUndirectedGraph graph;
	ext::vector<node::Node> id2node(netDef->nodeCnt);
	node::Node source, sink;
	unsigned i;

	for (i = 0; i < netDef->nodeCnt; i++) {
		node::Node node(i);
		graph.addNode(node);
		id2node[i] = node;
		if (i == netDef->source)
			source = node;
		if (i == netDef->sink)
			sink = node;
	}
	for (i = 0; i < netDef->edgeCnt; i++) {
		graph.addEdge(id2node[netDef->edges[i].node1], id2node[netDef->edges[i].node2], netDef->edges[i].capacity);
	}

	INFO ( "testing network #" << netID );

	graph::minimum_cut::Cut cut = graph::minimum_cut::FordFulkerson::findMinimumCut(graph, source, sink);

	CHECK (cutSize == (unsigned) cut.size());
	for (i = 0; i < cutSize; i++) {
		CHECK(cut.find(std::make_pair(id2node[cutDef[i].node1ID], id2node[cutDef[i].node2ID])) != cut.end());
	}
}

TEST_CASE ( "MinCut FF", "[unit][graph][minimum_cut]" ) {
	testDirNetwork(1, &network1, dircut1, sizeof(dircut1) / sizeof(dircut1[0]));
	testUndirNetwork(2, &network1, undircut1, sizeof(undircut1) / sizeof(undircut1[0]));
	testDirNetwork(3, &network2, dircut2, sizeof(dircut2) / sizeof(dircut2[0]));
	testUndirNetwork(4, &network2, undircut2, sizeof(undircut2) / sizeof(undircut2[0]));
	testDirNetwork(5, &network3, dircut3, sizeof(dircut3) / sizeof(dircut3[0]));
	testUndirNetwork(6, &network3, undircut3, sizeof(undircut3) / sizeof(undircut3[0]));
	testDirNetwork(7, &network4, dircut4, sizeof(dircut4) / sizeof(dircut4[0]));
	testUndirNetwork(8, &network4, undircut4, sizeof(undircut4) / sizeof(undircut4[0]));
}
