"""
Unit tests for adjlist.
"""

import io

import pytest

import networkx as nx
from networkx.utils import edges_equal, graphs_equal, nodes_equal


class TestGenerateAdjlist:
    @pytest.mark.parametrize("graph_type", [nx.Graph, nx.MultiGraph])
    def test_undirected(self, graph_type):
        G = nx.complete_graph(5, create_using=graph_type)
        lines = [
            "0 1 2 3 4",
            "1 2 3 4",
            "2 3 4",
            "3 4",
            "4",
        ]
        assert list(nx.generate_adjlist(G)) == lines

    @pytest.mark.parametrize("graph_type", [nx.DiGraph, nx.MultiDiGraph])
    def test_directed(self, graph_type):
        G = nx.complete_graph(5, create_using=graph_type)
        lines = [
            "0 1 2 3 4",
            "1 0 2 3 4",
            "2 0 1 3 4",
            "3 0 1 2 4",
            "4 0 1 2 3",
        ]
        assert list(nx.generate_adjlist(G)) == lines

        G = nx.path_graph(5, create_using=graph_type)
        G.add_edge(1, 0)
        lines = [
            "0 1",
            "1 2 0",
            "2 3",
            "3 4",
            "4",
        ]
        assert list(nx.generate_adjlist(G)) == lines

    @pytest.mark.parametrize("delimiter", [" ", ",", "\t"])
    def test_delimiter(self, delimiter):
        G = nx.complete_graph(3)
        lines = [
            f"0{delimiter}1{delimiter}2",
            f"1{delimiter}2",
            f"2",
        ]
        assert list(nx.generate_adjlist(G, delimiter=delimiter)) == lines

    def test_multiple_edges_undirected(self):
        G = nx.complete_graph(3, create_using=nx.MultiGraph)
        G.add_edge(0, 1)
        lines = [
            "0 1 1 2",
            "1 2",
            "2",
        ]
        assert list(nx.generate_adjlist(G)) == lines

    def test_multiple_edges_directed(self):
        G = nx.complete_graph(3, create_using=nx.MultiDiGraph)
        G.add_edge(0, 1)
        lines = [
            "0 1 1 2",
            "1 0 2",
            "2 0 1",
        ]
        assert list(nx.generate_adjlist(G)) == lines

        G.add_edge(1, 0)
        lines[1] = "1 0 0 2"
        assert list(nx.generate_adjlist(G)) == lines

    def test_multiple_edges_with_data(self):
        G = nx.complete_graph(3, create_using=nx.MultiGraph)
        G.add_edge(0, 1, weight=1)
        G.add_edge(0, 1, weight=2)
        lines = [
            "0 1 1 1 2",
            "1 2",
            "2",
        ]
        assert list(nx.generate_adjlist(G)) == lines

    def test_with_self_loop(self):
        G = nx.complete_graph(3)
        G.add_edge(0, 0)
        lines = [
            "0 1 2 0",
            "1 2",
            "2",
        ]
        assert list(nx.generate_adjlist(G)) == lines


