from __future__ import absolute_import
from __future__ import with_statement

from sqlalchemy import *
import sqlalchemy
from sqlalchemy.types import UnicodeText
from sqlalchemy.exceptions import UnboundExecutionError
from sqlalchemy.orm import synonym, aliased

# Why is this here, not in db? because it's easier to resolve dependencies
# this way
# From Turbogears, originally
from sqlalchemy.orm import create_session as orm_create_session

TXT=u"txt"

_engine = None
def get_engine():
    """Retrieve the engine based on the current configuration."""
    global _engine
    if not _engine:
        args = dict(custom.SQLALCHEMY_ARGS)
        dburi = args['dburi']
        assert dburi is not None,args
        del args['dburi']
        _engine = sqlalchemy.create_engine(dburi, **args)
    if not metadata.is_bound():
        metadata.bind = _engine
        return _engine

def create_session():
    """Create a session that uses the engine from thread-local metadata."""
    if not metadata.is_bound():
        get_engine()
    return orm_create_session()

metadata = sqlalchemy.MetaData()
from sqlalchemy.orm import scoped_session
session = scoped_session(create_session)

import elixir
elixir.metadata, elixir.session = metadata, session
from elixir import (ManyToOne, Entity, Field, OneToMany,
                    using_options, ManyToMany, setup_all)

from . import NoResultFound, MultipleResultsFound
from .flavors import FLAVORS
from . import custom, db

from datetime import datetime

def bazname(ent):
    return 'baz_'+ent.__name__.lower()

class SynManyToOne(ManyToOne):
    def __init__(self,of_kind,synonym,*args,**kw):
        ManyToOne.__init__(self,of_kind,*args,**kw)
        self.synonym = synonym
    def create_properties(self):
        ManyToOne.create_properties(self)
        self.add_mapper_property(self.synonym,synonym(self.name))
        
