# Copyright (C) 2022 Thomas Hoppe (h0bB1T). All rights reserved.
#
# Unauthorized copying of this file via any medium is strictly prohibited.
# Proprietary and confidential.

import hashlib

from dataclasses import dataclass
from typing import Any, Dict, List, Tuple

from .types import RepositoryType
from .texture_info import TextureInfo
from .texture_entry import TextureEntry
from .image_lookup import ImageLookup
from .image_hash_repository import HashRepository
from .scanners.pbr_directory import PBRDirectory
from .scanners.gray_directory import GrayDirectory
from .scanners.brush_directory import BrushDirectory
from .scanners.image_directory import ImageDirectory
from ..previews_registry import PreviewsCollection, PreviewsRegistry


@dataclass
class TextureCategory:
    name: str
    key: str
    entries: List[TextureEntry]


    def dispose(self, thumbs: PreviewsCollection, image_lookup: ImageLookup):
        """
        Cleanup this category.
        """
        [ e.dispose(thumbs, image_lookup) for e in self.entries ]


    def enums(self, thumbs: PreviewsCollection) -> List[Tuple[str, str, str, int, int]]:
        """
        Get enum of images in this repository, suitable for EnumProperty.
        """
        return [ e.get_enum(thumbs, i) for i, e in enumerate(self.entries) ]


    @staticmethod
    def key_of(repo_name: str, name: str) -> str:
        return hashlib.sha256(f'{repo_name}/{name}'.encode('utf-8')).hexdigest()



class TextureRepository:
    """
    A single repository that consists of a specific type of textures.
    """
    def __init__(self, name: str, type: RepositoryType, prefix: str, style: int):
        PreviewsRegistry.instance().register(name)
        self.__thumbs = PreviewsRegistry.instance().collection(name)
        self.name, self.__type = name, type
        #self.categories = {} # type: Dict[str, List[TextureEntry]]
        self.__categories = {} # type: Dict[str, TextureCategory]
        self.__prefix = prefix # Prefix in the UI (X: ..).
        self.__style = style # PBRLinear, PBRExtreme, Grunge, Brush, used for rescan


    def dispose(self, image_lookup: ImageLookup):
        for category in self.__categories.values(): 
            category.dispose(self.__thumbs, image_lookup)
        self.__categories.clear() 


    @property
    def prefix(self): return self.__prefix


    def update_prefix(self, prefix: str):
        self.__prefix = prefix


    @property
    def style(self): return self.__style


    def has_category(self, category_key: str) -> bool:
        return category_key in self.__categories


    def get_category_enums(self) -> List[Tuple[str, str, str]]:
        """
        Create list of all categories inside this repository, suitable for EnumProperty.
        """
        r = []
        for category in self.__categories.values():
            n = len(category.entries)
            fe = category.name.split('/')
            for i in range(len(fe) - 1):
                fe[i] = '  '
            fe = ''.join(fe)
            r.append((
                category.key,
                f'{self.__prefix}:{fe} [{n}]' if self.__prefix else f'{fe} [{n}]',
                f'{self.__prefix}:{category.name}' if self.__prefix else category.name
            ))
        return r


    def get_image_enums(self, category_key: str) -> List[Tuple[str, str, str, int, int]]:
        """
        Return enums for all images in the requested category.
        """
        return self.__categories[category_key].enums(self.__thumbs)


    def update_image_lookup(self, image_lookup: ImageLookup):
        """
        Put all images into the lookup to quickly find category for a given bin_hash
        """
        for category in self.__categories.values():
            for image in category.entries:
                image_lookup.add_entry(category.key, image.key, image.info.bin_hash, image.info.bin_hash_v2)


    def get_entry(self, category_key: str, image_key: str, with_preview: bool, with_gpu: bool) -> TextureEntry:
        """
        Get entry selected by category and image in the UI, optionally with guaranteed preview image loading.
        """
        for image in self.__categories[category_key].entries:
            if image.key == image_key:
                if with_preview or with_gpu:
                    image.load_preview(self.__thumbs, with_gpu)
                return image


    def get_entries(self, category_key: str, with_preview: bool, with_gpu: bool = False) -> List[TextureEntry]:
        """
        Get all entries from category, optionally with guaranteed preview image loading.
        """
        if with_preview or with_gpu:
            for image in self.__categories[category_key].entries:
                image.load_preview(self.__thumbs, with_gpu)
        return self.__categories[category_key].entries


    def create_update_previews(self, category_key: str, missing_only: bool, size: int, whole_repository: bool):
        """
        Re-render some or all previews in the given category.
        """
        categories = self.__categories.values() if whole_repository else [ self.__categories[category_key] ]
        for category in categories:
            for image in category.entries:
                image.create_update_preview(missing_only, size, self.__thumbs)


    def add_pbr(self, categories: List[PBRDirectory], hash_repository: HashRepository):
        for c in categories:
            entries = []
            for i in c.entries:
                entry = TextureEntry(TextureInfo(hash_repository, pbr=i))
                entries.append(entry)
                
            key = TextureCategory.key_of(self.name, c.name)
            self.__categories[key] = TextureCategory(c.name, key, entries)


    def add_gray(self, categories: List[GrayDirectory], hash_repository: HashRepository):
        for c in categories:
            entries = []
            for i in c.entries:
                entry = TextureEntry(TextureInfo(hash_repository, gray=i))
                entries.append(entry)

            key = TextureCategory.key_of(self.name, c.name)
            self.__categories[key] = TextureCategory(c.name, key, entries) 


    def add_brush(self, categories: List[BrushDirectory], hash_repository: HashRepository):
        for c in categories:
            entries = []
            for i in c.entries:
                entry = TextureEntry(TextureInfo(hash_repository, brush=i))
                entries.append(entry)

            key = TextureCategory.key_of(self.name, c.name)
            self.__categories[key] = TextureCategory(c.name, key, entries) 


    def add_image(self, categories: List[ImageDirectory], hash_repository: HashRepository):
        for c in categories:
            entries = []
            for i in c.entries:
                entry = TextureEntry(TextureInfo(hash_repository, image=i))
                entries.append(entry)

            key = TextureCategory.key_of(self.name, c.name)
            self.__categories[key] = TextureCategory(c.name, key, entries)


    @staticmethod
    def load(data: Dict[str, Any]) -> 'TextureRepository':
        r = TextureRepository(
            data.get('name', 'UNKNOWN'),
            data.get('type', 0),
            data.get('prefix', ''),
            data.get('style', 10)
        )

        for c in data.get('entries', []):
            entries = []
            for e in c.get('entries', []):
                entries.append(TextureEntry.load(e))

            name = c.get('category')
            key = TextureCategory.key_of(r.name, name)
            r.__categories[key] = TextureCategory(name, key, entries)

        return r


    def save(self) -> Dict[str, Any]:
        """
        Used to store data in a cache file, so scanning on every startup
        is not needed.
        """
        categories = []
        for category in self.__categories.values():
            entries = []
            for entry in category.entries:
                entries.append(entry.save())

            categories.append({
                'category': category.name,
                'entries': entries
            })

        return {
            'name': self.name,
            'type': self.__type,
            'prefix': self.prefix,
            'style': self.style,
            'entries': categories,
        }