class TestAdjlist:
    @classmethod
    def setup_class(cls):
        cls.G = nx.Graph(name="test")
        e = [("a", "b"), ("b", "c"), ("c", "d"), ("d", "e"), ("e", "f"), ("a", "f")]
        cls.G.add_edges_from(e)
        cls.G.add_node("g")
        cls.DG = nx.DiGraph(cls.G)
        cls.XG = nx.MultiGraph()
        cls.XG.add_weighted_edges_from([(1, 2, 5), (1, 2, 5), (1, 2, 1), (3, 3, 42)])
        cls.XDG = nx.MultiDiGraph(cls.XG)

    def test_read_multiline_adjlist_1(self):
        # Unit test for https://networkx.lanl.gov/trac/ticket/252
        s = b"""# comment line
1 2
# comment line
2
3
"""
        bytesIO = io.BytesIO(s)
        G = nx.read_multiline_adjlist(bytesIO)
        adj = {"1": {"3": {}, "2": {}}, "3": {"1": {}}, "2": {"1": {}}}
        assert graphs_equal(G, nx.Graph(adj))

    def test_unicode(self, tmp_path):
        G = nx.Graph()
        name1 = chr(2344) + chr(123) + chr(6543)
        name2 = chr(5543) + chr(1543) + chr(324)
        G.add_edge(name1, "Radiohead", **{name2: 3})

        fname = tmp_path / "adjlist.txt"
        nx.write_multiline_adjlist(G, fname)
        H = nx.read_multiline_adjlist(fname)
        assert graphs_equal(G, H)

    def test_latin1_err(self, tmp_path):
        G = nx.Graph()
        name1 = chr(2344) + chr(123) + chr(6543)
        name2 = chr(5543) + chr(1543) + chr(324)
        G.add_edge(name1, "Radiohead", **{name2: 3})
        fname = tmp_path / "adjlist.txt"
        with pytest.raises(UnicodeEncodeError):
            nx.write_multiline_adjlist(G, fname, encoding="latin-1")

    def test_latin1(self, tmp_path):
        G = nx.Graph()
        name1 = "Bj" + chr(246) + "rk"
        name2 = chr(220) + "ber"
        G.add_edge(name1, "Radiohead", **{name2: 3})
        fname = tmp_path / "adjlist.txt"
        nx.write_multiline_adjlist(G, fname, encoding="latin-1")
        H = nx.read_multiline_adjlist(fname, encoding="latin-1")
        assert graphs_equal(G, H)

    def test_parse_adjlist(self):
        lines = ["1 2 5", "2 3 4", "3 5", "4", "5"]
        nx.parse_adjlist(lines, nodetype=int)  # smoke test
        with pytest.raises(TypeError):
            nx.parse_adjlist(lines, nodetype="int")
        lines = ["1 2 5", "2 b", "c"]
        with pytest.raises(TypeError):
            nx.parse_adjlist(lines, nodetype=int)

    def test_adjlist_graph(self, tmp_path):
        G = self.G
        fname = tmp_path / "adjlist.txt"
        nx.write_adjlist(G, fname)
        H = nx.read_adjlist(fname)
        H2 = nx.read_adjlist(fname)
        assert H is not H2  # they should be different graphs
        assert nodes_equal(list(H), list(G))
        assert edges_equal(list(H.edges()), list(G.edges()))

    def test_adjlist_digraph(self, tmp_path):
        G = self.DG
        fname = tmp_path / "adjlist.txt"
        nx.write_adjlist(G, fname)
        H = nx.read_adjlist(fname, create_using=nx.DiGraph())
        H2 = nx.read_adjlist(fname, create_using=nx.DiGraph())
        assert H is not H2  # they should be different graphs
        assert nodes_equal(list(H), list(G))
        assert edges_equal(list(H.edges()), list(G.edges()), directed=True)

    def test_adjlist_integers(self, tmp_path):
        fname = tmp_path / "adjlist.txt"
        G = nx.convert_node_labels_to_integers(self.G)
        nx.write_adjlist(G, fname)
        H = nx.read_adjlist(fname, nodetype=int)
        H2 = nx.read_adjlist(fname, nodetype=int)
        assert H is not H2  # they should be different graphs
        assert nodes_equal(list(H), list(G))
        assert edges_equal(list(H.edges()), list(G.edges()))

    def test_adjlist_multigraph(self, tmp_path):
        G = self.XG
        fname = tmp_path / "adjlist.txt"
        nx.write_adjlist(G, fname)
        H = nx.read_adjlist(fname, nodetype=int, create_using=nx.MultiGraph())
        H2 = nx.read_adjlist(fname, nodetype=int, create_using=nx.MultiGraph())
        assert H is not H2  # they should be different graphs
        assert nodes_equal(list(H), list(G))
        assert edges_equal(list(H.edges()), list(G.edges()))

    def test_adjlist_multidigraph(self, tmp_path):
        G = self.XDG
        fname = tmp_path / "adjlist.txt"
        nx.write_adjlist(G, fname)
        H = nx.read_adjlist(fname, nodetype=int, create_using=nx.MultiDiGraph())
        H2 = nx.read_adjlist(fname, nodetype=int, create_using=nx.MultiDiGraph())
        assert H is not H2  # they should be different graphs
        assert nodes_equal(list(H), list(G))
        assert edges_equal(list(H.edges()), list(G.edges()), directed=True)

    def test_adjlist_delimiter(self):
        fh = io.BytesIO()
        G = nx.path_graph(3)
        nx.write_adjlist(G, fh, delimiter=":")
        fh.seek(0)
        H = nx.read_adjlist(fh, nodetype=int, delimiter=":")
        assert nodes_equal(list(H), list(G))
        assert edges_equal(list(H.edges()), list(G.edges()))