class Element(Entity):
    _ename = Field(Unicode(128), unique=True, required=True, index=True,
                   colname='ename', synonym='ename')
    # http://www.sitepoint.com/article/hierarchical-data-database/2/
    treeleft = Field(Integer,required=True,index=True)
    treeright = Field(Integer,required=True,index=True)
    #_supertype = SynManyToOne('Element', colname='supertype_id',
    #                          synonym='supertype')
    #subtypes = OneToMany('Element')
    propvals = OneToMany('Propval')
    # can be "normal", "list", or "toplevel"
    _orgmode = Field(Unicode(32),required=True,default=u'normal',
                     colname='orgmode',synonym='orgmode')

    using_options(order_by='treeleft',
                  tablename=bazname)
    
    @staticmethod
    def get(*args,**kw):
        if len(args) != 1 and len(kw) <= 0 or len(args) == 1 and len(kw) > 0:
                raise TypeError("Element.get() takes 1 element name or a bunch of kwargs!")

        if len(args) > 0:
            assert len(args) == 1
            ret = Element.get_by(ename=args[0])
            if ret is None:
                raise NoResultFound(args[0])
            return ret
        else:
            return Element.__search(**kw).one()

    def __init__(self, ename, parent=False, treeleft=0,treeright=0,
                 fromhook=False):
        assert '.' not in ename
        assert ' ' not in ename
        assert ':' not in ename
        assert '/' not in ename
        if fromhook:
            assert treeleft != 0
            assert treeright != 0
        else:
            assert treeleft == 0
            assert parent is not False
        Entity.__init__(self,_ename=ename,treeleft=treeleft,
                        treeright=treeright)
        session.flush()
        if not fromhook:
            # This has a side-effect that sets treeleft,treeright properly
            db.esetattr(self,'parent',parent)
        
    def __set_supertype(self,newparent):
        # Fire hooks
        db.esetattr(self, 'parent', newparent)
    def __get_supertype(self):
        if self.treeleft == 0:
            # Not done being constructed yet
            return False
        else:
            return (Element.query.filter(
                and_(Element.treeleft < self.treeleft,
                     Element.treeright > self.treeright))
                    .order_by(Element.treeright-Element.treeleft).first())
    supertype = property(__get_supertype,__set_supertype)

    def __set_orgmode(self,neworgmode):
        # Fire hooks
        db.esetattr(self, 'orgmode', neworgmode)
    def __get_orgmode(self):
        return self._orgmode
    orgmode = property(__get_orgmode,__set_orgmode)

    @staticmethod
    def getRoot():
        return Element.get_by(treeleft=1)

    def isRoot(self):
        return self.treeleft == 1

    @classmethod
    def getAll(cls):
        """Returns all elements, starting with the root element and always
        returning the parent before its children."""
        return cls.query.order_by(Element.treeleft).all()

    def __querySubtypes(self):
        midparent = aliased(Element)
        return (Element.query.filter(
                and_(Element.treeleft > self.treeleft,
                     Element.treeright < self.treeright,
                     ~exists().where(
                        and_(midparent.treeleft > self.treeleft,
                             midparent.treeright < self.treeright,
                             Element.treeleft > midparent.treeleft,
                             Element.treeright < midparent.treeright)))))
    def getSubtypes(self):
        return self.__querySubtypes().all()
    def hasChildren(self):
        return self.treeright - self.treeleft > 1
    
    def __queryDescendants(self,includeMe=False):
        if includeMe:
            a = Element.treeleft >= self.treeleft
            b = Element.treeright <= self.treeright
        else:
            a = Element.treeleft > self.treeleft
            b = Element.treeright < self.treeright
        return (Element.query.filter(and_(a,
                                          b))
                .order_by(Element.treeleft))
    def getDescendants(self,*args,**kw):
        return self.__queryDescendants(*args,**kw).all()
    def countDescendants(self):
        return (self.treeright - self.treeleft - 1) / 2
    def getLeaves(self):
        return self.__queryDescendants().filter(
            Element.treeright-Element.treeleft==1).all()
    def __queryAncestors(self):
        return (Element.query.filter(and_(Element.treeleft < self.treeleft,
                                          Element.treeright > self.treeright))
                .order_by(Element.treeleft))
    def getAncestors(self,*args,**kw):
        return self.__queryAncestors(*args,**kw).all()

    def childTree(self):
        """Return a child tree structure.  Since there's only a single root,
        just returns that root, not a list of roots."""

        return Element.asTree(self.__queryDescendants(includeMe=True))[0]

    def queryRelations(*elements):
        """Return a query for all elements either an ancestor or a descendent
        of any of the given elements.  Includes the given elements.
        Can be called like e.queryRelations()
        or Element.queryRelations(e1,e2,e3)."""

        return Element.query.filter(
            or_(or_(and_(Element.treeleft < e.treeleft,
                         Element.treeright > e.treeright)
                    for e in elements),
                or_(and_(Element.treeleft >= e.treeleft,
                         Element.treeright <= e.treeright)
                    for e in elements))).order_by(Element.treeleft)

    @staticmethod
    def asTree(query):
        """Formats query as a tree.

        The return value is a list of roots.  A node is either a leaf
        or a subtree.
        A leaf is [element].  A subtree is [element, [<>], [<>]]."""
        desc = query.order_by(Element.treeleft).all()
        ret = []
        stack = [ret]
        while len(desc) > 0:
            e = desc.pop(0)
            while len(stack) > 1 and stack[-1][0].treeright < e.treeleft:
                stack.pop()
            bubble = [e]
            stack[-1].append(bubble)
            stack.append(bubble)
        return ret
    
    def __set_ename(self,newename):
        db.esetattr(self,'ename',newename)
    def __get_ename(self):
        return self._ename
    ename = property(__get_ename,__set_ename)

    def __repr__(self):
        return "<Element: %s>"%self.ename.encode('utf-8')

    def __contains__(self, key):
        try:
            self[key]
        except KeyError:
            return False
        else:
            return True

    def __getitem__(self, key):
        #import pdb
        #pdb.set_trace()
        if not isinstance(key,Prop):
            prop = Prop.get_by(name=key)
            if prop is None:
                raise KeyError("There's no prop %s anywhere!"%key)
        else:
            prop = key
        try:
            pv = Propval.query.filter(
                and_(Propval.prop==prop,
                     Propval.element==self)).one()
            return pv
                
        except NoResultFound:
            raise KeyError("Element %s does not have prop %s!" % (self.ename,
                                                                  prop.name))
    def __setitem__(self, key, value):
        if not isinstance(key,Prop):
            prop = Prop.set_or_create(name=key)
            if not prop:
                raise KeyError("Prop '%s' is not defined!"%key)
        else:
            prop = key
        # -? typecheck?
        Propval.set_or_create(element=self,prop=prop,value=value)
    def __delitem__(self, key):
        prop = Prop.get_by(name=key)
        if not prop:
            raise KeyError("Prop '%s' is not defined!"%key)
        pv = Propval.get_by(prop=prop,element=self)
        if pv is None:
            raise KeyError("Element %s has no value for %s!"
                           % (self.ename, key))
        if key not in self.supertype:
            pv.delete()
        else:
            pv.value = prop.default
    def __iter__(self):
        for pv in (Propval.query.filter(Propval.element==self).all()):
            yield pv.prop.name

    @classmethod
    def __search(cls,**kw):
        """elm.search(category='mon',name='sakura') returns the
        a query for elements with those propvals, using inheritance and such."""
        q = cls.query
        for pname in kw:
            val = kw[pname]
            q = q.filter(cls.propvals.any(
                and_(Propval.prop==Prop.get_by(name=pname),
                     Propval.cache_entries.any(
                and_(CacheEntry.format==TXT, CacheEntry.value==val)))))
        return q
    @classmethod
    def search(cls,**kw):
        return cls.__search(**kw).all()

    def delete(self):
        db.edelete(self)

