import os, sys

from . import format
from bazbase import structure, custom

root = None

path_for_element = {}
parent_for_element = {}
children_for_element = {}
undug_elements = []

class parent_handler(object):
    def start_element(self,e,parent=None):
        pass
        
    def end_element(self, e):
        pass
        
    def prop(self, e, p, contents):
        if p == u'parent':
            parent_for_element[e] = contents
        else:
            pass

def dig_element_helper(nexte):
    nextpath = os.path.dirname(path_for_element[nexte])
    # Could already be there because of toplevels
    if nexte not in children_for_element:
        children_for_element[nexte] = []
    for d in os.listdir(os.path.join(root, nextpath)):
        if d.endswith('.yaml') and d != '%s.yaml' % nexte:
            kid = d[:-len('.yaml')]
            path_for_element[kid] = '%s/%s.yaml' % (nextpath, kid)
            parent_for_element[kid] = nexte
            children_for_element[kid] = ()
            children_for_element[nexte].append(kid)
        elif os.path.exists(os.path.join(root, nextpath, d, '%s.yaml' % d)):
            path_for_element[d] = '%s/%s/%s.yaml' % (nextpath, d, d)
            parent_for_element[d] = nexte
            children_for_element[nexte].append(d)
            undug_elements.append(d)

def dig_until_element(ename):
    if not path_for_element:
        for d in os.listdir(root):
            path = '%s/%s.yaml' % (d, d)
            if os.path.exists(os.path.join(root, path)):
                path_for_element[d] = path
                if d == 'Object':
                    parent_for_element[d] = None
                else:
                    format.parse(os.path.join(root, path), parent_handler())
                undug_elements.append(d)
    while undug_elements and (ename is None or ename not in path_for_element):
        nexte = undug_elements.pop(0)
        dig_element_helper(nexte)

def dig_element(ename):
    assert path_for_element
    if ename in undug_elements:
        undug_elements.remove(ename)
        dig_element_helper(ename)
    else:
        assert ename in path_for_element
        assert ename in children_for_element, ename

def dig_completely():
    dig_until_element(None)

class value_map_handler(object):
    def __init__(self, od, elm):
        self.elm = elm
        self.od = od

    def start_element(self,e,parent=None):
        pass
        
    def end_element(self, e):
        pass
        
    def prop(self, e, p, contents):
        if p == u'parent':
            pass
        else:
            if isinstance(contents, format.Include):
                fmt = contents.ext
                contents = contents.val
            else:
                contents = contents.encode('utf-8')
                fmt = 'creole'
            self.od[p] = structure.Propval(self.elm, p, contents, fmt)
    def get_include(self, path):
        with file(os.path.join(root, os.path.dirname(self.elm._path), 
                               path)) as fil:
            return fil.read()

class YamlElement(structure.Element):
    __slots__ = ('_path',)
    
    def __init__(self, path):
        assert path.endswith('.yaml'), path
        assert not path.startswith('/')
        ename = unicode(os.path.basename(path[:-len('.yaml')]), 'utf-8')
        structure.Element.__init__(self, ename)
        self._path = path

    #def set_prop(self, propname, value):
    #    self._e[propname] = value

    def get_descendants(self):
        ret = []
        kids = get_children(self.ename, self)
        for k in kids:
            ret.append(k)
            ret += k.get_descendants()
        return ret
    def is_ancestor_of(self, other):
        return self in other.get_ancestors()

    def get_ancestors(self):
        ret = []
        e = self.get_parent()
        while e is not None:
            ret.insert(0, e)
            e = e.get_parent()
        return ret

    #def create_child(self, ename):
    #    return YamlElement(model.Element(ename, self._e))

    def _get_value_map_impl(self, od):
        format.parse(os.path.join(root, self._path), 
                     value_map_handler(od, self))
        parent = self.get_parent()
        if parent is not None:
            vm = parent.get_value_map()
            for pname in vm:
                if pname not in od:
                    od[pname] = structure.Propval(
                        self, pname, structure.get_prop(pname).default, 
                        'creole')

    #def set_parent(self, parent):
    #    self._e.parent = parent._e

def get_root_element():
    return YamlElement('Object/Object.yaml')

#def create_root_element(ename):
#    return YamlElement(model.Element(ename, parent=None))

class prop_handler(object):
    def __init__(self, attrs):
        self.attrs = attrs

    def start_element(self,e,parent=None):
        pass
        
    def end_element(self, e):
        pass
        
    def prop(self, p, a, contents):
        self.attrs[a] = format.interpret_as_prop(a, contents)

class YamlProp(structure.Prop):
    __slots__ = ()

    def __init__(self, pname):
        attrs = {}
        format.parse(os.path.join(root, 'props/%s.yaml' % pname),
                     prop_handler(attrs))
        # TODO(xavid): centralize this defaulting with model
        if u'flavor' not in attrs:
            attrs[u'flavor'] = custom.DEFAULT_FLAVOR
        if u'default' not in attrs:
            attrs[u'default'] = structure.PARENT_DOT_THIS
        if u'visible' not in attrs:
            attrs[u'visible'] = False
        if u'comment' not in attrs:
            attrs[u'comment'] = u''
        structure.Prop.__init__(self, pname, attrs[u'flavor'], 
                                attrs[u'default'], 
                                attrs[u'visible'], attrs[u'comment'])

    #def set_flavor(self, flavor):
    #    self._p.flavor = flavor
    #    self._flavor = flavor

    #def set_default(self, default):
    #    self._p.default = default
    #    self._default = default

    #def set_visible(self, visible):
    #    self._p.visible = visible
    #    self._visibile = visible

    #def set_comment(self, comment):
    #    self._p.comment = comment
    #    self._comment = comment

    # Should be ordered such that each element is followed by a single
    # region consisting of any included descendants.  Because of prop
    # inheritance, there shouldn't be holes.
    #def containing_elements(self):
    #    return [YamlElement(e)
    #            for e in model.Element.query.join(model.Element.propvals)
    #            .filter(model.Propval.prop_id == self._p.id)
    #            .order_by(model.Element.treeleft).all()]

def list_all_props():
    return [YamlProp(p[:-len('.yaml')]) 
            for p in os.listdir(os.path.join(root, 'props'))
            if p.endswith('yaml')]

def list_all_elements():
    dig_completely()
    return [YamlElement(path_for_element[ename]) 
            for ename in path_for_element]

def get_element(ename):
    dig_until_element(ename)

    if ename in path_for_element:
        return YamlElement(path_for_element[ename])
    else:
        return None

def get_prop(pname):
    if os.path.exists('props/%s.yaml' % pname):
        return YamlProp(pname)
    else:
        return None

#def create_prop(pname, flavor=None):
#    return YamlProp(model.Prop(pname, flavor))

def get_parent(ename, e):
    dig_until_element(ename)
    p = parent_for_element[ename]
    if p is None:
        # Object
        return None
    else:
        assert p != ename, (p, ename)
        dig_until_element(p)
        assert p in path_for_element, (p, path_for_element)
        return YamlElement(path_for_element[p])

def get_children(ename, e):
    dig_until_element(ename)
    dig_element(ename)
    return [YamlElement(path_for_element[d]) 
            for d in children_for_element[ename]]

# Overall operations
#def clear_database():
#    model.clear()

def flush_database_for_test():
    pass

#def verify_tree_for_test():
#    model.verifyTree()
