from basicgraph import *

#####
#
# classic Breadth First Search
#
# See, e.g., http://en.wikipedia.org/wiki/Breadth-first_search
#

def bfs(graph, startNode):
    for n in graph.nodes:
        n.setStatus('unseen')
    startNode.setStatus('seen')
    #print("Initializing queue with: ", startNode.name)
    queue = Queue()
    queue.enqueue(startNode)
    while queue.size() != 0:
        #print(queue)
        currentNode = queue.dequeue()
        #print("removed {} from queue. Processing neighbors".format(currentNode.name))
        for node in graph.neighborsOf(currentNode):
            if node.getStatus() == 'unseen':
                node.setStatus('seen')
                queue.enqueue(node)
        currentNode.setStatus('processed')

def genStatesGraph():
    global g
    inStream = open('states.txt', 'r')
    adjListsFromFile = []
    for line in inStream:
        lineAsList = line.split(',')
        lineAsList = [abbrev.strip() for abbrev in lineAsList]
        adjListsFromFile.append(lineAsList)
    g = Graph()
    for line in adjListsFromFile:
        n = Node(line[0])
        g.addNode(n)
    for line in adjListsFromFile:
        node1 = g.getNode(line[0])
        for neighbor in line[1:]:
            node2 = g.getNode(neighbor)
            #print(node1, node2)
            if not g.hasEdge(node1, node2):
                g.addEdge(node1, node2)
    return g

# Both arguments must be two-letter state abbreviations.
#
# NOTE: if you run this before modifying Node and bfs to add/use a distance property, this
# will crash.
#
# E.g. testDS9x('IA', 'TX') will
# print "To get from IA to TX you must travel through at least 3 states."
#
def testDS9x(startState, endState):
    statesGraph = genStatesGraph()
    startNode = statesGraph.getNode(startState)
    endNode = statesGraph.getNode(endState)
    bfs(statesGraph, startNode)
    print("To get from {} to {} you must travel through at least {} states.".format(startState, endState, endNode.getDistance()))


#####

class Queue:                                                                                               
                                                                                                           
    # We represent an empty queue using an empty list                                                      
    def __init__(self):                                                                                    
        self.q = []                                                                                        
                                                                                                           
    # Return True if there are no items in the queue.  Return False otherwise.                             
    def isEmpty(self):                                                                                     
        return (len(self.q) == 0)                                                                                                 
                                                                                                                                                                                                                     
    # Add an item to the rear of the queue.                                                                
    def enqueue (self, item):                                                                              
        self.q.append(item)                                                                                                  
                                                                                                                                                                                                                     
    # If the queue is not empty, remove and return the queue's front item                                  
    # If the queue is empty, generate an error or print(a message and return None.                         
    def dequeue (self):
        if self.isEmpty():
            raise ValueError("dequeue invoked on empty Queue")
        else:
            result = self.q[0]
            # Note that this is O(n). Okay for use in CS1210 BUT use
            # a better implementation if you will do many dequeues on
            # very big queue! (Python's collections.deque provides a good
            # implementation)
            self.q = self.q[1:]
            return result
                                                                                                           
    # Return the number of items currently in the queue                                                    
    def size(self):                                                                                        
        return(len(self.q))

    def __repr__(self):
        return "queue{front: " + str(self.q)[1:-1] + " :end}" 
