'''
Default entity collection implementation
'''
import sys
import re
 
class BaseCollection(list):
    def __init__(self, entities=None):
        list.__init__(self)
        if entities is not None:
            self.extend(entities)
 
    def extend(self, entities):
        for e in entities:
            self.append(e)
 
    def clear(self):
        del self[:]
 
    def resolve_absolute(self, key, full_path, entity=None, root=None):
        if root is None:
            root = entity._descriptor.resolve_root
        if root:
            full_path = '%s.%s' % (root, full_path)
        module_path, classname = full_path.rsplit('.', 1)
        module = sys.modules[module_path]
        res = getattr(module, classname, None)
        if res is None:
            if entity is not None:
                raise Exception("Couldn't resolve target '%s' <%s> in '%s'!"
                                % (key, full_path, entity.__name__))
            else:
                raise Exception("Couldn't resolve target '%s' <%s>!"
                                % (key, full_path))
        return res
 
    def __getattr__(self, key):
        return self.resolve(key)
 
# default entity collection
class GlobalEntityCollection(BaseCollection):
    def __init__(self, entities=None):
        # _entities is a dict of entities keyed on their name.
        self._entities = {}
        super(GlobalEntityCollection, self).__init__(entities)
 
    def append(self, entity):
        '''
        Add an entity to the collection.
        '''
        super(EntityCollection, self).append(entity)
 
        existing_entities = self._entities.setdefault(entity.__name__, [])
        existing_entities.append(entity)
 
    def resolve(self, key, entity=None):
        '''
        Resolve a key to an Entity. The optional `entity` argument is the
        "source" entity when resolving relationship targets.
        '''
        # Do we have a fully qualified entity name?
        if '.' in key:
            return self.resolve_absolute(key, key, entity)
        else:
            # Otherwise we look in the entities of this collection
            res = self._entities.get(key, None)
            if res is None:
                if entity:
                    raise Exception("Couldn't resolve target '%s' in '%s'"
                                    % (key, entity.__name__))
                else:
                    raise Exception("This collection does not contain any "
                                    "entity corresponding to the key '%s'!"
                                    % key)
            elif len(res) > 1:
                raise Exception("'%s' resolves to several entities, you should"
                                " use the full path (including the full module"
                                " name) to that entity." % key)
            else:
                return res[0]
 
    def clear(self):
        self._entities = {}
        super(GlobalEntityCollection, self).clear()
 
# backward compatible name
EntityCollection = GlobalEntityCollection
 
_leading_dots = re.compile('^([.]*).*$')
 
class RelativeEntityCollection(BaseCollection):
    # the entity=None does not make any sense with a relative entity collection
    def resolve(self, key, entity):
        '''
        Resolve a key to an Entity. The optional `entity` argument is the
        "source" entity when resolving relationship targets.
        '''
        full_path = key
 
        if '.' not in key or key.startswith('.'):
            # relative target
 
            # any leading dot is stripped and with each dot removed,
            # the entity_module is stripped of one more chunk (starting with
            # the last one).
            num_dots = _leading_dots.match(full_path).end(1)
            full_path = full_path[num_dots:]
            chunks = entity.__module__.split('.')
            chunkstokeep = len(chunks) - num_dots
            if chunkstokeep < 0:
                raise Exception("Couldn't resolve relative target "
                    "'%s' relative to '%s'" % (key, entity.__module__))
            entity_module = '.'.join(chunks[:chunkstokeep])
 
            if entity_module and entity_module is not '__main__':
                full_path = '%s.%s' % (entity_module, full_path)
 
            root = ''
        else:
            root = None
        return self.resolve_absolute(key, full_path, entity, root=root)
 
    def __getattr__(self, key):
        raise NotImplementedError