#!/usr/bin/env python
from nose.tools import assert_equal
from nose.tools import assert_is
from nose.tools import assert_not_equal
from nose.tools import assert_raises

import networkx as nx
from networkx.testing.utils import *

from test_graph import BaseAttrGraphTester, TestGraph


class BaseMultiGraphTester(BaseAttrGraphTester):
    def test_has_edge(self):
        G = self.K3
        assert_equal(G.has_edge(0, 1), True)
        assert_equal(G.has_edge(0, -1), False)
        assert_equal(G.has_edge(0, 1, 0), True)
        assert_equal(G.has_edge(0, 1, 1), False)

    def test_get_edge_data(self):
        G = self.K3
        assert_equal(G.get_edge_data(0, 1), {0: {}})
        assert_equal(G[0][1], {0: {}})
        assert_equal(G[0][1][0], {})
        assert_equal(G.get_edge_data(10, 20), None)
        assert_equal(G.get_edge_data(0, 1, 0), {})

    def test_adjacency(self):
        G = self.K3
        assert_equal(dict(G.adjacency()),
                     {0: {1: {0: {}}, 2: {0: {}}},
                      1: {0: {0: {}}, 2: {0: {}}},
                      2: {0: {0: {}}, 1: {0: {}}}})

    def deepcopy_edge_attr(self, H, G):
        assert_equal(G[1][2][0]['foo'], H[1][2][0]['foo'])
        G[1][2][0]['foo'].append(1)
        assert_not_equal(G[1][2][0]['foo'], H[1][2][0]['foo'])

    def shallow_copy_edge_attr(self, H, G):
        assert_equal(G[1][2][0]['foo'], H[1][2][0]['foo'])
        G[1][2][0]['foo'].append(1)
        assert_equal(G[1][2][0]['foo'], H[1][2][0]['foo'])

    def graphs_equal(self, H, G):
        assert_equal(G._adj, H._adj)
        assert_equal(G._node, H._node)
        assert_equal(G.graph, H.graph)
        assert_equal(G.name, H.name)
        if not G.is_directed() and not H.is_directed():
            assert_is(H._adj[1][2][0], H._adj[2][1][0])
            assert_is(G._adj[1][2][0], G._adj[2][1][0])
        else:  # at least one is directed
            if not G.is_directed():
                G._pred = G._adj
                G._succ = G._adj
            if not H.is_directed():
                H._pred = H._adj
                H._succ = H._adj
            assert_equal(G._pred, H._pred)
            assert_equal(G._succ, H._succ)
            assert_is(H._succ[1][2][0], H._pred[2][1][0])
            assert_is(G._succ[1][2][0], G._pred[2][1][0])

    def same_attrdict(self, H, G):
        # same attrdict in the edgedata
        old_foo = H[1][2][0]['foo']
        H.adj[1][2][0]['foo'] = 'baz'
        assert_equal(G._adj, H._adj)
        H.adj[1][2][0]['foo'] = old_foo
        assert_equal(G._adj, H._adj)

        old_foo = H.nodes[0]['foo']
        H.nodes[0]['foo'] = 'baz'
        assert_equal(G._node, H._node)
        H.nodes[0]['foo'] = old_foo
        assert_equal(G._node, H._node)

    def different_attrdict(self, H, G):
        # used by graph_equal_but_different
        old_foo = H[1][2][0]['foo']
        H.adj[1][2][0]['foo'] = 'baz'
        assert_not_equal(G._adj, H._adj)
        H.adj[1][2][0]['foo'] = old_foo
        assert_equal(G._adj, H._adj)

        old_foo = H.nodes[0]['foo']
        H.nodes[0]['foo'] = 'baz'
        assert_not_equal(G._node, H._node)
        H.nodes[0]['foo'] = old_foo
        assert_equal(G._node, H._node)

    def test_to_undirected(self):
        G = self.K3
        self.add_attributes(G)
        H = nx.MultiGraph(G)
        self.is_shallow_copy(H, G)
        H = G.to_undirected()
        self.is_deepcopy(H, G)

    def test_to_directed(self):
        G = self.K3
        self.add_attributes(G)
        H = nx.MultiDiGraph(G)
        self.is_shallow_copy(H, G)
        H = G.to_directed()
        self.is_deepcopy(H, G)

    def test_number_of_edges_selfloops(self):
        G = self.K3
        G.add_edge(0, 0)
        G.add_edge(0, 0)
        G.add_edge(0, 0, key='parallel edge')
        G.remove_edge(0, 0, key='parallel edge')
        assert_equal(G.number_of_edges(0, 0), 2)
        G.remove_edge(0, 0)
        assert_equal(G.number_of_edges(0, 0), 1)

    def test_edge_lookup(self):
        G = self.Graph()
        G.add_edge(1, 2, foo='bar')
        G.add_edge(1, 2, 'key', foo='biz')
        assert_edges_equal(G.edges[1, 2, 0], {'foo': 'bar'})
        assert_edges_equal(G.edges[1, 2, 'key'], {'foo': 'biz'})

    def test_edge_attr4(self):
        G = self.Graph()
        G.add_edge(1, 2, key=0, data=7, spam='bar', bar='foo')
        assert_edges_equal(G.edges(data=True),
                           [(1, 2, {'data': 7, 'spam': 'bar', 'bar': 'foo'})])
        G[1][2][0]['data'] = 10  # OK to set data like this
        assert_edges_equal(G.edges(data=True),
                           [(1, 2, {'data': 10, 'spam': 'bar', 'bar': 'foo'})])

        G.adj[1][2][0]['data'] = 20
        assert_edges_equal(G.edges(data=True),
                           [(1, 2, {'data': 20, 'spam': 'bar', 'bar': 'foo'})])
        G.edges[1, 2, 0]['data'] = 21  # another spelling, "edge"
        assert_edges_equal(G.edges(data=True),
                           [(1, 2, {'data': 21, 'spam': 'bar', 'bar': 'foo'})])
        G.adj[1][2][0]['listdata'] = [20, 200]
        G.adj[1][2][0]['weight'] = 20
        assert_edges_equal(G.edges(data=True),
                           [(1, 2, {'data': 21, 'spam': 'bar', 'bar': 'foo',
                            'listdata': [20, 200], 'weight':20})])


