
from pathlib import Path
import uuid
import bpy


class CatalogItem:
    """Represent a single item of a catalog"""
    def __init__(self, catalog, path=None, name=None, id=None):

        self.catalog = catalog

        self.path = path
        self.name = name
        self.id = str(id or uuid.uuid4())

        if isinstance(self.path, Path):
            self.path = self.path.as_posix()
        
        if self.path and not self.name:
            self.name = self.norm_name(self.path)

    def parts(self):
        return Path(self.name).parts

    def norm_name(self, name):
        """Get a norm name from a catalog_path entry"""
        return name.replace('/', '-')

    def __repr__(self):
        return f'CatalogItem(name={self.name}, path={self.path}, id={self.id})'


class CatalogContext:
    """Utility class to get catalog relative to the current context asset browser area"""

    @staticmethod
    def poll():
        return asset_utils.SpaceAssetInfo.is_asset_browser(bpy.context.space_data)

    @property
    def id(self):
        if not self.poll():
            return

        return bpy.context.space_data.params.catalog_id

    @property
    def item(self):
        if not self.poll():
            return

        return self.get(id=self.active_id)

    @property
    def path(self):
        if not self.poll():
            return

        if self.active_item:
            return self.active_item.path

        return ''


class Catalog:
    """Represent the catalog of the blender asset browser library"""
    def __init__(self, directory=None):

        self.directory = None
        self._data = {}

        if directory:
            self.directory = Path(directory)
        
        self.context = CatalogContext()
    
    @property
    def filepath(self):
        """Get the filepath of the catalog text file relative to the directory"""
        if self.directory:
            return self.directory /'blender_assets.cats.txt'

    def read(self):
        """Read the catalog file of the library target directory or of the specified directory"""

        if not self.filepath or not self.filepath.exists():
            return {}

        self._data.clear()
        
        print(f'Read catalog from {self.filepath}')
        for line in self.filepath.read_text(encoding="utf-8").split('\n'):
            if line.startswith(('VERSION', '#')) or not line:
                continue

            cat_id, cat_path, cat_name = line.split(':')
            self._data[cat_id] = CatalogItem(self, name=cat_name, id=cat_id, path=cat_path)
        
        return self

    def write(self, sort=True):
        """Write the catalog file in the library target directory or of the specified directory"""
        
        if not self.filepath:
            raise Exception(f'Cannot write catalog {self} no filepath setted')

        lines = ['VERSION 1', '']

        catalog_items = list(self)
        if sort:
            catalog_items.sort(key=lambda x : x.path)

        for catalog_item in catalog_items:
            lines.append(f"{catalog_item.id}:{catalog_item.path}:{catalog_item.name}")
        
        print(f'Write Catalog at: {self.filepath}')
        self.filepath.write_text('\n'.join(lines), encoding="utf-8")

    def get(self, path=None, id=None, fallback=None):
        """Found a catalog item by is path or id"""
        if isinstance(path, Path):
            path = path.as_posix()
        
        if id:
            return self._data.get(id)

        for catalog_item in self:
            if catalog_item.path == path:
                return catalog_item
        
        return fallback

    def remove(self, catalog_item):
        """Get a CatalogItem with is path and removing it if found"""

        if not isinstance(catalog_item, CatalogItem):
            catalog_item = self.get(catalog_item)

        if catalog_item:
            return self._data.pop(catalog_item.id)

        print(f'Warning: {catalog_item} cannot be remove, not in {self}')
        return None

    def add(self, catalog_path):
        """Adding a CatalogItem with the missing parents"""

        # Add missing parents catalog
        for parent in Path(catalog_path).parents[:-1]:
            print(parent, self.get(parent))
            if self.get(parent):
                continue
            
            cat_item = CatalogItem(self, path=parent)
            self._data[cat_item.id] = cat_item

        cat_item = self.get(catalog_path)
        if not cat_item:
            cat_item = CatalogItem(self, path=catalog_path)
            self._data[cat_item.id] = cat_item

        return cat_item
        
    def update(self, catalogs):
        'Add or remove catalog entries if on the list given or not'

        catalogs = set(catalogs) # Remove doubles

        added = [c for c in catalogs if not self.get(path=c)]
        removed = [c.path for c in self if c.path not in catalogs]

        if added:
            print(f'{len(added)} Catalog Entry Added \n{tuple(c.name for c in added[:10])}...\n')
        if removed:
            print(f'{len(removed)} Catalog Entry Removed \n{tuple(c.name for c in removed[:10])}...\n')

        for catalog_item in removed:
            self.remove(catalog_item)

        for catalog_item in added:
            self.add(catalog_item)

    def __iter__(self):
        return self._data.values().__iter__()
    
    def __getitem__(self, key):
        if isinstance(key, int):
            return self._data.values()[key]

        return self._data[key]

    def __contains__(self, item):
        if isinstance(item, str): # item is the id
            return item in self._data
        else:
            return item in self

    def __repr__(self):
        return f'Catalog(filepath={self.filepath})'