# Valid flavors are currently u'string',u'text',u'integer',u'references',
# or u'boolean'
# but are basically defined in custom
# All props with the same name have the same flavor, even if "unrelated"

class Prop(Entity):
    _name = Field(Unicode(128),unique=True,required=True,index=True,
                  colname='name', synonym='name')
    _flavor = Field(Unicode(128),colname='flavor',synonym='flavor')
    _default = Field(Binary,required=True,
                     colname='default',synonym='default')
    propvals = OneToMany('Propval')

    using_options(tablename=bazname)
    
    @staticmethod
    def get(pname):
        ret = Prop.get_by(name=pname)
        if ret is None:
            raise NoResultFound
        return ret

    def __init__(self, name, flavor=None, default="<<parent.this />>",
                 fromhook=False):
        assert ' ' not in name
        assert '.' not in name
        assert ':' not in name
        assert '/' not in name
        assert '=' not in name
        assert '@' not in name
        if flavor is None:
            flavor = custom.DEFAULT_FLAVOR
        Entity.__init__(self,_name=name,_flavor=flavor,
                        _default=default)
        session.flush()
        if not fromhook:
            db.psetattr(self,'flavor',flavor)
            db.psetattr(self,'default',default)

    def __set_name(self,newname):
        db.psetattr(self,'pname',newname)
    def __get_name(self):
        return self._name
    name = property(__get_name,__set_name)

    def __set_flavor(self,newflavor):
        db.psetattr(self,'flavor',newflavor)
    def __get_flavor(self):
        return self._flavor
    flavor = property(__get_flavor,__set_flavor)

    def __set_default(self,newdefault):
        db.psetattr(self,'default',newdefault)
    def __get_default(self):
        return self._default
    default = property(__get_default,__set_default)

    def delete(self):
        db.pdelete(self)

    @classmethod
    def getAll(cls):
        return cls.query.all()

    def elementTree(self):
        return Element.asTree(Element.query.join(Element.propvals)
                              .filter(Propval.prop == self))


    @classmethod
    def set_or_create(cls,name,flavor=None):
        ret = cls.get_by(name=name)
        if ret is None:
            if flavor is None:
                flavor = custom.DEFAULT_FLAVOR
            ret = cls(name=name,
                      flavor=flavor)
        elif flavor is not None:
            ret.flavor = flavor
        session.flush()
        return ret

