import gzip
import io
import unittest
import warnings

from igraph import Graph, InternalError

from .utils import temporary_file

try:
    import networkx as nx
except ImportError:
    nx = None

try:
    import graph_tool as gt
except ImportError:
    gt = None

try:
    import pandas as pd
except ImportError:
    pd = None


GRAPHML_EXAMPLE_FILE = """\
<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns
        http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
<!-- Created by igraph -->
<key id="v_name" for="node" attr.name="name" attr.type="string"/>
<graph id="G" edgedefault="undirected">
    <node id="n0">
    <data key="v_name">a</data>
    </node>
    <node id="n1">
    <data key="v_name">b</data>
    </node>
    <node id="n2">
    <data key="v_name">c</data>
    </node>
    <node id="n3">
    <data key="v_name">d</data>
    </node>
    <node id="n4">
    <data key="v_name">e</data>
    </node>
    <node id="n5">
    <data key="v_name">f</data>
    </node>
    <edge source="n0" target="n1">
    </edge>
    <edge source="n0" target="n2">
    </edge>
    <edge source="n0" target="n3">
    </edge>
    <edge source="n1" target="n2">
    </edge>
    <edge source="n3" target="n4">
    </edge>
    <edge source="n3" target="n5">
    </edge>
    <edge source="n4" target="n5">
    </edge>
</graph>
</graphml>
"""


