from .utils.item_collection import ItemCollection


class ItemClsManager(ItemCollection):
    """ This class manages all known item classes
    (see :py:class:`~.item.Item` base class). Normally only one instance of
    this class is used.
    
    :param autogenerate: If `True` then getting a missing item by model class
        will generate a new item class with all parameters auto configured.
    """
    
    def __init__(self, autogenerate=False):
        super().__init__(collection_id=None)
        self.autogenerate = autogenerate

    
    def get_by_path(self, path, relative_to=None):
        """ Gets item classes using `path` (module path and class name separated
        by dot).
        
        :param path: Item path. Multiple initial dots allowed, e.g.
            `'...some_module.SomeClass'`.
        :param relative_to: In case `path` argument is relative, this arguments
            contains a path it is relative to.
        :return: List of item classes.
        """
        
        result = []
        # if path is relative or just a class name
        if path.startswith('.') or '.' not in path:
            
            if path.startswith('.'):
                if relative_to is None:
                    raise Exception('Relative path not allowed without setting '
                                    '`relative_to` argument.')
                stripped_path = path.lstrip('.')
                dot_count = len(path) - len(stripped_path)
                if dot_count > 1:
                    path_parts = relative_to.split('.')[:-(dot_count-1)]
                    relative_to = '.'.join(path_parts)
            else:
                stripped_path = path
            
            if relative_to:
                path = '{}.{}'.format(relative_to, stripped_path)      
        
        search_by_name = '.' not in path
        for item_cls in self.collection:
            # by name
            if search_by_name:
                if item_cls.__name__ == path:
                    result.append(item_cls)
                    continue
            
            # by path
            item_path = '{}.{}'.format(item_cls.__module__,
                                       item_cls.__name__)
            if item_path == path:
                result.append(item_cls)
                
        return result
    
    
    def __autogenerate_item_cls(self, model_cls):
        from .item import Item  # circular reference
        
        return Item._Auto(model_cls, metadata={
                    'autogenerated_item_cls': True,
                })
    
    
    def get_by_model_cls(self, model_cls, collection_id=None):
        """ Gets item classes using ORM model class.
        
        :param model_cls: An ORM model class that an item is using to persist
            data.
        :param collection_id: Scope ID of the item class.
        :return: List of item classes.
        """
        from .scope import Scope
        
        result = []
        if collection_id:
            scope = Scope.get_collection_by_id(collection_id)
        else:
            scope = None
        
        for item_cls in self.collection:
            if item_cls.model_cls is model_cls:
                if scope:
                    item_cls = scope[item_cls]
                result.append(item_cls)
        
        if not result and self.autogenerate:
            item_cls = self.__autogenerate_item_cls(model_cls)
            if scope:
                item_cls = scope[item_cls]
            result.append(item_cls)
            
        return result
    
    
    def autocomplete_item_classes(self):
        """ If `self.autogenerate` is `True` then auto generates missing item
        classes for models classes that are needed for already registered item
        classes via relations.
        """
        if not self.autogenerate:
            return
        
        # generating all missing items
        while True:
            regestry_copy = self.collection.copy()
            for item_cls in regestry_copy:
                item_cls.complete_setup()
                for relation in item_cls.relations.values():
                    relation['item_cls'].complete_setup()
            if regestry_copy == self.collection:
                break
        
    def __repr__(self):
        return '<{}()>'.format(self.__class__.__name__)


#: Normally only this instance of :py:class:`~ItemClsManager` is used.
item_cls_manager = ItemClsManager()
