#    Copyright (C) 2004-2018 by
#    Aric Hagberg <hagberg@lanl.gov>
#    Dan Schult <dschult@colgate.edu>
#    Pieter Swart <swart@lanl.gov>
#    All rights reserved.
#    BSD license.
#
# Author:  Aric Hagberg (hagberg@lanl.gov),
#          Pieter Swart (swart@lanl.gov),
#          Dan Schult(dschult@colgate.edu)
"""View of Graphs as SubGraph, Reverse, Directed, Undirected.

In some algorithms it is convenient to temporarily morph
a graph to exclude some nodes or edges. It should be better
to do that via a view than to remove and then re-add.
In other algorithms it is convenient to temporarily morph
a graph to reverse directed edges, or treat a directed graph
as undirected, etc. This module provides those graph views.

The resulting views are essentially read-only graphs that
report data from the orignal graph object. We provide an
attribute G._graph which points to the underlying graph object.

Note: Since graphviews look like graphs, one can end up with
view-of-view-of-view chains. Be careful with chains because
they become very slow with about 15 nested views.
For the common simple case of node induced subgraphs created
from the graph class, we short-cut the chain by returning a
subgraph of the original graph directly rather than a subgraph
of a subgraph. We are careful not to disrupt any edge filter in
the middle subgraph. In general, determining how to short-cut
the chain is tricky and much harder with restricted_views than
with induced subgraphs.
Often it is easiest to use .copy() to avoid chains.
"""
from networkx.classes.coreviews import UnionAdjacency, UnionMultiAdjacency, \
    FilterAtlas, FilterAdjacency, FilterMultiAdjacency
from networkx.classes.filters import no_filter
from networkx.exception import NetworkXError, NetworkXNotImplemented
# remove the graph class import when deprecated GraphView removed
from networkx.classes import Graph, DiGraph, MultiGraph, MultiDiGraph

import networkx as nx

__all__ = ['generic_graph_view', 'subgraph_view', 'reverse_view',
           'SubGraph', 'SubDiGraph', 'SubMultiGraph', 'SubMultiDiGraph',
           'ReverseView', 'MultiReverseView',
           'DiGraphView', 'MultiDiGraphView',
           'GraphView', 'MultiGraphView',
           ]


def generic_graph_view(G, create_using=None):
    if create_using is None:
        newG = G.__class__()
    else:
        newG = nx.empty_graph(0, create_using)
    if G.is_multigraph() != newG.is_multigraph():
        raise NetworkXError("Multigraph for G must agree with create_using")
    newG = nx.freeze(newG)

    # create view by assigning attributes from G
    newG._graph = G
    newG.graph = G.graph

    newG._node = G._node
    if newG.is_directed():
        if G.is_directed():
            newG._succ = G._succ
            newG._pred = G._pred
            newG._adj = G._succ
        else:
            newG._succ = G._adj
            newG._pred = G._adj
            newG._adj = G._adj
    elif G.is_directed():
        if G.is_multigraph():
            newG._adj = UnionMultiAdjacency(G._succ, G._pred)
        else:
            newG._adj = UnionAdjacency(G._succ, G._pred)
    else:
        newG._adj = G._adj
    return newG


def subgraph_view(G, filter_node=no_filter, filter_edge=no_filter):
    newG = nx.freeze(G.__class__())
    newG._NODE_OK = filter_node
    newG._EDGE_OK = filter_edge

    # create view by assigning attributes from G
    newG._graph = G
    newG.graph = G.graph

    newG._node = FilterAtlas(G._node, filter_node)
    if G.is_multigraph():
        Adj = FilterMultiAdjacency

        def reverse_edge(u, v, k): return filter_edge(v, u, k)
    else:
        Adj = FilterAdjacency

        def reverse_edge(u, v): return filter_edge(v, u)
    if G.is_directed():
        newG._succ = Adj(G._succ, filter_node, filter_edge)
        newG._pred = Adj(G._pred, filter_node, reverse_edge)
        newG._adj = newG._succ
    else:
        newG._adj = Adj(G._adj, filter_node, filter_edge)
    return newG


def reverse_view(G):
    if not G.is_directed():
        msg = "not implemented for undirected type"
        raise NetworkXNotImplemented(msg)
    newG = generic_graph_view(G)
    newG._succ, newG._pred = G._pred, G._succ
    newG._adj = newG._succ
    return newG


# The remaining definitions are for backward compatibility with v2.0 and 2.1
def ReverseView(G):
    # remove by v3 if not before
    import warnings
    msg = 'ReverseView is deprecated. Use reverse_view instead'
    warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
    return reverse_view(G)


def SubGraph(G, filter_node=no_filter, filter_edge=no_filter):
    # remove by v3 if not before
    import warnings
    msg = 'SubGraph is deprecated. Use subgraph_view instead'
    warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
    return subgraph_view(G, filter_node, filter_edge)


def GraphView(G):
    # remove by v3 if not before
    import warnings
    msg = 'GraphView is deprecated. Use generic_graph_view instead'
    warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
    return generic_graph_view(G, Graph)


def DiGraphView(G):
    # remove by v3 if not before
    import warnings
    msg = 'GraphView is deprecated. Use generic_graph_view instead'
    warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
    return generic_graph_view(G, DiGraph)


def MultiGraphView(G):
    # remove by v3 if not before
    import warnings
    msg = 'GraphView is deprecated. Use generic_graph_view instead'
    warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
    return generic_graph_view(G, MultiGraph)


def MultiDiGraphView(G):
    # remove by v3 if not before
    import warnings
    msg = 'GraphView is deprecated. Use generic_graph_view instead'
    warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
    return generic_graph_view(G, MultiDiGraph)


MultiReverseView = ReverseView
SubMultiGraph = SubMultiDiGraph = SubDiGraph = SubGraph