class ForeignTests(unittest.TestCase):
    def testDIMACS(self):
        with temporary_file(
            """\
        c
        c        This is a simple example file to demonstrate the
        c     DIMACS input file format for minimum-cost flow problems.
        c
        c problem line :
        p max 4 5
        c
        c node descriptor lines :
        n 1 s
        n 4 t
        c
        c arc descriptor lines :
        a 1 2 4
        a 1 3 2
        a 2 3 2
        a 2 4 3
        a 3 4 5
        """
        ) as tmpfname:
            graph = Graph.Read_DIMACS(tmpfname, False)
            self.assertTrue(isinstance(graph, Graph))
            self.assertTrue(graph.vcount() == 4 and graph.ecount() == 5)
            self.assertTrue(graph["source"] == 0 and graph["target"] == 3)
            self.assertTrue(graph.es["capacity"] == [4, 2, 2, 3, 5])
            graph.write_dimacs(tmpfname)

    def testDL(self):
        with temporary_file(
            """\
        dl n=5
        format = fullmatrix
        labels embedded
        data:
        larry david lin pat russ
        Larry 0 1 1 1 0
        david 1 0 0 0 1
        Lin 1 0 0 1 0
        Pat 1 0 1 0 1
        russ 0 1 0 1 0
        """
        ) as tmpfname:
            g = Graph.Read_DL(tmpfname)
            self.assertTrue(isinstance(g, Graph))
            self.assertTrue(g.vcount() == 5 and g.ecount() == 12)
            self.assertTrue(g.is_directed())
            self.assertTrue(
                sorted(g.get_edgelist())
                == [
                    (0, 1),
                    (0, 2),
                    (0, 3),
                    (1, 0),
                    (1, 4),
                    (2, 0),
                    (2, 3),
                    (3, 0),
                    (3, 2),
                    (3, 4),
                    (4, 1),
                    (4, 3),
                ]
            )

        with temporary_file(
            """\
        dl n=5
        format = fullmatrix
        labels:
        barry,david
        lin,pat
        russ
        data:
        0 1 1 1 0
        1 0 0 0 1
        1 0 0 1 0
        1 0 1 0 1
        0 1 0 1 0
        """
        ) as tmpfname:
            g = Graph.Read_DL(tmpfname)
            self.assertTrue(isinstance(g, Graph))
            self.assertTrue(g.vcount() == 5 and g.ecount() == 12)
            self.assertTrue(g.is_directed())
            self.assertTrue(
                sorted(g.get_edgelist())
                == [
                    (0, 1),
                    (0, 2),
                    (0, 3),
                    (1, 0),
                    (1, 4),
                    (2, 0),
                    (2, 3),
                    (3, 0),
                    (3, 2),
                    (3, 4),
                    (4, 1),
                    (4, 3),
                ]
            )

        with temporary_file(
            """\
        DL n=5
        format = edgelist1
        labels:
        george, sally, jim, billy, jane
        labels embedded:
        data:
        george sally 2
        george jim 3
        sally jim 4
        billy george 5
        jane jim 6
        """
        ) as tmpfname:
            g = Graph.Read_DL(tmpfname, False)
            self.assertTrue(isinstance(g, Graph))
            self.assertTrue(g.vcount() == 5 and g.ecount() == 5)
            self.assertTrue(not g.is_directed())
            self.assertTrue(
                sorted(g.get_edgelist()) == [(0, 1), (0, 2), (0, 3), (1, 2), (2, 4)]
            )

    def _testNCOLOrLGL(self, func, fname, can_be_reopened=True):
        g = func(fname, names=False, weights=False, directed=False)
        self.assertTrue(isinstance(g, Graph))
        self.assertTrue(g.vcount() == 4 and g.ecount() == 5)
        self.assertTrue(not g.is_directed())
        self.assertTrue(
            sorted(g.get_edgelist()) == [(0, 1), (0, 2), (1, 1), (1, 3), (2, 3)]
        )
        self.assertTrue(
            "name" not in g.vertex_attributes() and "weight" not in g.edge_attributes()
        )
        if not can_be_reopened:
            return

        g = func(fname, names=False, directed=False)
        self.assertTrue(
            "name" not in g.vertex_attributes() and "weight" in g.edge_attributes()
        )
        self.assertTrue(g.es["weight"] == [1, 2, 0, 3, 0])

        g = func(fname, directed=False)
        self.assertTrue(
            "name" in g.vertex_attributes() and "weight" in g.edge_attributes()
        )
        self.assertTrue(g.vs["name"] == ["eggs", "spam", "ham", "bacon"])
        self.assertTrue(g.es["weight"] == [1, 2, 0, 3, 0])

    def testNCOL(self):
        with temporary_file(
            """\
        eggs spam 1
        ham eggs 2
        ham bacon
        bacon spam 3
        spam spam"""
        ) as tmpfname:
            self._testNCOLOrLGL(func=Graph.Read_Ncol, fname=tmpfname)

        with temporary_file(
            """\
        eggs spam
        ham eggs
        ham bacon
        bacon spam
        spam spam"""
        ) as tmpfname:
            g = Graph.Read_Ncol(tmpfname)
            self.assertTrue(
                "name" in g.vertex_attributes() and "weight" not in g.edge_attributes()
            )

    @unittest.skipIf(pd is None, "test case depends on Pandas")
    def testNCOLWithDataFrame(self):
        # Regression test for https://github.com/igraph/python-igraph/issues/446
        from pandas import DataFrame

        df = DataFrame({"from": [1, 2], "to": [2, 3]})
        self.assertRaises(TypeError, Graph.Read_Ncol, df)

    def testLGL(self):
        with temporary_file(
            """\
        # eggs
        spam 1
        # ham
        eggs 2
        bacon
        # bacon
        spam 3
        # spam
        spam"""
        ) as tmpfname:
            self._testNCOLOrLGL(func=Graph.Read_Lgl, fname=tmpfname)

        with temporary_file(
            """\
        # eggs
        spam
        # ham
        eggs
        bacon
        # bacon
        spam
        # spam
        spam"""
        ) as tmpfname:
            with warnings.catch_warnings():
                warnings.simplefilter("ignore")
                g = Graph.Read_Lgl(tmpfname)
            self.assertTrue(
                "name" in g.vertex_attributes() and "weight" not in g.edge_attributes()
            )

        # This is not an LGL file; we are testing error handling here
        with temporary_file(
            """\
        1 2
        1 3
        """
        ) as tmpfname:
            with self.assertRaises(InternalError):
                Graph.Read_Lgl(tmpfname)

    def testLGLWithIOModule(self):
        with temporary_file(
            """\
        # eggs
        spam 1
        # ham
        eggs 2
        bacon
        # bacon
        spam 3
        # spam
        spam"""
        ) as tmpfname:
            with io.open(tmpfname, "r") as fp:
                self._testNCOLOrLGL(
                    func=Graph.Read_Lgl, fname=fp, can_be_reopened=False
                )

    def testAdjacency(self):
        with temporary_file(
            """\
        # Test comment line
        0 1 1 0 0 0
        1 0 1 0 0 0
        1 1 0 0 0 0
        0 0 0 0 2 2
        0 0 0 2 0 2
        0 0 0 2 2 0
        """
        ) as tmpfname:
            g = Graph.Read_Adjacency(tmpfname)
            self.assertTrue(isinstance(g, Graph))
            self.assertEqual(g.vcount(), 6)
            self.assertEqual(g.ecount(), 18)
            self.assertTrue(g.is_directed())
            self.assertTrue("weight" not in g.edge_attributes())

            g = Graph.Read_Adjacency(tmpfname, attribute="weight")
            self.assertTrue(isinstance(g, Graph))
            self.assertEqual(g.vcount(), 6)
            self.assertEqual(g.ecount(), 12)
            self.assertTrue(g.is_directed())
            self.assertTrue(g.es["weight"] == [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2])

            g.write_adjacency(tmpfname)

    def testGraphML(self):
        with temporary_file(GRAPHML_EXAMPLE_FILE) as tmpfname:
            try:
                g = Graph.Read_GraphML(tmpfname)
            except NotImplementedError as e:
                self.skipTest(str(e))

            self.assertTrue(isinstance(g, Graph))
            self.assertEqual(g.vcount(), 6)
            self.assertEqual(g.ecount(), 7)
            self.assertFalse(g.is_directed())
            self.assertTrue("name" in g.vertex_attributes())

            g.write_graphml(tmpfname)
            g.write_graphml(tmpfname, prefixattr=False)

    def testGraphMLz(self):
        with temporary_file(
            gzip.compress(GRAPHML_EXAMPLE_FILE.encode("utf-8"))
        ) as tmpfname:
            try:
                g = Graph.Read_GraphMLz(tmpfname)
            except NotImplementedError as e:
                self.skipTest(str(e))

            self.assertTrue(isinstance(g, Graph))
            self.assertEqual(g.vcount(), 6)
            self.assertEqual(g.ecount(), 7)
            self.assertFalse(g.is_directed())
            self.assertTrue("name" in g.vertex_attributes())

    def testPickle(self):
        pickle = [
            128,
            2,
            99,
            105,
            103,
            114,
            97,
            112,
            104,
            10,
            71,
            114,
            97,
            112,
            104,
            10,
            113,
            1,
            40,
            75,
            3,
            93,
            113,
            2,
            75,
            1,
            75,
            2,
            134,
            113,
            3,
            97,
            137,
            125,
            125,
            125,
            116,
            82,
            113,
            4,
            125,
            98,
            46,
        ]
        pickle = bytes(pickle)
        with temporary_file(pickle, "wb", binary=True) as tmpfname:
            g = Graph.Read_Pickle(pickle)
            self.assertTrue(isinstance(g, Graph))
            self.assertTrue(g.vcount() == 3 and g.ecount() == 1 and not g.is_directed())
            g.write_pickle(tmpfname)

    def testDictList(self):
        g = Graph.Full(3)

        # Check with vertex ids
        self.assertEqual(
            g.to_dict_list(),
            (
                [{}, {}, {}],
                [
                    {"source": 0, "target": 1},
                    {"source": 0, "target": 2},
                    {"source": 1, "target": 2},
                ],
            ),
        )

        # Check failure for vertex names
        self.assertRaises(AttributeError, g.to_dict_list, False)

        # Check with vertex names
        g.vs["name"] = ["apple", "pear", "peach"]
        self.assertEqual(
            g.to_dict_list(),
            (
                [{"name": "apple"}, {"name": "pear"}, {"name": "peach"}],
                [
                    {"source": 0, "target": 1},
                    {"source": 0, "target": 2},
                    {"source": 1, "target": 2},
                ],
            ),
        )
        self.assertEqual(
            g.to_dict_list(use_vids=False),
            (
                [{"name": "apple"}, {"name": "pear"}, {"name": "peach"}],
                [
                    {"source": "apple", "target": "pear"},
                    {"source": "apple", "target": "peach"},
                    {"source": "pear", "target": "peach"},
                ],
            ),
        )

    def testTupleList(self):
        g = Graph.Full(3)

        # Check with vertex ids
        self.assertEqual(
            g.to_tuple_list(),
            [(0, 1), (0, 2), (1, 2)],
        )

        # Check failure for edge names
        self.assertRaises(AttributeError, g.to_tuple_list, True, "name")

        # Edge attributes
        g.es["name"] = ["first_edge", "second", None]
        self.assertEqual(
            g.to_tuple_list(edge_attrs="name"),
            [(0, 1, "first_edge"), (0, 2, "second"), (1, 2, None)],
        )
        self.assertEqual(
            g.to_tuple_list(edge_attrs=["name"]),
            [(0, 1, "first_edge"), (0, 2, "second"), (1, 2, None)],
        )

        # Missing vertex names
        self.assertRaises(AttributeError, g.to_tuple_list, False)

        # Vertex names
        g.vs["name"] = ["apple", "pear", "peach"]
        self.assertEqual(
            g.to_tuple_list(use_vids=False, edge_attrs="name"),
            [
                ("apple", "pear", "first_edge"),
                ("apple", "peach", "second"),
                ("pear", "peach", None),
            ],
        )

    def testSequenceDict(self):
        g = Graph.Full(3)

        # Check with vertex ids
        self.assertEqual(g.to_list_dict(), {0: [1, 2], 1: [2]})
        self.assertEqual(
            g.to_list_dict(sequence_constructor=tuple),
            {0: (1, 2), 1: (2,)},
        )

        # Check failure for vertex names
        self.assertRaises(AttributeError, g.to_list_dict, False)

        # Check with vertex names
        g.vs["name"] = ["apple", "pear", "peach"]
        self.assertEqual(
            g.to_list_dict(use_vids=False),
            {"apple": ["pear", "peach"], "pear": ["peach"]},
        )

    def testDictDict(self):
        g = Graph([(0, 1), (0, 2), (1, 2)])

        # Check with vertex ids, no edge attrs
        self.assertEqual(
            g.to_dict_dict(),
            {0: {1: {}, 2: {}}, 1: {2: {}}},
        )

        # With vertex ids, edge attrs
        g.es["name"] = ["first_edge", "second", None]
        # Check with vertex ids, incomplete edge attrs
        self.assertEqual(
            g.to_dict_dict(),
            {
                0: {1: {"name": "first_edge"}, 2: {"name": "second"}},
                1: {2: {"name": None}},
            },
        )
        self.assertEqual(
            g.to_dict_dict(skip_none=True),
            {0: {1: {"name": "first_edge"}, 2: {"name": "second"}}, 1: {2: {}}},
        )

        # With vertex names
        g.vs["name"] = ["apple", "pear", "peach"]
        self.assertEqual(
            g.to_dict_dict(use_vids=False),
            {
                "apple": {"pear": {"name": "first_edge"}, "peach": {"name": "second"}},
                "pear": {"peach": {"name": None}},
            },
        )
        self.assertEqual(
            g.to_dict_dict(use_vids=False, skip_none=True),
            {
                "apple": {"pear": {"name": "first_edge"}, "peach": {"name": "second"}},
                "pear": {"peach": {}},
            },
        )

    @unittest.skipIf(pd is None, "test case depends on Pandas")
    def testVertexDataFrames(self):
        g = Graph([(0, 1), (0, 2), (0, 3), (1, 2), (2, 4)])

        # No vertex names, no attributes
        df = g.get_vertex_dataframe()
        self.assertEqual(df.shape, (5, 0))
        self.assertEqual(list(df.index), [0, 1, 2, 3, 4])

        # Vertex names, no attributes
        g.vs["name"] = ["eggs", "spam", "ham", "bacon", "yello"]
        df = g.get_vertex_dataframe()
        self.assertEqual(df.shape, (5, 1))
        self.assertEqual(list(df.index), [0, 1, 2, 3, 4])
        self.assertEqual(list(df["name"]), g.vs["name"])
        self.assertEqual(list(df.columns), ["name"])

        # Vertex names and attributes (common case)
        g.vs["weight"] = [0, 5, 1, 4, 42]
        df = g.get_vertex_dataframe()
        self.assertEqual(df.shape, (5, 2))
        self.assertEqual(list(df.index), [0, 1, 2, 3, 4])
        self.assertEqual(list(df["name"]), g.vs["name"])
        self.assertEqual(set(df.columns), {"name", "weight"})
        self.assertEqual(list(df["weight"]), g.vs["weight"])

        # No vertex names, with attributes (common case)
        g = Graph([(0, 1), (0, 2), (0, 3), (1, 2), (2, 4)])
        g.vs["weight"] = [0, 5, 1, 4, 42]
        df = g.get_vertex_dataframe()
        self.assertEqual(df.shape, (5, 1))
        self.assertEqual(list(df.index), [0, 1, 2, 3, 4])
        self.assertEqual(list(df.columns), ["weight"])
        self.assertEqual(list(df["weight"]), g.vs["weight"])

    @unittest.skipIf(pd is None, "test case depends on Pandas")
    def testEdgeDataFrames(self):
        g = Graph([(0, 1), (0, 2), (0, 3), (1, 2), (2, 4)])

        # No edge names, no attributes
        df = g.get_edge_dataframe()
        self.assertEqual(df.shape, (5, 2))
        self.assertEqual(list(df.index), [0, 1, 2, 3, 4])
        self.assertEqual(list(df.columns), ["source", "target"])

        # Edge names, no attributes
        g.es["name"] = ["my", "list", "of", "five", "edges"]
        df = g.get_edge_dataframe()
        self.assertEqual(df.shape, (5, 3))
        self.assertEqual(list(df.index), [0, 1, 2, 3, 4])
        self.assertEqual(list(df["name"]), g.es["name"])
        self.assertEqual(set(df.columns), {"source", "target", "name"})

        # No edge names, with attributes
        g = Graph([(0, 1), (0, 2), (0, 3), (1, 2), (2, 4)])
        g.es["weight"] = [6, -0.4, 0, 1, 3]
        df = g.get_edge_dataframe()
        self.assertEqual(df.shape, (5, 3))
        self.assertEqual(list(df.index), [0, 1, 2, 3, 4])
        self.assertEqual(set(df.columns), {"source", "target", "weight"})
        self.assertEqual(list(df["weight"]), g.es["weight"])

        # Edge names, with weird attributes
        g.es["name"] = ["my", "list", "of", "five", "edges"]
        g.es["weight"] = [6, -0.4, 0, 1, 3]
        g.es["source"] = ["this", "is", "a", "little", "tricky"]
        df = g.get_edge_dataframe()
        self.assertEqual(df.shape, (5, 5))
        self.assertEqual(list(df.index), [0, 1, 2, 3, 4])
        self.assertEqual(set(df.columns), {"source", "target", "name", "weight"})
        self.assertEqual(list(df["name"]), g.es["name"])
        self.assertEqual(list(df["weight"]), g.es["weight"])

        i = 2 + list(df.columns[2:]).index("source")
        self.assertEqual(list(df.iloc[:, i]), g.es["source"])

    @unittest.skipIf(nx is None, "test case depends on networkx")
    def testGraphNetworkx(self):
        # Undirected
        g = Graph.Ring(10)
        g["gattr"] = "graph_attribute"
        g.vs["vattr"] = list(range(g.vcount()))
        g.es["eattr"] = list(range(len(g.es)))

        # Go to networkx and back
        g_nx = g.to_networkx()
        g2 = Graph.from_networkx(g_nx)

        self.assertFalse(g2.is_directed())
        self.assertTrue(g2.is_simple())
        self.assertEqual(g.vcount(), g2.vcount())
        self.assertEqual(sorted(g.get_edgelist()), sorted(g2.get_edgelist()))

        # Test attributes
        self.assertEqual(g.attributes(), g2.attributes())
        self.assertEqual(sorted(["vattr", "_nx_name"]), sorted(g2.vertex_attributes()))
        for i, vertex in enumerate(g.vs):
            vertex2 = g2.vs[i]
            for an in vertex.attribute_names():
                if an == "vattr":
                    continue
                self.assertEqual(vertex.attributes()[an], vertex2.attributes()[an])
        self.assertEqual(g.edge_attributes(), g2.edge_attributes())
        for edge in g.es:
            eid = g2.get_eid(edge.source, edge.target)
            edge2 = g2.es[eid]
            for an in edge.attribute_names():
                self.assertEqual(edge.attributes()[an], edge2.attributes()[an])

        # Directed
        g = Graph.Ring(10, directed=True)

        # Go to networkx and back
        g_nx = g.to_networkx()
        g2 = Graph.from_networkx(g_nx)

        self.assertTrue(g2.is_directed())
        self.assertTrue(g2.is_simple())
        self.assertEqual(g.vcount(), g2.vcount())
        self.assertEqual(sorted(g.get_edgelist()), sorted(g2.get_edgelist()))

        # Test networkx with custom node hashables
        # In this case each node is a tuple of ints
        g_nx = nx.DiGraph()
        inf = float("inf")
        g_nx.add_edges_from(
            (
                ((0, 0), (1, 1), {"weight": 3.0}),
                ((0, 0), (1, 0), {"weight": inf}),
            )
        )
        g = Graph.from_networkx(g_nx)
        self.assertTrue(g.is_directed())
        self.assertEqual(g.vcount(), 3)
        self.assertTrue(g.es["weight"] == [3.0, inf] or g.es["weight"] == [inf, 3.0])

    @unittest.skipIf(nx is None, "test case depends on networkx")
    def testMultigraphNetworkx(self):
        # Undirected
        g = Graph.Ring(10)
        g.add_edge(0, 1)
        g["gattr"] = "graph_attribute"
        g.vs["vattr"] = list(range(g.vcount()))
        g.es["eattr"] = list(range(len(g.es)))

        # Go to networkx and back
        g_nx = g.to_networkx()
        g2 = Graph.from_networkx(g_nx)

        self.assertFalse(g2.is_directed())
        self.assertFalse(g2.is_simple())
        self.assertEqual(g.vcount(), g2.vcount())
        self.assertEqual(sorted(g.get_edgelist()), sorted(g2.get_edgelist()))

        # Test attributes
        self.assertEqual(g.attributes(), g2.attributes())
        self.assertEqual(
            sorted(["vattr", "_nx_name"]),
            sorted(g2.vertex_attributes()),
        )
        self.assertEqual(
            sorted(["eattr", "_nx_multiedge_key"]),
            sorted(g2.edge_attributes()),
        )

        # Testing parallel edges is a bit more tricky
        edge2_found = set()
        for edge in g.es:
            # Go through all parallel edges between these two vertices
            for edge2 in g2.es:
                if edge2 in edge2_found:
                    continue
                if edge.source != edge2.source:
                    continue
                if edge.target != edge2.target:
                    continue
                # Check all attributes between these two
                for an in edge.attribute_names():
                    if edge.attributes()[an] != edge2.attributes()[an]:
                        break
                else:
                    # Correspondence found
                    edge2_found.add(edge2)
                    break

            else:
                self.assertTrue(False)

        # Directed
        g = Graph.Ring(10, directed=True)
        g.add_edge(0, 1)

        # Go to networkx and back
        g_nx = g.to_networkx()
        g2 = Graph.from_networkx(g_nx)

        self.assertTrue(g2.is_directed())
        self.assertFalse(g2.is_simple())
        self.assertEqual(g.vcount(), g2.vcount())
        self.assertEqual(sorted(g.get_edgelist()), sorted(g2.get_edgelist()))

    @unittest.skipIf(gt is None, "test case depends on graph-tool")
    def testGraphGraphTool(self):
        # Undirected
        g = Graph.Ring(10)
        g["gattr"] = "graph_attribute"
        g.vs["vattr"] = list(range(g.vcount()))
        g.es["eattr"] = list(range(len(g.es)))

        # Go to graph-tool and back
        g_gt = g.to_graph_tool(
            graph_attributes={"gattr": "object"},
            vertex_attributes={"vattr": "int"},
            edge_attributes={"eattr": "int"},
        )
        g2 = Graph.from_graph_tool(g_gt)

        self.assertFalse(g2.is_directed())
        self.assertTrue(g2.is_simple())
        self.assertEqual(g.vcount(), g2.vcount())
        self.assertEqual(sorted(g.get_edgelist()), sorted(g2.get_edgelist()))

        # Test attributes
        self.assertEqual(g.attributes(), g2.attributes())
        self.assertEqual(g.vertex_attributes(), g2.vertex_attributes())
        for i, vertex in enumerate(g.vs):
            vertex2 = g2.vs[i]
            for an in vertex.attribute_names():
                self.assertEqual(vertex.attributes()[an], vertex2.attributes()[an])
        self.assertEqual(g.edge_attributes(), g2.edge_attributes())
        for edge in g.es:
            eid = g2.get_eid(edge.source, edge.target)
            edge2 = g2.es[eid]
            for an in edge.attribute_names():
                self.assertEqual(edge.attributes()[an], edge2.attributes()[an])

        # Directed
        g = Graph.Ring(10, directed=True)

        # Go to graph-tool and back
        g_gt = g.to_graph_tool()

        g2 = Graph.from_graph_tool(g_gt)

        self.assertTrue(g2.is_directed())
        self.assertTrue(g2.is_simple())
        self.assertEqual(g.vcount(), g2.vcount())
        self.assertEqual(sorted(g.get_edgelist()), sorted(g2.get_edgelist()))

    @unittest.skipIf(gt is None, "test case depends on graph-tool")
    def testMultigraphGraphTool(self):
        # Undirected
        g = Graph.Ring(10)
        g.add_edge(0, 1)
        g["gattr"] = "graph_attribute"
        g.vs["vattr"] = list(range(g.vcount()))
        g.es["eattr"] = list(range(len(g.es)))

        # Go to graph-tool and back
        g_gt = g.to_graph_tool(
            graph_attributes={"gattr": "object"},
            vertex_attributes={"vattr": "int"},
            edge_attributes={"eattr": "int"},
        )
        g2 = Graph.from_graph_tool(g_gt)

        self.assertFalse(g2.is_directed())
        self.assertFalse(g2.is_simple())
        self.assertEqual(g.vcount(), g2.vcount())
        self.assertEqual(sorted(g.get_edgelist()), sorted(g2.get_edgelist()))

        # Test attributes
        self.assertEqual(g.attributes(), g2.attributes())
        self.assertEqual(g.vertex_attributes(), g2.vertex_attributes())
        for i, vertex in enumerate(g.vs):
            vertex2 = g2.vs[i]
            for an in vertex.attribute_names():
                self.assertEqual(vertex.attributes()[an], vertex2.attributes()[an])
        self.assertEqual(g.edge_attributes(), g2.edge_attributes())
        # Testing parallel edges is a bit more tricky
        edge2_found = set()
        for edge in g.es:
            # Go through all parallel edges between these two vertices
            for edge2 in g2.es:
                if edge2 in edge2_found:
                    continue
                if edge.source != edge2.source:
                    continue
                if edge.target != edge2.target:
                    continue
                # Check all attributes between these two
                for an in edge.attribute_names():
                    if edge.attributes()[an] != edge2.attributes()[an]:
                        break
                else:
                    # Correspondence found
                    edge2_found.add(edge2)
                    break

            else:
                self.assertTrue(False)

        # Directed
        g = Graph.Ring(10, directed=True)
        g.add_edge(0, 1)

        # Go to graph-tool and back
        g_gt = g.to_graph_tool()
        g2 = Graph.from_graph_tool(g_gt)

        self.assertTrue(g2.is_directed())
        self.assertFalse(g2.is_simple())
        self.assertEqual(g.vcount(), g2.vcount())
        self.assertEqual(sorted(g.get_edgelist()), sorted(g2.get_edgelist()))


def suite():
    foreign_suite = unittest.defaultTestLoader.loadTestsFromTestCase(ForeignTests)
    return unittest.TestSuite([foreign_suite])


def test():
    runner = unittest.TextTestRunner()
    runner.run(suite())


if __name__ == "__main__":
    test()
