#!/usr/bin/env python
from random import Random

import pytest


import networkx as nx
from networkx import convert_node_labels_to_integers as cnlti


class TestDistance:
    def setup_method(self):
        G = cnlti(nx.grid_2d_graph(4, 4), first_label=1, ordering="sorted")
        self.G = G

    def test_eccentricity(self):
        assert nx.eccentricity(self.G, 1) == 6
        e = nx.eccentricity(self.G)
        assert e[1] == 6

        sp = dict(nx.shortest_path_length(self.G))
        e = nx.eccentricity(self.G, sp=sp)
        assert e[1] == 6

        e = nx.eccentricity(self.G, v=1)
        assert e == 6

        # This behavior changed in version 1.8 (ticket #739)
        e = nx.eccentricity(self.G, v=[1, 1])
        assert e[1] == 6
        e = nx.eccentricity(self.G, v=[1, 2])
        assert e[1] == 6

        # test against graph with one node
        G = nx.path_graph(1)
        e = nx.eccentricity(G)
        assert e[0] == 0
        e = nx.eccentricity(G, v=0)
        assert e == 0
        pytest.raises(nx.NetworkXError, nx.eccentricity, G, 1)

        # test against empty graph
        G = nx.empty_graph()
        e = nx.eccentricity(G)
        assert e == {}

    def test_diameter(self):
        assert nx.diameter(self.G) == 6

    def test_radius(self):
        assert nx.radius(self.G) == 4

    def test_periphery(self):
        assert set(nx.periphery(self.G)) == set([1, 4, 13, 16])

    def test_center(self):
        assert set(nx.center(self.G)) == set([6, 7, 10, 11])

    def test_bound_diameter(self):
        assert nx.diameter(self.G, usebounds=True) == 6

    def test_bound_radius(self):
        assert nx.radius(self.G, usebounds=True) == 4

    def test_bound_periphery(self):
        result = set([1, 4, 13, 16])
        assert set(nx.periphery(self.G, usebounds=True)) == result

    def test_bound_center(self):
        result = set([6, 7, 10, 11])
        assert set(nx.center(self.G, usebounds=True)) == result

    def test_radius_exception(self):
        G = nx.Graph()
        G.add_edge(1, 2)
        G.add_edge(3, 4)
        pytest.raises(nx.NetworkXError, nx.diameter, G)

    def test_eccentricity_infinite(self):
        with pytest.raises(nx.NetworkXError):
            G = nx.Graph([(1, 2), (3, 4)])
            e = nx.eccentricity(G)

    def test_eccentricity_undirected_not_connected(self):
        with pytest.raises(nx.NetworkXError):
            G = nx.Graph([(1, 2), (3, 4)])
            e = nx.eccentricity(G, sp=1)

    def test_eccentricity_directed_weakly_connected(self):
        with pytest.raises(nx.NetworkXError):
            DG = nx.DiGraph([(1, 2), (1, 3)])
            nx.eccentricity(DG)