class TestMultiGraph(BaseMultiGraphTester, TestGraph):
    def setUp(self):
        self.Graph = nx.MultiGraph
        # build K3
        ed1, ed2, ed3 = ({0: {}}, {0: {}}, {0: {}})
        self.k3adj = {0: {1: ed1, 2: ed2},
                      1: {0: ed1, 2: ed3},
                      2: {0: ed2, 1: ed3}}
        self.k3edges = [(0, 1), (0, 2), (1, 2)]
        self.k3nodes = [0, 1, 2]
        self.K3 = self.Graph()
        self.K3._adj = self.k3adj
        self.K3._node = {}
        self.K3._node[0] = {}
        self.K3._node[1] = {}
        self.K3._node[2] = {}

    def test_data_input(self):
        G = self.Graph(data={1: [2], 2: [1]}, name="test")
        assert_equal(G.name, "test")
        expected = [(1, {2: {0: {}}}), (2, {1: {0: {}}})]
        assert_equal(sorted(G.adj.items()), expected)

    def test_getitem(self):
        G = self.K3
        assert_equal(G[0], {1: {0: {}}, 2: {0: {}}})
        assert_raises(KeyError, G.__getitem__, 'j')
        assert_raises((TypeError, nx.NetworkXError), G.__getitem__, ['A'])

    def test_remove_node(self):
        G = self.K3
        G.remove_node(0)
        assert_equal(G.adj, {1: {2: {0: {}}}, 2: {1: {0: {}}}})
        assert_raises((KeyError, nx.NetworkXError), G.remove_node, -1)

    def test_add_edge(self):
        G = self.Graph()
        G.add_edge(0, 1)
        assert_equal(G.adj, {0: {1: {0: {}}}, 1: {0: {0: {}}}})
        G = self.Graph()
        G.add_edge(*(0, 1))
        assert_equal(G.adj, {0: {1: {0: {}}}, 1: {0: {0: {}}}})

    def test_add_edge_conflicting_key(self):
        G = self.Graph()
        G.add_edge(0, 1, key=1)
        G.add_edge(0, 1)
        assert_equal(G.number_of_edges(), 2)
        G = self.Graph()
        G.add_edges_from([(0, 1, 1, {})])
        G.add_edges_from([(0, 1)])
        assert_equal(G.number_of_edges(), 2)

    def test_add_edges_from(self):
        G = self.Graph()
        G.add_edges_from([(0, 1), (0, 1, {'weight': 3})])
        assert_equal(G.adj, {0: {1: {0: {}, 1: {'weight': 3}}},
                             1: {0: {0: {}, 1: {'weight': 3}}}})
        G.add_edges_from([(0, 1), (0, 1, {'weight': 3})], weight=2)
        assert_equal(G.adj, {0: {1: {0: {}, 1: {'weight': 3},
                                     2: {'weight': 2}, 3: {'weight': 3}}},
                             1: {0: {0: {}, 1: {'weight': 3},
                                     2: {'weight': 2}, 3: {'weight': 3}}}})
        G = self.Graph()
        edges = [(0, 1, {'weight': 3}), (0, 1, (('weight', 2),)),
                 (0, 1, 5), (0, 1, 's')]
        G.add_edges_from(edges)
        keydict = {0: {'weight': 3}, 1: {'weight': 2}, 5: {}, 's': {}}
        assert_equal(G._adj, {0: {1: keydict}, 1: {0: keydict}})

        # too few in tuple
        assert_raises(nx.NetworkXError, G.add_edges_from, [(0,)])
        # too many in tuple
        assert_raises(nx.NetworkXError, G.add_edges_from, [(0, 1, 2, 3, 4)])
        # not a tuple
        assert_raises(TypeError, G.add_edges_from, [0])

    def test_remove_edge(self):
        G = self.K3
        G.remove_edge(0, 1)
        assert_equal(G.adj, {0: {2: {0: {}}},
                             1: {2: {0: {}}},
                             2: {0: {0: {}},
                                 1: {0: {}}}})

        assert_raises((KeyError, nx.NetworkXError), G.remove_edge, -1, 0)
        assert_raises((KeyError, nx.NetworkXError), G.remove_edge, 0, 2,
                      key=1)

    def test_remove_edges_from(self):
        G = self.K3.copy()
        G.remove_edges_from([(0, 1)])
        kd = {0: {}}
        assert_equal(G.adj, {0: {2: kd}, 1: {2: kd}, 2: {0: kd, 1: kd}})
        G.remove_edges_from([(0, 0)])  # silent fail
        self.K3.add_edge(0, 1)
        G = self.K3.copy()
        G.remove_edges_from(list(G.edges(data=True, keys=True)))
        assert_equal(G.adj, {0: {}, 1: {}, 2: {}})
        G = self.K3.copy()
        G.remove_edges_from(list(G.edges(data=False, keys=True)))
        assert_equal(G.adj, {0: {}, 1: {}, 2: {}})
        G = self.K3.copy()
        G.remove_edges_from(list(G.edges(data=False, keys=False)))
        assert_equal(G.adj, {0: {}, 1: {}, 2: {}})
        G = self.K3.copy()
        G.remove_edges_from([(0, 1, 0), (0, 2, 0, {}), (1, 2)])
        assert_equal(G.adj, {0: {1: {1: {}}}, 1: {0: {1: {}}}, 2: {}})

    def test_remove_multiedge(self):
        G = self.K3
        G.add_edge(0, 1, key='parallel edge')
        G.remove_edge(0, 1, key='parallel edge')
        assert_equal(G.adj, {0: {1: {0: {}}, 2: {0: {}}},
                             1: {0: {0: {}}, 2: {0: {}}},
                             2: {0: {0: {}}, 1: {0: {}}}})
        G.remove_edge(0, 1)
        kd = {0: {}}
        assert_equal(G.adj, {0: {2: kd}, 1: {2: kd}, 2: {0: kd, 1: kd}})
        assert_raises((KeyError, nx.NetworkXError), G.remove_edge, -1, 0)


