''' Some utility functions that do document traversal '''
from biothings.utils.common import is_seq

# doc labelled in breadth first way with letters
test_doc = {
    'a': {
        'b': {
            'e': {
                'i': 'i value',
                'j': 'j value'
            },
            'f': 'f value'
        },
        'c': 'c value',
        'd': {
            'g': {
                'k': 'k value',
                'l': 'l value'
            },
            'h': 'h value'
        }
    }
}

class QueueEmptyError(Exception):
    pass


class StackEmptyError(Exception):
    pass


class Stack(object):
    def __init__(self):
        self.stack = []

    def push(self, obj):
        ''' put obj on stack '''
        self.stack.insert(0, obj)

    def pop(self):
        try:
            return self.stack.pop(0)
        except IndexError:
            raise StackEmptyError("Can't pop object from an empty stack")

    def isempty(self):
        return len(self.stack) == 0


class Queue(object):
    def __init__(self):
        self.queue = []

    def push(self, obj):
        ''' put obj on queue '''
        self.queue.append(obj)

    def pop(self):
        ''' get next obj from queue '''
        try:
            return self.queue.pop(0)
        except IndexError:
            # empty queue
            raise QueueEmptyError("Can't pop object from an empty queue")

    def isempty(self):
        return len(self.queue) == 0

def breadth_first_traversal(doc):
    ''' Yield a 2 element tuple for every k, v pair in document items (nodes are visited in breadth first order
        k is itself a tuple of keys annotating the path for this node (v) to root
        v is the node value
    '''
    return _generic_traversal(doc, Queue)

def depth_first_traversal(doc):
    ''' Yield a 2 element tuple for every k, v pair in document items (nodes are visited in depth first order
        k is itself a tuple of keys annotating the path for this node (v) to root
        v is the node value
    '''
    return _generic_traversal(doc, Stack)

def _generic_traversal(doc, structure):
    _struct = structure()

    # push first level
    for (k, v) in doc.items():
        # _struct.push((tuple([k]), v))   # TODO: remove this line
        _struct.push(((k,), v))

    while not _struct.isempty():
        _next = _struct.pop()
        yield _next
        if isinstance(_next[1], dict):
            # push this level
            for (k, v) in _next[1].items():
                _struct.push((tuple(list(_next[0]) + [k]), v))
        elif is_seq(_next[1]):
            # push all elements in a list/tuple
            for o in _next[1]:
                _struct.push((_next[0], o))

def breadth_first_recursive_traversal(doc, path=None):
    ''' doesn't exactly implement breadth first ordering it seems, not sure why... '''
    #TODO fix this...
    path = path or []
    if isinstance(doc, dict):
        for (k, v) in doc.items():
            yield (tuple(list(path) + [k]), v)
        for (k, v) in doc.items():
            yield from breadth_first_recursive_traversal(v, tuple(list(path) + [k]))
    elif is_seq(doc):
        for o in doc:
            yield (tuple(list(path)), o)
        for o in doc:
            yield from breadth_first_recursive_traversal(o, tuple(list(path)))

def depth_first_recursive_traversal(doc, path=None):
    path = path or []
    if isinstance(doc, dict):
        for (k, v) in doc.items():
            _path = tuple(list(path) + [k])
            yield (_path, v)
            yield from depth_first_recursive_traversal(v, _path)
    elif is_seq(doc):
        for o in doc:
            _path = tuple(list(path))
            yield (_path, o)
            yield from depth_first_recursive_traversal(o, _path)
