# distutils: language=c++

from cython.operator import dereference, preincrement

from libcpp.vector cimport vector
from libcpp cimport bool as bool_t
from libcpp.string cimport string

from .base cimport _Algorithm, Algorithm
from .graph cimport _Graph, Graph
from .helpers import stdstring
from .structures cimport count, index, node

cdef extern from "<networkit/Globals.hpp>" namespace "NetworKit":

	index _none "NetworKit::none"

none = _none

cdef extern from "cython_helper.h":
	void throw_runtime_error(string message)

cdef extern from "<networkit/reachability/ReachableNodes.hpp>":

	cdef cppclass _ReachableNodes "NetworKit::ReachableNodes"(_Algorithm):
		_ReachableNodes(_Graph G, bool_t exact) except +
		count numberOfReachableNodes(node u) except +
		count numberOfReachableNodesLB(node u) except +
		count numberOfReachableNodesUB(node u) except +
		bool_t exact

cdef class ReachableNodes(Algorithm):
	"""
	ReachableNodes(G, exact)	

	Determines or estimates the number of reachable nodes from each node in the graph.

	Parameters
	----------
	G : networkit.Graph
		The input graph.
	exact : bool
		Whether or not to compute the number of reachable nodes exactly. Only
		used for directed graphs, on undirected graphs the number of reachable
		nodes from every node can be computed in linear time.
	"""

	cdef Graph _G

	def __cinit__(self, Graph G, exact = True):
		self._G = G
		self._this = new _ReachableNodes(G._this, exact)

	def numberOfReachableNodes(self, node u):
		"""
		numberOfReachableNodes(u)

		Returns the number of reachable nodes from the given node 'u'. Only available if 'exact' is true.

		Parameters
		----------
		u : int
			A node.

		Returns
		-------
		int
			The number of nodes reachable from 'u'.
		"""
		return (<_ReachableNodes*>(self._this)).numberOfReachableNodes(u)

	def numberOfReachableNodesLB(self, node u):
		"""
		numberOfReachableNodesLB(u)

		Returns a lower bound of the number of reachable nodes from the given node 'u'.

		Parameters
		----------
		u : int
			A node.

		Returns
		-------
		int
			Lower bound of number of nodes reachable from 'u'.
		"""
		return (<_ReachableNodes*>(self._this)).numberOfReachableNodesLB(u)

	def numberOfReachableNodesUB(self, node u):
		"""
		numberOfReachableNodesUB(u)

		Returns an upper bound of the number of reachable nodes from the given node 'u'.

		Parameters
		----------
		u : int
			A node.

		Returns
		-------
		int
			Upper bound of number of nodes reachable from 'u'.
		"""
		return (<_ReachableNodes*>(self._this)).numberOfReachableNodesUB(u)

	property exact:
		def __get__(self):
			return (<_ReachableNodes*>(self._this)).exact
		def __set__(self, bool_t exact):
			""" Use a different edge direction. """
			(<_ReachableNodes*>(self._this)).exact = exact

cdef cppclass PathCallbackWrapper:
	void* callback
	__init__(object callback):
		this.callback = <void*>callback
	void cython_call_operator(vector[node] path):
		cdef bool_t error = False
		cdef string message
		try:
			(<object>callback)(path)
		except Exception as e:
			error = True
			message = stdstring("An Exception occurred, aborting execution of iterator: {0}".format(e))
		if (error):
			throw_runtime_error(message)

cdef extern from "<networkit/reachability/AllSimplePaths.hpp>":

	cdef cppclass _AllSimplePaths "NetworKit::AllSimplePaths"(_Algorithm):
		_AllSimplePaths(_Graph G, node source, node target, count cutoff) except +
		count numberOfSimplePaths() except +
		vector[vector[node]] getAllSimplePaths() except +
		void forAllSimplePaths[Callback](Callback c) except +

cdef class AllSimplePaths(Algorithm):
	"""
	AllSimplePaths(G, source, target, cutoff=None)

	Algorithm to compute all existing simple paths from a source node to a
	target node. The maximum length of the paths can be fixed through 'cutoff'.

	Note
	----
	CAUTION: This algorithm could take a lot of time on large networks (many
	edges), especially if the cutoff value is high or not specified.

	Parameters
	----------
	G : networkit.Graph
		The input graph.
	source : int
		The source node.
	target : int
		The target node.
	cutoff : int, optional
		The maximum length of the simple paths. Default: None

	"""

	cdef Graph _G

	def __cinit__(self, Graph G, source, target, cutoff=none):
		self._G = G
		self._this = new _AllSimplePaths(G._this, source, target, cutoff)

	def numberOfSimplePaths(self):
		"""
		numberOfSimplePaths()

		Returns the number of simple paths.

		Returns
		-------
		int
			The number of simple paths.
		"""
		return (<_AllSimplePaths*>(self._this)).numberOfSimplePaths()

	def getAllSimplePaths(self):
		"""
		getAllSimplePaths()

		Returns all the simple paths from source to target.

		Returns
		-------
		list(list(int))
			A list containing all simple paths (each represented by a list of nodes).
		"""
		return (<_AllSimplePaths*>(self._this)).getAllSimplePaths()

	def forAllSimplePaths(self, object callback):
		""" 
		forAllSimplePaths(callback)		

		More efficient path iterator. Iterates over all the simple paths.

		Parameters
		-----------
		callback : object
			Any callable object that takes the parameter path
		"""
		cdef PathCallbackWrapper* wrapper
		try:
			wrapper = new PathCallbackWrapper(callback)
			(<_AllSimplePaths*>(self._this)).forAllSimplePaths[PathCallbackWrapper](dereference(wrapper))
		finally:
			del wrapper