class Propval(Entity):
    prop = ManyToOne('Prop',required=True)
    element = ManyToOne('Element',required=True)
    _value = Field(Binary,required=True,colname='value',synonym='value')
    cache_entries = OneToMany('CacheEntry')

    using_options(tablename=bazname)

    def __init__(self,element,prop,value=None,fromhook=False):
        assert element is not None,kw
        if value is None:
            value = prop.default
        if isinstance(value,unicode):
            value = value.encode('utf-8')
        Entity.__init__(self,element=element,
                        prop=prop,_value=value)
        session.flush()
        if not fromhook:
            db.setprop(element,prop.name,
                       buffer(value) if FLAVORS[prop.flavor].binary
                       else unicode(value,'utf-8'))
    
    def __set_value(self,newval):
        if isinstance(newval,unicode):
            newval = newval.encode('utf-8')
        db.setprop(self.element,self.prop.name,
                   buffer(newval) if FLAVORS[self.prop.flavor].binary
                   else unicode(newval,'utf-8'))
        assert self.value == newval
    def __get_value(self):
        return self._value
    value = property(__get_value,__set_value)

    @staticmethod
    def set_or_create(element, prop, value):
        if isinstance(value,unicode):
            value = value.encode('utf-8')
        assert element is not None
        db.setprop(element,prop.name,
                   buffer(value) if FLAVORS[prop.flavor].binary
                   else unicode(value,'utf-8'))

    def __repr__(self):
        try:
            return "<%s[%s]>" % (self.element.ename.encode('utf-8'),
                                      self.prop.name.encode('utf-8'))
        except UnboundExecutionError:
            return "<Unbound Propval>"

    def delete(self):
        db.delete(self.element,self.prop.name)

    def __unicode__(self):
        return unicode(self.render())
    
    def render_any(self,*formats):
        for f in formats:
            ce = CacheEntry.get_by(propval=self,format=f)
            if ce:
                # What's the right logic here?
                try:
                    return unicode(ce.value,'utf-8'),f
                except UnicodeDecodeError:
                    return ce.value,f
        else:
            from . import conversion, wiki
            im = conversion.convert_any(self,*formats)
            value = im.asData()
            ext = im.getExtension()
            deps = im.getDeps()
            if not FLAVORS[self.prop.flavor].binary:
                if ext.startswith('.'):
                    assert isinstance(value,str), [repr(value),
                                                   self.prop.name,
                                                   formats]
                else:
                    assert isinstance(value,unicode), [repr(value),
                                                       self.prop.name,
                                                       formats]
            # Do dependencies
            if wiki.DISCORDIA not in deps:
                if isinstance(value,unicode):
                    svalue = value.encode('utf-8')
                else:
                    svalue = value
                ce = CacheEntry(propval=self,format=ext,value=svalue)
                for d in deps:
                    dep = Dependency.get_or_create(*d)
                    if dep not in ce.dependencies:
                        ce.dependencies.append(dep)
                session.flush()
            return value,ext
    def render(self,format=TXT):
        value,ext = self.render_any(format)
        return value

class Dependency(Entity):
    ename = Field(Unicode(128),required=True)
    pname = Field(Unicode(128),required=True)
    dependents = ManyToMany('CacheEntry',tablename='baz_depmap')

    using_options(tablename=bazname)

    @classmethod
    def get_or_create(cls,ename,pname):
        ename=unicode(ename)
        pname=unicode(pname)
        assert ename is not None
        assert pname is not None
        r = cls.get_by(ename=ename,pname=pname)
        if r is None:
            r = cls(ename=ename,pname=pname)
            session.flush()
        return r

class CacheEntry(Entity):
    """A cache entry caches the evaluated value of a Propval for a given
    format.  It gets invalidated when any Propval it depends on changes."""
    propval = ManyToOne('Propval',required=True)
    format = Field(Unicode(10),required=True,default=TXT)
    value = Field(Binary,required=True)
    dependencies = ManyToMany('Dependency',tablename='baz_depmap')

    using_options(tablename=bazname)

    def __init__(self,**kw):
        assert 'value' in kw
        Entity.__init__(self,**kw)

    def delete(self):
        self.propval.cache_entries.remove(self)
        for d in list(self.dependencies):
            d.dependents.remove(self)
        Entity.delete(self)

    def invalidate(self):
        pv = self.propval
        self.delete()
        if self.format == TXT and FLAVORS[pv.prop.flavor].indexed:
            # Indexed properties need to autoregen
            pv.render()

setup_all()

# Indexes -- sort of a hack
Index('PropvalInd',Propval._descriptor.get_column('prop_id'),
      Propval._descriptor.get_column('element_id'),
      unique=True)
#Index('PropvalHistoryInd',PropvalHistory._descriptor.get_column('propval_id'),
#      PropvalHistory._descriptor.get_column('revision'),
#      unique=True)
Index('DependencyInd',Dependency._descriptor.get_column('ename'),
      Dependency._descriptor.get_column('pname'),unique=True)
Index('CacheEntryInd',CacheEntry._descriptor.get_column('propval_id'),
      CacheEntry._descriptor.get_column('format'),unique=True)