class TestEdgeSubgraph(object):
    """Unit tests for the :meth:`MultiGraph.edge_subgraph` method."""

    def setup(self):
        # Create a doubly-linked path graph on five nodes.
        G = nx.MultiGraph()
        nx.add_path(G, range(5))
        nx.add_path(G, range(5))
        # Add some node, edge, and graph attributes.
        for i in range(5):
            G.nodes[i]['name'] = 'node{}'.format(i)
        G.adj[0][1][0]['name'] = 'edge010'
        G.adj[0][1][1]['name'] = 'edge011'
        G.adj[3][4][0]['name'] = 'edge340'
        G.adj[3][4][1]['name'] = 'edge341'
        G.graph['name'] = 'graph'
        # Get the subgraph induced by one of the first edges and one of
        # the last edges.
        self.G = G
        self.H = G.edge_subgraph([(0, 1, 0), (3, 4, 1)])

    def test_correct_nodes(self):
        """Tests that the subgraph has the correct nodes."""
        assert_equal([0, 1, 3, 4], sorted(self.H.nodes()))

    def test_correct_edges(self):
        """Tests that the subgraph has the correct edges."""
        assert_equal([(0, 1, 0, 'edge010'), (3, 4, 1, 'edge341')],
                     sorted(self.H.edges(keys=True, data='name')))

    def test_add_node(self):
        """Tests that adding a node to the original graph does not
        affect the nodes of the subgraph.

        """
        self.G.add_node(5)
        assert_equal([0, 1, 3, 4], sorted(self.H.nodes()))

    def test_remove_node(self):
        """Tests that removing a node in the original graph does
        affect the nodes of the subgraph.

        """
        self.G.remove_node(0)
        assert_equal([1, 3, 4], sorted(self.H.nodes()))

    def test_node_attr_dict(self):
        """Tests that the node attribute dictionary of the two graphs is
        the same object.

        """
        for v in self.H:
            assert_equal(self.G.nodes[v], self.H.nodes[v])
        # Making a change to G should make a change in H and vice versa.
        self.G.nodes[0]['name'] = 'foo'
        assert_equal(self.G.nodes[0], self.H.nodes[0])
        self.H.nodes[1]['name'] = 'bar'
        assert_equal(self.G.nodes[1], self.H.nodes[1])

    def test_edge_attr_dict(self):
        """Tests that the edge attribute dictionary of the two graphs is
        the same object.

        """
        for u, v, k in self.H.edges(keys=True):
            assert_equal(self.G._adj[u][v][k], self.H._adj[u][v][k])
        # Making a change to G should make a change in H and vice versa.
        self.G._adj[0][1][0]['name'] = 'foo'
        assert_equal(self.G._adj[0][1][0]['name'],
                     self.H._adj[0][1][0]['name'])
        self.H._adj[3][4][1]['name'] = 'bar'
        assert_equal(self.G._adj[3][4][1]['name'],
                     self.H._adj[3][4][1]['name'])

    def test_graph_attr_dict(self):
        """Tests that the graph attribute dictionary of the two graphs
        is the same object.

        """
        assert_is(self.G.graph, self.H.graph)
