import csv
import sys

from util import Node, StackFrontier, QueueFrontier

# Maps names to a set of corresponding person_ids
names = {}

# Maps person_ids to a dictionary of: name, birth, movies (a set of movie_ids)
people = {}

# Maps movie_ids to a dictionary of: title, year, stars (a set of person_ids)
movies = {}


def load_data(directory):
	"""
	Load data from CSV files into memory.
	"""
	# Load people
	with open(f"{directory}/people.csv", encoding="utf-8") as f:
		reader = csv.DictReader(f)
		for row in reader:
			people[row["id"]] = {
				"name": row["name"],
				"birth": row["birth"],
				"movies": set()
			}
			if row["name"].lower() not in names:
				names[row["name"].lower()] = {row["id"]}
			else:
				names[row["name"].lower()].add(row["id"])

	# Load movies
	with open(f"{directory}/movies.csv", encoding="utf-8") as f:
		reader = csv.DictReader(f)
		for row in reader:
			movies[row["id"]] = {
				"title": row["title"],
				"year": row["year"],
				"stars": set()
			}

	# Load stars
	with open(f"{directory}/stars.csv", encoding="utf-8") as f:
		reader = csv.DictReader(f)
		for row in reader:
			try:
				people[row["person_id"]]["movies"].add(row["movie_id"])
				movies[row["movie_id"]]["stars"].add(row["person_id"])
			except KeyError:
				pass


def main():
	if len(sys.argv) > 2:
		sys.exit("Usage: python degrees.py [directory]")
	directory = sys.argv[1] if len(sys.argv) == 2 else "large"

	# Load data from files into memory
	print("Loading data...")
	load_data(directory)
	print("Data loaded.")

	source = person_id_for_name(input("Name: "))
	if source is None:
		sys.exit("Person not found.")
	target = person_id_for_name(input("Name: "))
	if target is None:
		sys.exit("Person not found.")

	path = shortest_path(source, target)

	if path is None:
		print("Not connected.")
	else:
		degrees = len(path)
		print(f"{degrees} degrees of separation.")
		path = [(None, source)] + path
		for i in range(degrees):
			person1 = people[path[i][1]]["name"]
			person2 = people[path[i + 1][1]]["name"]
			movie = movies[path[i + 1][0]]["title"]
			print(f"{i + 1}: {person1} and {person2} starred in {movie}")


def shortest_path(source, target):
	"""
	Returns the shortest list of (movie_id, person_id) pairs
	that connect the source to the target.

	If no possible path, returns None.
	"""
	frontier = QueueFrontier()
	explored = StackFrontier()
	frontier.add(Node(source, None, None))
	results = [[]]
	
	while frontier:
		currentNode = frontier.remove()
		
		if currentNode.state == target:
			tmp = currentNode
			print("Found %s" %(people[tmp.state]))
			print("Found!")
			cost = 0
			r = []
			r.append(tmp)
			index = len(results)
			
			while tmp:
				if not tmp.parent:
					r.append(tmp.parent)
					print("Parent found! %s" %(people[tmp.state]))
					
				tmp = tmp.parent
				cost+=1
			results.append(r)
			print("Estimated Cost %d" %(cost))
			#break
		
		if not explored.contains_state(currentNode.state):
			# currentNode.state has person ID.
			neighbors = neighbors_for_person(currentNode.state)
			explored.add(currentNode)
			
			for i in neighbors:
				frontier.add(Node(i[1], currentNode, None))
			
	return results

def person_id_for_name(name):
	"""
	Returns the IMDB id for a person's name,
	resolving ambiguities as needed.
	"""
	person_ids = list(names.get(name.lower(), set()))
	if len(person_ids) == 0:
		return None
	elif len(person_ids) > 1:
		print(f"Which '{name}'?")
		for person_id in person_ids:
			person = people[person_id]
			name = person["name"]
			birth = person["birth"]
			print(f"ID: {person_id}, Name: {name}, Birth: {birth}")
		try:
			person_id = input("Intended Person ID: ")
			if person_id in person_ids:
				return person_id
		except ValueError:
			pass
		return None
	else:
		return person_ids[0]


def neighbors_for_person(person_id):
	"""
	Returns (movie_id, person_id) pairs for people
	who starred with a given person.
	"""
	movie_ids = people[person_id]["movies"]
	neighbors = set()
	for movie_id in movie_ids:
		for person_id in movies[movie_id]["stars"]:
			neighbors.add((movie_id, person_id))
	return neighbors


if __name__ == "__main__":
	main()