class TestMultilineAdjlist:
    @classmethod
    def setup_class(cls):
        cls.G = nx.Graph(name="test")
        e = [("a", "b"), ("b", "c"), ("c", "d"), ("d", "e"), ("e", "f"), ("a", "f")]
        cls.G.add_edges_from(e)
        cls.G.add_node("g")
        cls.DG = nx.DiGraph(cls.G)
        cls.DG.remove_edge("b", "a")
        cls.DG.remove_edge("b", "c")
        cls.XG = nx.MultiGraph()
        cls.XG.add_weighted_edges_from([(1, 2, 5), (1, 2, 5), (1, 2, 1), (3, 3, 42)])
        cls.XDG = nx.MultiDiGraph(cls.XG)

    def test_parse_multiline_adjlist(self):
        lines = [
            "1 2",
            "b {'weight':3, 'name': 'Frodo'}",
            "c {}",
            "d 1",
            "e {'weight':6, 'name': 'Saruman'}",
        ]
        nx.parse_multiline_adjlist(iter(lines))  # smoke test
        with pytest.raises(TypeError):
            nx.parse_multiline_adjlist(iter(lines), nodetype=int)
        nx.parse_multiline_adjlist(iter(lines), edgetype=str)  # smoke test
        with pytest.raises(TypeError):
            nx.parse_multiline_adjlist(iter(lines), nodetype=int)
        lines = ["1 a"]
        with pytest.raises(TypeError):
            nx.parse_multiline_adjlist(iter(lines))
        lines = ["a 2"]
        with pytest.raises(TypeError):
            nx.parse_multiline_adjlist(iter(lines), nodetype=int)
        lines = ["1 2"]
        with pytest.raises(TypeError):
            nx.parse_multiline_adjlist(iter(lines))
        lines = ["1 2", "2 {}"]
        with pytest.raises(TypeError):
            nx.parse_multiline_adjlist(iter(lines))

    def test_multiline_adjlist_graph(self, tmp_path):
        G = self.G
        fname = tmp_path / "adjlist.txt"
        nx.write_multiline_adjlist(G, fname)
        H = nx.read_multiline_adjlist(fname)
        H2 = nx.read_multiline_adjlist(fname)
        assert H is not H2  # they should be different graphs
        assert nodes_equal(list(H), list(G))
        assert edges_equal(list(H.edges()), list(G.edges()))

    def test_multiline_adjlist_digraph(self, tmp_path):
        G = self.DG
        fname = tmp_path / "adjlist.txt"
        nx.write_multiline_adjlist(G, fname)
        H = nx.read_multiline_adjlist(fname, create_using=nx.DiGraph())
        H2 = nx.read_multiline_adjlist(fname, create_using=nx.DiGraph())
        assert H is not H2  # they should be different graphs
        assert nodes_equal(list(H), list(G))
        assert edges_equal(list(H.edges()), list(G.edges()), directed=True)

    def test_multiline_adjlist_integers(self, tmp_path):
        fname = tmp_path / "adjlist.txt"
        G = nx.convert_node_labels_to_integers(self.G)
        nx.write_multiline_adjlist(G, fname)
        H = nx.read_multiline_adjlist(fname, nodetype=int)
        H2 = nx.read_multiline_adjlist(fname, nodetype=int)
        assert H is not H2  # they should be different graphs
        assert nodes_equal(list(H), list(G))
        assert edges_equal(list(H.edges()), list(G.edges()))

    def test_multiline_adjlist_multigraph(self, tmp_path):
        G = self.XG
        fname = tmp_path / "adjlist.txt"
        nx.write_multiline_adjlist(G, fname)
        H = nx.read_multiline_adjlist(fname, nodetype=int, create_using=nx.MultiGraph())
        H2 = nx.read_multiline_adjlist(
            fname, nodetype=int, create_using=nx.MultiGraph()
        )
        assert H is not H2  # they should be different graphs
        assert nodes_equal(list(H), list(G))
        assert edges_equal(list(H.edges()), list(G.edges()))

    def test_multiline_adjlist_multidigraph(self, tmp_path):
        G = self.XDG
        fname = tmp_path / "adjlist.txt"
        nx.write_multiline_adjlist(G, fname)
        H = nx.read_multiline_adjlist(
            fname, nodetype=int, create_using=nx.MultiDiGraph()
        )
        H2 = nx.read_multiline_adjlist(
            fname, nodetype=int, create_using=nx.MultiDiGraph()
        )
        assert H is not H2  # they should be different graphs
        assert nodes_equal(list(H), list(G))
        assert edges_equal(list(H.edges()), list(G.edges()), directed=True)

    def test_multiline_adjlist_delimiter(self):
        fh = io.BytesIO()
        G = nx.path_graph(3)
        nx.write_multiline_adjlist(G, fh, delimiter=":")
        fh.seek(0)
        H = nx.read_multiline_adjlist(fh, nodetype=int, delimiter=":")
        assert nodes_equal(list(H), list(G))
        assert edges_equal(list(H.edges()), list(G.edges()))


@pytest.mark.parametrize(
    ("lines", "delim"),
    (
        (["1 2 5", "2 3 4", "3 5", "4", "5"], None),  # No extra whitespace
        (["1\t2\t5", "2\t3\t4", "3\t5", "4", "5"], "\t"),  # tab-delimited
        (
            ["1\t2\t5", "2\t3\t4", "3\t5\t", "4\t", "5"],
            "\t",
        ),  # tab-delimited, extra delims
        (
            ["1\t2\t5", "2\t3\t4", "3\t5\t\t\n", "4\t", "5"],
            "\t",
        ),  # extra delim+newlines
    ),
)
def test_adjlist_rstrip_parsing(lines, delim):
    """Regression test related to gh-7465"""
    expected = nx.Graph([(1, 2), (1, 5), (2, 3), (2, 4), (3, 5)])
    nx.utils.graphs_equal(nx.parse_adjlist(lines, delimiter=delim), expected)
