import networkx as nx
import numpy as np
import matplotlib.pyplot as plt
import operator
import random

# Here, we'll simulate a random surfer stepping from page to page numIter times
def pageranks_walk(G, numIter):
	visits = {}
	for v in G.nodes():
		visits[v] = 0
	
	v = random.choice(list(G.nodes()))
	for i in range(0, numIter):
		visits[v] += 1
		
		if G.out_degree(v) > 0:
			v = random.choice(list(G.successors(v)))
		else:
			v = random.choice(list(G.nodes()))
	
	pageranks = {}
	for v in G.nodes():
		pageranks[v] = visits[v] / numIter
	
	return pageranks

# Here, we'll explicitly calculate PageRanks in a vertex-centric iterative way
def pageranks_calc(G, numIter):
	pageranks = {}
	sinks = 0
	sinksNext = 0
	for v in G.nodes():
		pageranks[v] = 1.0 / G.order()
		if G.out_degree(v) == 0:
			sinks += pageranks[v]
		
	for i in range(0, numIter):
		for v in G.nodes():
			pageranks[v] = sinks / G.order()
			
			for e in G.in_edges(v):
				u = e[0]
				pageranks[v] += pageranks[u] / G.out_degree(u)
				
			if G.out_degree(v) == 0:
				sinksNext += pageranks[v]
		
		sinks = sinksNext
		sinksNext = 0
		
	return pageranks

# Here, we'll use a linear algebraic approach that is similar to the computation
# that we're doing above for the vertex-centric approach
def pageranks_algebraic(G, numIter):
	A = nx.to_numpy_matrix(G)
	D = np.zeros(len(A))
	for i in range(0, len(A)):
		D[i] = A.sum(axis=1)[i]
		
	D = np.linalg.inv(np.diag(D))
	M = np.transpose(D * A)
	
	p = np.full((len(A), 1), 1.0 / len(A))
	
	for i in range(0, numIter):
		p = M * p
	
	pageranks = {}
	count = 0
	for i in G.nodes:
		pageranks[i] = p[count]
		count += 1
		
	return pageranks

# Here, we'll compute personalized pageranks from some 'root' vertex
def pageranks_personalized(G, numIter, root):
	alpha = 0.1
	stops = {}
	for v in G.nodes():
		stops[v] = 0
	
	for i in range(0, numIter):
		v = root
		stop = False
		while stop == False:
			if random.random() < alpha:
				stop = True
			else:
				v = random.choice(list(G.successors(v)))
		
		stops[v] += 1
	
	pageranks = {}
	for v in G.nodes():
		pageranks[v] = stops[v] / numIter
	
	return pageranks



# Data from https://www.pro-football-reference.com/years/2021/games.htm
# Read it in and convert it into a graph of directed edges where the loser of
# each game points to the winner.
G = nx.read_edgelist("nfl.data", create_using=nx.DiGraph(), data = (("pL", int),("pW",int), ("ydsL",int), ("ydsW",int), ("toL", int),  ("toW", int)) )

# We'll consider a couple ways to represent this competition network. First, 
# we'll only use the winner/loser relationship. Next, we can effectively 
# 'weight' the edges by the scores that resulted from the game. The greater the
# point differential, the more weight we'll effectively give to the edges going
# from the loser to the winner.
D = nx.MultiDiGraph()
for e in G.edges():
	E = G.get_edge_data(e[0],e[1])
	D.add_edge(e[0], e[1]);
	
	# for i in range(0, E['pW']):
	# 	D.add_edge(e[0], e[1]);
		
	# for i in range(0, E['pL']):
	# 	D.add_edge(e[1], e[0]);

P = pageranks_walk(D, 100)
P = pageranks_calc(D, 100)
P = pageranks_algebraic(D, 100)
P = pageranks_personalized(D, 10000, 'Bengals')
P = sorted(P.items(), key=operator.itemgetter(1), reverse=True)
