
"""
    path_functions
    ~~~~~~~~~~~~~~

    Contains functions used for computation of accessible mutational paths.

    :author: Fahad Khalid
    :license: MIT, see LICENSE for more details.
"""

import igraph

#from memory_profiler import profile

from genonets_utils import Utils


class PathAnalyzer :
	# Constructor
	def __init__(self, network, netUtils, delta) :
		# Reference to the network on which to perform this
		# analysis
		self.network = network

		# Get a reference to the NetworkUtils object
		self.netUtils = netUtils

		# Get a reference to the BitSeqManipulator in use
		self.bitManip = netUtils.bitManip

		# Keep a copy of the delta value
		self.delta = delta

		# Summit vertex, to be populated later.
		self.summitId = None

		# Dict to store all accessible paths. {vertexId : [paths]}
		self.allPathsToPeak = self.initPathsToPeak()

	def initPathsToPeak(self) :
		return { vId : [] for vId in range(len(self.network.vs["sequences"])) }

	def getSummitId(self) :
		return self.summitId

	def getAllPathsToPeak(self) :
		return self.allPathsToPeak

	def getPathsThruVtxs(self) :
		# List: Each element is the No. of paths through the vertex Id
		# 		corresponding to the index
		pathsThruVtx = [ 0 for i in range(self.network.vcount()) ]

		# Go through accessible paths corresponding to each vertex
		for vtxId in range(self.network.vcount()) :
			# Get the list of accessible paths for this vertex
			vtxPaths = self.network.vs[vtxId]["pathsToSummit"]

			# For each path,
			for path in vtxPaths :
				# For each vertex in the path,
				for vtx in path :
					# Increment the No. of paths that go through
					# this vertex
					pathsThruVtx[vtx] += 1

		return pathsThruVtx

	# Get the ratio of accessible paths to all paths of the
	# given length.
	def getAccessiblePaths(self, pathLength) :
		# Stats for the entire network
		totalPaths  = 0	# Total mutational paths (only shortest)
		allAccPaths = 0	# No. of accessible paths (only shortest)

		# Get the sequence with the highest score. This sequence
		# will represent the global peak. All paths use this
		# sequence as the target.
		summit = Utils.getSeqWithMaxScore(self.network, self.bitManip.seqLength)

		# Get the vertex object that represents the summit
		trgtVrtx = self.netUtils.getVertex(summit, self.network)

		# Store a copy of the summit
		self.summitId = trgtVrtx.index

		# Get a list of all sequences in the network
		sequences = self.network.vs["sequences"]
		# Remove the target itself
		sequences.remove(summit)

		# For each sequence in the network
		for source in sequences :
			# Get all shortest paths as well as all accessible
			# shortest paths from source to summit
			self.getShortestAccPaths(source, trgtVrtx, 0)

			# shrtPaths, accPaths = self.getShortestAccPaths(source, trgtVrtx, 
			# 							pathLength + 1)

		# 	# If at least one shortest path of length == 'pathLength' was
		# 	# found,
		# 	if shrtPaths :
		# 		# Increment the counts
		# 		totalPaths  += float(len(shrtPaths))
		# 		allAccPaths += float(len(accPaths))

		# try :
		# 	return float(allAccPaths) / float(totalPaths)
		# except ZeroDivisionError :
		# 	return 0

		return

	# From within all shortest paths between source sequence and the 
	# given target vertex, computes all accessible paths.
	# For computation, returns only those paths that are of the given
	# length. However, all accessible paths are stored regardless of
	# size. This is then used in the visualization.
	#@profile
	def getShortestAccPaths(self, source, trgtVrtx, pathLength) :
		# Get the source and target vertices
		srcVrtx  = self.netUtils.getVertex(source, self.network)

		# Get all shortest paths between source and target
		allShrtPaths = self.network.get_all_shortest_paths(srcVrtx, 
			trgtVrtx, mode=igraph.OUT)

		# Get all shortest accessible paths
		shrtAccPaths = [ self.network.vs[path].indices \
							for path in allShrtPaths \
							if self.isAccessible(path) ]
		# Store the paths
		self.allPathsToPeak[srcVrtx.index].extend(shrtAccPaths)

		# # If the shortest path length is the same as the required
		# # length,
		# if len(allShrtPaths[0]) == pathLength :
		# 	allShrtAccPaths = [ self.network.vs[path]["sequences"] \
		# 						for path in allShrtPaths \
		# 						if self.isAccessible(path) ]
		# else :
		# 	# We did not find a path of the required length
		# 	allShrtPaths = None
		# 	allShrtAccPaths = None

		# return allShrtPaths, allShrtAccPaths

		return

	# Determines whether the given path is an accessible path, i.e.,
	# scores on this path increase monotonously.
	def isAccessible(self, path) :
		isAcc = True

		# Get a list of escores for the sequences in the path
		escores = self.network.vs[path]["escores"]

		# Place holder to keep track of the highest score
		# encountered inside the loop
		maxYet = -0.5
		# For each escore in the list
		for i in range(len(escores) - 1) :
			# If the current score is higher than the max so far,
			if escores[i] > maxYet :
				# Assign current escore to max
				maxYet = escores[i]

			# If the next sequence in the path is in a lower bin,
			if (maxYet - self.delta > escores[i + 1]) :
				# The increase in e-score is not monotonous. Hence,
				# the path is not accessible.
				isAcc = False
				break

		return isAcc
