from itertools import combinations

from nose.tools import assert_equal
from nose.tools import assert_false
from nose.tools import assert_true

import networkx as nx
from networkx.generators.geometric import euclidean


def l1dist(x, y):
    return sum(abs(a - b) for a, b in zip(x, y))


class TestRandomGeometricGraph(object):
    """Unit tests for the :func:`~networkx.random_geometric_graph`
    function.

    """
    def test_number_of_nodes(self):
        G = nx.random_geometric_graph(50, 0.25)
        assert_equal(len(G), 50)
        G = nx.random_geometric_graph(range(50), 0.25)
        assert_equal(len(G), 50)

    def test_distances(self):
        """Tests that pairs of vertices adjacent if and only if they are
        within the prescribed radius.

        """
        # Use the Euclidean metric, the default according to the
        # documentation.
        dist = euclidean
        G = nx.random_geometric_graph(50, 0.25)
        for u, v in combinations(G, 2):
            # Adjacent vertices must be within the given distance.
            if v in G[u]:
                assert_true(dist(G.nodes[u]['pos'], G.nodes[v]['pos']) <= 0.25)
            # Nonadjacent vertices must be at greater distance.
            else:
                assert_false(dist(G.nodes[u]['pos'], G.nodes[v]['pos']) <= 0.25)

    def test_p(self):
        """Tests for providing an alternate distance metric to the
        generator.

        """
        # Use the L1 metric.
        dist = l1dist
        G = nx.random_geometric_graph(50, 0.25, p=1)
        for u, v in combinations(G, 2):
            # Adjacent vertices must be within the given distance.
            if v in G[u]:
                assert_true(dist(G.nodes[u]['pos'], G.nodes[v]['pos']) <= 0.25)
            # Nonadjacent vertices must be at greater distance.
            else:
                assert_false(dist(G.nodes[u]['pos'], G.nodes[v]['pos']) <= 0.25)

    def test_node_names(self):
        """Tests using values other than sequential numbers as node IDs.

        """
        import string
        nodes = list(string.ascii_lowercase)
        G = nx.random_geometric_graph(nodes, 0.25)
        assert_equal(len(G), len(nodes))

        dist = euclidean
        for u, v in combinations(G, 2):
            # Adjacent vertices must be within the given distance.
            if v in G[u]:
                assert_true(dist(G.nodes[u]['pos'], G.nodes[v]['pos']) <= 0.25)
            # Nonadjacent vertices must be at greater distance.
            else:
                assert_false(dist(G.nodes[u]['pos'], G.nodes[v]['pos']) <= 0.25)


def join(G, u, v, theta, alpha, metric):
    """Returns ``True`` if and only if the nodes whose attributes are
    ``du`` and ``dv`` should be joined, according to the threshold
    condition for geographical threshold graphs.

    ``G`` is an undirected NetworkX graph, and ``u`` and ``v`` are nodes
    in that graph. The nodes must have node attributes ``'pos'`` and
    ``'weight'``.

    ``metric`` is a distance metric.

    """
    du, dv = G.nodes[u], G.nodes[v]
    u_pos, v_pos = du['pos'], dv['pos']
    u_weight, v_weight = du['weight'], dv['weight']
    return theta * metric(u_pos, v_pos) ** alpha <= u_weight + v_weight


class TestGeographicalThresholdGraph(object):
    """Unit tests for the :func:`~networkx.geographical_threshold_graph`
    function.

    """

    def test_number_of_nodes(self):
        G = nx.geographical_threshold_graph(50, 100)
        assert_equal(len(G), 50)
        G = nx.geographical_threshold_graph(range(50), 100)
        assert_equal(len(G), 50)

    def test_distances(self):
        """Tests that pairs of vertices adjacent if and only if their
        distances meet the given threshold.

        """
        # Use the Euclidean metric, the default according to the
        # documentation.
        dist = euclidean
        G = nx.geographical_threshold_graph(50, 100)
        for u, v in combinations(G, 2):
            # Adjacent vertices must not exceed the threshold.
            if v in G[u]:
                assert_true(join(G, u, v, 100, 2, dist))
            # Nonadjacent vertices must exceed the threshold.
            else:
                assert_false(join(G, u, v, 100, 2, dist))

    def test_metric(self):
        """Tests for providing an alternate distance metric to the
        generator.

        """
        # Use the L1 metric.
        dist = l1dist
        G = nx.geographical_threshold_graph(50, 100, metric=dist)
        for u, v in combinations(G, 2):
            # Adjacent vertices must not exceed the threshold.
            if v in G[u]:
                assert_true(join(G, u, v, 100, 2, dist))
            # Nonadjacent vertices must exceed the threshold.
            else:
                assert_false(join(G, u, v, 100, 2, dist))


class TestWaxmanGraph(object):
    """Unit tests for the :func:`~networkx.waxman_graph` function."""

    def test_number_of_nodes_1(self):
        G = nx.waxman_graph(50, 0.5, 0.1)
        assert_equal(len(G), 50)
        G = nx.waxman_graph(range(50), 0.5, 0.1)
        assert_equal(len(G), 50)

    def test_number_of_nodes_2(self):
        G = nx.waxman_graph(50, 0.5, 0.1, L=1)
        assert_equal(len(G), 50)
        G = nx.waxman_graph(range(50), 0.5, 0.1, L=1)
        assert_equal(len(G), 50)

    def test_metric(self):
        """Tests for providing an alternate distance metric to the
        generator.

        """
        # Use the L1 metric.
        dist = l1dist
        G = nx.waxman_graph(50, 0.5, 0.1, metric=dist)
        assert_equal(len(G), 50)


class TestNavigableSmallWorldGraph(object):

    def test_navigable_small_world(self):
        G = nx.navigable_small_world_graph(5, p=1, q=0)
        gg = nx.grid_2d_graph(5, 5).to_directed()
        assert_true(nx.is_isomorphic(G, gg))

        G = nx.navigable_small_world_graph(5, p=1, q=0, dim=3)
        gg = nx.grid_graph([5, 5, 5]).to_directed()
        assert_true(nx.is_isomorphic(G, gg))

        G = nx.navigable_small_world_graph(5, p=1, q=0, dim=1)
        gg = nx.grid_graph([5]).to_directed()
        assert_true(nx.is_isomorphic(G, gg))
