from abc import ABCMeta, abstractmethod

from tests.graph_impl import AbstractNode, AbstractEdge


class GraphTest(metaclass=ABCMeta):

    @abstractmethod
    def setUp(self):
        raise NotImplementedError

    def test_add_node(self):
        node1 = AbstractNode(1)
        node2 = AbstractNode(2)
        self.assertEqual(0, len(self.graph.getNodes()))
        self.assertTrue(self.graph.addNode(node1))
        self.assertEqual(1, len(self.graph.getNodes()))
        self.assertTrue(self.graph.addNode(node2))
        self.assertEqual(2, len(self.graph.getNodes()))
        self.assertEqual(node2, self.graph.getNode(2))
        self.assertFalse(self.graph.addNode(node1))
        self.assertEqual(2, len(self.graph.getNodes()))
        self.assertEqual(node2, self.graph.getNode(2))

    def test_add_edge(self):
        node1 = AbstractNode(1)
        node2 = AbstractNode(2)
        node3 = AbstractNode(3)
        node4 = AbstractNode(4)
        node5 = AbstractNode(5)
        self.graph.addNode(node1)
        self.graph.addNode(node2)
        self.graph.addNode(node3)
        self.graph.addNode(node4)
        self.graph.addNode(node5)
        edge1 = AbstractEdge(1, node1, node2)
        edge2 = AbstractEdge(2, node2, node3)
        edge3 = AbstractEdge(3, node4, node5)
        self.assertEqual(0, len(self.graph.getEdges()))
        self.assertTrue(self.graph.addEdge(edge1))
        self.assertEqual(1, len(self.graph.getEdges()))
        self.assertTrue(self.graph.addEdge(edge2))
        self.assertTrue(self.graph.addEdge(edge3))
        self.assertEqual(3, len(self.graph.getEdges()))
        self.assertEqual(2, len(self.graph.getIncidentEdges(node2)))
        self.assertEqual(1, len(self.graph.getIncomingEdges(node2)))
        self.assertEqual(edge1, self.graph.getOutgoingEdges(node1)[0])
        self.assertFalse(self.graph.addEdge(edge1))
        self.assertEqual(3, len(self.graph.getEdges()))
        self.assertEqual(2, len(self.graph.getIncidentEdges(node2)))
        self.assertEqual(1, len(self.graph.getIncomingEdges(node2)))

    def test_remove_node(self):
        node1 = AbstractNode(1)
        node2 = AbstractNode(2)
        node3 = AbstractNode(3)
        node4 = AbstractNode(4)
        node5 = AbstractNode(5)
        self.graph.addNode(node1)
        self.graph.addNode(node2)
        self.graph.addNode(node3)
        self.graph.addNode(node4)
        self.graph.addNode(node5)
        edge1 = AbstractEdge(1, node1, node2)
        edge2 = AbstractEdge(2, node2, node3)
        edge3 = AbstractEdge(3, node4, node5)
        self.graph.addEdge(edge1)
        self.graph.addEdge(edge2)
        self.graph.addEdge(edge3)
        self.assertEqual(5, len(self.graph.getNodes()))
        self.assertEqual(3, len(self.graph.getEdges()))
        self.assertTrue(self.graph.removeNode(node5))
        self.assertEqual(4, len(self.graph.getNodes()))
        self.assertEqual(2, len(self.graph.getEdges()))
        self.assertTrue(self.graph.removeNode(node2))
        self.assertEqual(0, len(self.graph.getEdges()))
        self.assertEqual(3, len(self.graph.getNodes()))

    def test_remove_edge(self):
        node1 = AbstractNode(1)
        node2 = AbstractNode(2)
        node3 = AbstractNode(3)
        node4 = AbstractNode(4)
        node5 = AbstractNode(5)
        self.graph.addNode(node1)
        self.graph.addNode(node2)
        self.graph.addNode(node3)
        self.graph.addNode(node4)
        self.graph.addNode(node5)
        edge1 = AbstractEdge(1, node1, node2)
        edge2 = AbstractEdge(2, node2, node3)
        edge3 = AbstractEdge(3, node4, node5)
        self.graph.addEdge(edge1)
        self.graph.addEdge(edge2)
        self.graph.addEdge(edge3)
        self.assertEqual(5, len(self.graph.getNodes()))
        self.assertEqual(3, len(self.graph.getEdges()))
        self.assertEqual(1, len(self.graph.getIncidentEdges(node4)))
        self.assertEqual(2, len(self.graph.getIncidentEdges(node2)))
        self.assertTrue(self.graph.removeEdge(edge3))
        self.assertEqual(0, len(self.graph.getIncidentEdges(node4)))
        self.assertEqual(5, len(self.graph.getNodes()))
        self.assertEqual(2, len(self.graph.getEdges()))
        self.assertTrue(self.graph.removeEdge(edge2))
        self.assertEqual(1, len(self.graph.getIncidentEdges(node2)))
        self.assertEqual(5, len(self.graph.getNodes()))
        self.assertEqual(1, len(self.graph.getEdges()))

    def test_find_edge(self):
        node1 = AbstractNode(1)
        node2 = AbstractNode(2)
        node3 = AbstractNode(3)
        self.graph.addNode(node1)
        self.graph.addNode(node2)
        self.graph.addNode(node3)
        edge1 = AbstractEdge(1, node1, node2)
        edge2 = AbstractEdge(2, node1, node3)
        self.graph.addEdge(edge1)
        self.graph.addEdge(edge2)
        self.assertEqual(self.graph.get_edge_by_nodes(node1, node2), edge1)
        self.assertEqual(self.graph.get_edge_by_nodes(node1, node3), edge2)

    def test_not_find_edge(self):
        node1 = AbstractNode(1)
        node2 = AbstractNode(2)
        node3 = AbstractNode(3)
        self.graph.addNode(node1)
        self.graph.addNode(node2)
        self.graph.addNode(node3)
        edge1 = AbstractEdge(1, node1, node2)
        edge2 = AbstractEdge(2, node1, node3)
        self.graph.addEdge(edge1)
        self.graph.addEdge(edge2)
        self.assertIsNone(self.graph.get_edge_by_nodes(node2, node3))


    def test_find_loop(self):
        node1 = AbstractNode(1)
        node2 = AbstractNode(2)
        self.graph.addNode(node1)
        self.graph.addNode(node2)
        edge1 = AbstractEdge(1, node1, node2)
        edge2 = AbstractEdge(2, node1, node1)
        self.graph.addEdge(edge1)
        self.graph.addEdge(edge2)
        self.assertEqual(self.graph.get_edge_by_nodes(node1, node1), edge2)

    def test_graph_works_after_edge_removal(self):
        node1 = AbstractNode(1)
        node2 = AbstractNode(2)
        node3 = AbstractNode(3)
        self.graph.addNode(node1)
        self.graph.addNode(node2)
        self.graph.addNode(node3)
        edge1 = AbstractEdge(1, node1, node2)
        edge2 = AbstractEdge(2, node2, node3)
        edge3 = AbstractEdge(3, node1, node3)
        self.graph.addEdge(edge1)
        self.graph.addEdge(edge2)
        self.graph.addEdge(edge3)
        self.assertTrue(self.graph.isDirected())
        self.graph.removeEdge(edge2)
        self.assertTrue(self.graph.isDirected())