class hook(object):
    def begin(self):
        session.begin()
    def commit(self):
        session.commit()
        session.close()
    def abort(self):
        session.rollback()
        session.close()

    def esetattr(self,e,attr,val):
        assert attr in ('parent','ename','orgmode')
        if attr == 'ename':
            assert e is not None
            oldename = e.ename
            e._ename = val
            for ce in list(
                CacheEntry.query.join(CacheEntry.dependencies).filter(
                or_(Dependency.ename == oldename,
                    Dependency.ename == val)).all()):
                ce.invalidate()
        elif attr == 'orgmode':
            e._orgmode = val
        elif attr == 'parent':
            parent = val
            if e is None or e.treeleft == 0:
                self.oldparent = None
                self.created = True
                precount = Element.query.filter(Element.treeleft != 0).count()
                if parent is None:
                    assert Element.query.count() == 0
                    # Must be the toplevel element, which is unique
                    treeleft = 1
                    treeright = 2
                else:
                    assert Element.getRoot().treeright == precount*2,(
                        "precondition",
                        Element.getRoot().treeright,precount*2)
                    # Move old nodes for insert
                    point = parent.treeright
                    session.execute(Element.table.update(
                        Element.treeright >= point,
                        values=dict(treeright=Element.treeright+2)
                        ))
                    session.execute(Element.table.update(
                        Element.treeleft >= point,
                        values=dict(treeleft= Element.treeleft+2)
                        ))
                    treeleft = point
                    treeright = point+1
                e.treeleft = treeleft
                e.treeright = treeright
                session.flush()
                session.expire_all()

                if parent is not None:
                    assert e.supertype == parent,(repr(point),
                                                     (e.treeleft,e.treeright),
                                                     repr(e.supertype),
                                                     (e.supertype.treeleft,
                                                      e.supertype.treeright),
                                                     repr(parent),
                                                     (parent.treeleft,
                                                      parent.treeright))
                assert Element.getRoot().treeright == (precount+1)*2,(
                    Element.getRoot().treeright, (precount+1)*2)
            else:
                # Changing supertype
                self.oldparent = e.supertype
                self.created = False
                assert self.oldparent is not None
                assert parent is not None
                assert not (parent.treeleft > e.treeleft
                            and parent.treeright < e.treeright),"An element cannot be its own grandpa!"

                # Mod tree
                oldleft = e.treeleft
                oldright = e.treeright
                delta = oldright-oldleft
                #rightq = parent.treeright > self.oldparent.treeright
                rightq = parent.treeright > oldright
                if rightq:
                    newleft = parent.treeright - delta - 1
                else:
                    newleft = parent.treeright
                newright = newleft + delta
                dist = newleft-oldleft
                # We need to get the ids of us and children first, because the
                # following updates will cause confusion
                descandme = [e.id for e in Element.query.filter(
                        and_(Element.treeleft >= oldleft,
                             Element.treeright <= oldright))]
                if rightq:
                    session.execute(Element.table.update(
                        and_(Element.treeright > oldright,
                             Element.treeright <= newright),
                        values=dict(treeright=Element.treeright-delta-1)))
                    session.execute(Element.table.update(
                        and_(Element.treeleft > oldright,
                             Element.treeleft <= newright),
                        values=dict(treeleft=Element.treeleft-delta-1)))
                else:
                    session.execute(Element.table.update(
                        and_(Element.treeright >= newleft,
                             Element.treeright < oldleft),
                        values=dict(treeright=Element.treeright+delta+1)))
                    session.execute(Element.table.update(
                        and_(Element.treeleft >= newleft,
                             Element.treeleft < oldleft),
                        values=dict(treeleft=Element.treeleft+delta+1)))
                # Me and my children, so both left and right must be in the range
                session.execute(Element.table.update(
                    Element.id.in_(descandme),
                    values=dict(treeleft=Element.treeleft + dist,
                                treeright=Element.treeright + dist)
                    ))
                session.flush()
                session.expire_all()

                # Clear defaulting propvals we no longer need
                npropval = aliased(Propval)
                opropval = aliased(Propval)
                for pv in Propval.query.join(Propval.prop).filter(
                    and_(Propval.element==e,
                         Propval.value == Prop.default,
                         exists().where(
                            and_(opropval.element == self.oldparent,
                                 opropval.prop_id == Propval.prop_id)),
                         ~exists().where(
                            and_(npropval.element == parent,
                                 npropval.prop_id == Propval.prop_id)))):
                    pv.delete()
    def post_esetattr(self,e,attr,val):
        if attr == 'parent':
            if e.treeleft != 1:
                parent = e.supertype
                # Defaulting propvals
                for pv in Propval.query.filter_by(element=parent):
                    mev = Propval.get_by(prop=pv.prop,element=e)
                    if mev is None:
                        # Create defaults to supertype
                        Propval(prop=pv.prop,element=e)
                # Dependencies
                if self.oldparent:
                    oldparentness = [and_(Dependency.ename == self.oldparent.ename,
                                          Dependency.pname == u'__children')]
                else:
                    oldparentness = []
                if self.created:
                    createdness = [Dependency.ename == e.ename]
                else:
                    createdness = []
                for ce in list(CacheEntry.query.join(CacheEntry.dependencies)
                               .filter(
                    or_(and_(Dependency.ename == e.ename,
                             Dependency.pname == u'__parent'),
                        and_(Dependency.ename == parent.ename,
                             Dependency.pname == u'__children'),
                        *(oldparentness+createdness)))
                               .all()):
                    ce.invalidate()
        
    def edelete(self,e):
        assert e.treeleft != 1,"You can't delete the root element."
        assert e.treeright - e.treeleft == 1,"You can't delete an element with children."
        for pv in e.propvals:
            self.delete(e,pv.prop.name,invalidateDependencies=False)
        invalid = list(CacheEntry.query.join(CacheEntry.dependencies)
                       .filter(Dependency.ename == e.ename).all())
        oldright = e.treeright
        Entity.delete(e)
        session.flush()
        for ce in invalid:
            ce.invalidate()
        # Fill in the gap; we know that we have no children
        session.execute(Element.table.update(
            Element.treeright > oldright,
            values=dict(treeright=Element.treeright-2)))
        session.execute(Element.table.update(
            Element.treeleft > oldright,
            values=dict(treeleft=Element.treeleft-2)))

    def psetattr(self,p,attr,val):
        if p is None:
            assert False
            #assert attr != 'pname'
            #p=Prop(name=pname,fromhook=True,
            #       **{attr:val})
        else:
            if attr == 'flavor':
                oldflavor = p._flavor
                p._flavor = val
                for ce in list(CacheEntry.query.join(CacheEntry.dependencies)
                               .filter(Dependency.pname == p.name).all()):
                    ce.invalidate()
                if not FLAVORS[oldflavor].indexed and FLAVORS[val].indexed:
                    for pv in p.propvals:
                        pv.render()
            elif attr == 'default':
                olddefault = p.default
                p._default = val
                # Mod whatever was defaulting
                session.execute(Propval.table.update(
                    and_(Propval.prop == p,
                         Propval.value == olddefault),
                    values=dict(value=val)))
                session.flush()
                session.expire_all()
            elif attr == 'pname':
                oldname = p._name
                p._name = val
                for ce in list(
                    CacheEntry.query.join(CacheEntry.dependencies)
                    .filter(or_(Dependency.pname == oldname,
                                Dependency.pname == val)).all()):
                    ce.invalidate()
            else:
                raise Exception('Unknown pattr %s!'%attr)
        
    def pdelete(self,p):
        assert len(p.propvals) <= 0,"You can't delete an in-use Prop!"
        Entity.delete(p)

    def setprop(self,e,pname,val):
        assert isinstance(val,(unicode,buffer)),repr(val)
        p = Prop.get(pname)
        pv = Propval.get_by(element=e,prop=p)
        if pv is None:
            pv = Propval(element=e,prop=p,value=val,fromhook=True)
        else:
            pv._value = val.encode('utf-8') if hasattr(val,'encode') else val
            session.flush()
            for ce in list(CacheEntry.query.join(CacheEntry.dependencies)
                           .filter(or_(CacheEntry.propval == pv,
                                       and_(Dependency.ename == e.ename,
                                            Dependency.pname == pname)))
                           .all()):
                ce.invalidate()
            session.flush()
        # Whether newly created or not
        if FLAVORS[p.flavor].indexed:
            pv.render()

    def post_setprop(self,e,pname,val):
        p = Prop.get(pname)
        # -? Not really necessary if the prop wasn't just created...
        for sub in e.getSubtypes():
            kidv = Propval.get_by(prop=p,element=sub)
            if kidv is None:
                Propval(prop=p,element=sub) # Recurses
    
    def delete(self,e,pname,invalidateDependencies=True):
        p = Prop.get(pname)
        pv = Propval.get_by(element=e,prop=p)
        
        for ce in list(pv.cache_entries):
            ce.delete()
        if invalidateDependencies:
            invalid = list(CacheEntry.query.join(CacheEntry.dependencies)
                           .filter(and_(Dependency.ename == e.ename,
                                        Dependency.pname == pname,
                                        CacheEntry.propval != pv))
                           .all())
        Entity.delete(pv)
        session.flush()
        if invalidateDependencies:
            for ce in invalid:
                ce.invalidate()
            
# It needs to be the primary hook
custom.commit_hooks = [hook()]+custom.commit_hooks

__all__ = ['Element','Prop','Propval']