class TestResistanceDistance:
    @classmethod
    def setup_class(cls):
        global np
        global sp_sparse
        np = pytest.importorskip('numpy')
        scipy = pytest.importorskip('scipy')
        sp_sparse = pytest.importorskip('scipy.sparse')

    def setup_method(self):
        G = nx.Graph()
        G.add_edge(1, 2, weight=2)
        G.add_edge(2, 3, weight=4)
        G.add_edge(3, 4, weight=1)
        G.add_edge(1, 4, weight=3)
        self.G = G

    def test_laplacian_submatrix(self):
        from networkx.algorithms.distance_measures import _laplacian_submatrix
        M = sp_sparse.csr_matrix([[1, 2, 3],
                                     [4, 5, 6],
                                     [7, 8, 9]], dtype=np.float32)
        N = sp_sparse.csr_matrix([[5, 6],
                                     [8, 9]], dtype=np.float32)
        Mn, Mn_nodelist = _laplacian_submatrix(1, M, [1, 2, 3])
        assert Mn_nodelist == [2, 3]
        assert np.allclose(Mn.toarray(), N.toarray())

    def test_laplacian_submatrix_square(self):
        with pytest.raises(nx.NetworkXError):
            from networkx.algorithms.distance_measures import _laplacian_submatrix
            M = sp_sparse.csr_matrix([[1, 2],
                                         [4, 5],
                                         [7, 8]], dtype=np.float32)
            _laplacian_submatrix(1, M, [1, 2, 3])

    def test_laplacian_submatrix_matrix_node_dim(self):
        with pytest.raises(nx.NetworkXError):
            from networkx.algorithms.distance_measures import _laplacian_submatrix
            M = sp_sparse.csr_matrix([[1, 2, 3],
                                         [4, 5, 6],
                                         [7, 8, 9]], dtype=np.float32)
            _laplacian_submatrix(1, M, [1, 2, 3, 4])

    def test_resistance_distance(self):
        rd = nx.resistance_distance(self.G, 1, 3, 'weight', True)
        test_data = 1/(1/(2+4) + 1/(1+3))
        assert round(rd, 5) == round(test_data, 5)

    def test_resistance_distance_noinv(self):
        rd = nx.resistance_distance(self.G, 1, 3, 'weight', False)
        test_data = 1/(1/(1/2+1/4) + 1/(1/1+1/3))
        assert round(rd, 5) == round(test_data, 5)

    def test_resistance_distance_no_weight(self):
        rd = nx.resistance_distance(self.G, 1, 3)
        assert round(rd, 5) == 1

    def test_resistance_distance_neg_weight(self):
        self.G[2][3]['weight'] = -4
        rd = nx.resistance_distance(self.G, 1, 3, 'weight', True)
        test_data = 1/(1/(2+-4) + 1/(1+3))
        assert round(rd, 5) == round(test_data, 5)

    def test_multigraph(self):
        G = nx.MultiGraph()
        G.add_edge(1, 2, weight=2)
        G.add_edge(2, 3, weight=4)
        G.add_edge(3, 4, weight=1)
        G.add_edge(1, 4, weight=3)
        rd = nx.resistance_distance(G, 1, 3, 'weight', True)
        assert np.isclose(rd, 1/(1/(2+4) + 1/(1+3)))

    def test_resistance_distance_div0(self):
        with pytest.raises(ZeroDivisionError):
            self.G[1][2]['weight'] = 0
            nx.resistance_distance(self.G, 1, 3, 'weight')

    def test_resistance_distance_not_connected(self):
        with pytest.raises(nx.NetworkXError):
            self.G.add_node(5)
            nx.resistance_distance(self.G, 1, 5)

    def test_resistance_distance_same_node(self):
        with pytest.raises(nx.NetworkXError):
            nx.resistance_distance(self.G, 1, 1)

    def test_resistance_distance_nodeA_not_in_graph(self):
        with pytest.raises(nx.NetworkXError):
            nx.resistance_distance(self.G, 9, 1)

    def test_resistance_distance_nodeB_not_in_graph(self):
        with pytest.raises(nx.NetworkXError):
            nx.resistance_distance(self.G, 1, 9)


class TestBarycenter(object):
    """Test :func:`networkx.algorithms.distance_measures.barycenter`."""
    def barycenter_as_subgraph(self, g, **kwargs):
        """Return the subgraph induced on the barycenter of g"""
        b = nx.barycenter(g, **kwargs)
        assert isinstance(b, list)
        assert set(b) <= set(g)
        return g.subgraph(b)

    def test_must_be_connected(self):
        pytest.raises(nx.NetworkXNoPath, nx.barycenter, nx.empty_graph(5))

    def test_sp_kwarg(self):
        # Complete graph K_5. Normally it works...
        K_5 = nx.complete_graph(5)
        sp = dict(nx.shortest_path_length(K_5))
        assert nx.barycenter(K_5, sp=sp) == list(K_5)

        # ...but not with the weight argument
        for u, v, data in K_5.edges.data():
            data['weight'] = 1
        pytest.raises(ValueError, nx.barycenter, K_5, sp=sp, weight='weight')

        # ...and a corrupted sp can make it seem like K_5 is disconnected
        del sp[0][1]
        pytest.raises(nx.NetworkXNoPath, nx.barycenter, K_5, sp=sp)

    def test_trees(self):
        """The barycenter of a tree is a single vertex or an edge.

        See [West01]_, p. 78.
        """
        prng = Random(0xdeadbeef)
        for i in range(50):
            RT = nx.random_tree(prng.randint(1, 75), prng)
            b = self.barycenter_as_subgraph(RT)
            if len(b) == 2:
                assert b.size() == 1
            else:
                assert len(b) == 1
                assert b.size() == 0

    def test_this_one_specific_tree(self):
        """Test the tree pictured at the bottom of [West01]_, p. 78."""
        g = nx.Graph({
            'a': ['b'],
            'b': ['a', 'x'],
            'x': ['b', 'y'],
            'y': ['x', 'z'],
            'z': ['y', 0, 1, 2, 3, 4],
            0: ['z'], 1: ['z'], 2: ['z'], 3: ['z'], 4: ['z']})
        b = self.barycenter_as_subgraph(g, attr='barycentricity')
        assert list(b) == ['z']
        assert not b.edges
        expected_barycentricity = {0: 23, 1: 23, 2: 23, 3: 23, 4: 23,
                                   'a': 35, 'b': 27, 'x': 21, 'y': 17, 'z': 15
                                   }
        for node, barycentricity in expected_barycentricity.items():
            assert g.nodes[node]['barycentricity'] == barycentricity

        # Doubling weights should do nothing but double the barycentricities
        for edge in g.edges:
            g.edges[edge]['weight'] = 2
        b = self.barycenter_as_subgraph(g, weight='weight',
                                        attr='barycentricity2')
        assert list(b) == ['z']
        assert not b.edges
        for node, barycentricity in expected_barycentricity.items():
            assert g.nodes[node]['barycentricity2'] == barycentricity*2
