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

import bpy, os, math

from typing import List, Tuple

from bpy.types import PropertyGroup, WindowManager
from bpy.props import StringProperty, BoolProperty, EnumProperty, CollectionProperty, PointerProperty, FloatProperty

from .constants import texture_pack_options
from .registries.enum_registry import EnumRegistry
from .registries.resource_lists_registry import ResourceListsRegistry
from .registries.config_registry import ConfigRegistry
 
class TagProperty(PropertyGroup):
    title: StringProperty() # type: ignore


class AssetSubsetProperty(PropertyGroup):
    """
    Specifies a subset of node groups to handle
    submenus in pies.
    """
    id: StringProperty() # type: ignore
    text: StringProperty() # type: ignore


# Can't be inlined in class declaration:
# https://blender.stackexchange.com/questions/141913/create-a-tree-of-properties
AssetSubsetProperty.children = bpy.props.CollectionProperty(type=AssetSubsetProperty)    


class PropertySection(PropertyGroup):
    """
    Each panel gets is own instance, so changing a setting in one panel, does not effect another.
    """
    section_id: StringProperty() # type: ignore

    export_library_back: EnumProperty(
        name='',
        description='Export to this Library',
        items=lambda self, context: [ EnumRegistry.replace_for_enum3(l.path, l.name, l.name) for l in context.preferences.filepaths.asset_libraries ]
    ) # type: ignore

    @property
    def export_library(self, string):
        self.export_library_back = EnumRegistry.id_for_str(string)

    @property
    def export_library(self) -> str:
        return EnumRegistry.str_for_id(self.export_library_back)


    export_file_back: EnumProperty(
        name='',
        description='Export to this File',
        items=lambda self, _: [ EnumRegistry.replace_for_enum3(*e) for e in ResourceListsRegistry.get().library_files(self.export_library) ]
    ) # type: ignore

    @property
    def export_file(self, string):
        self.export_file_back = EnumRegistry.id_for_str(string)

    @property
    def export_file(self) -> str:
        return EnumRegistry.str_for_id(self.export_file_back)


    catalog_back: EnumProperty(
        name='',
        description='Catalog to map this Item to',
        items=lambda self, _: [ EnumRegistry.replace_for_enum3(*e) for e in ResourceListsRegistry.get().catalogs(self.export_library) ]
    ) # type: ignore

    @property
    def catalog(self, string):
        self.catalog_back = EnumRegistry.id_for_str(string)

    @property
    def catalog(self) -> str:
        return EnumRegistry.str_for_id(self.catalog_back)


    tag_select_back: EnumProperty(
        name='', 
        description='Select Tag previously used',
        items=lambda self, _: [ EnumRegistry.replace_for_enum3(*e) for e in ConfigRegistry.get().tags() ]
    ) # type: ignore

    @property
    def tag_select(self, string):
        self.tag_select_back = EnumRegistry.id_for_str(string)

    @property
    def tag_select(self) -> str:
        return EnumRegistry.str_for_id(self.tag_select_back)


    tags: CollectionProperty(name='Tags', type=TagProperty) # type: ignore
    new_tag: StringProperty(name='', description='Enter new Tag Name') # type: ignore

    description: StringProperty(name='', description='Asset Description') # type: ignore

    show_stats: BoolProperty(name='', description='Open Panel with Info about current Library .blend') # type: ignore

    use_render_as_preview: BoolProperty(name='', description='Use the current Render Result as Preview Image, instead of rendering a Preview or use Placeholder Image', default=False) # type: ignore

    def full_filename(self) -> str:
        """
        Full blend path.
        """
        return os.path.join(self.export_library, self.export_file)

    def export_catalog(self) -> str:
        """
        Catalog if current library contains it, otherwise ''.
        """
        return self.catalog if ResourceListsRegistry.get().catalogs(self.export_library) else ''

    def tag_string(self) -> str:
        """
        All active tags, joined by ~ into a single string to use in stringproperty.
        """
        return '~'.join([ t.title for t in self.tags])


class Properties(PropertyGroup):
    material_node_section: PointerProperty(type=PropertySection) # type: ignore
    geometry_node_section: PointerProperty(type=PropertySection) # type: ignore
    object_collection_section: PointerProperty(type=PropertySection) # type: ignore


    collection_back: EnumProperty(
        name='Collection',
        description='Collection to export',
        items=lambda _, __: [ EnumRegistry.replace_for_enum3(c.name, c.name, c.name) for c in bpy.data.collections ]
    ) # type: ignore

    @property
    def collection(self, string):
        self.collection_back = EnumRegistry.id_for_str(string)

    @property
    def collection(self) -> str:
        return EnumRegistry.str_for_id(self.collection_back)    
    

    author: StringProperty(name='', description='Author of this Asset') # type: ignore
    texture_pack_mode: EnumProperty(
        name='Texture pack mode',
        description='Select style of texture packing',
        items=texture_pack_options
    ) # type: ignore
    auto_place_padding: FloatProperty(name='Padding', description='Padding used in Grid placement', default=0.2) # type: ignore


    shader_tag_select_back: EnumProperty(
        name='Category',
        description='Category to store the Node Tree',
        items=lambda _, __: [ EnumRegistry.replace_for_enum3(*e) for e in ConfigRegistry.get().shader_tags() ]
    ) # type: ignore

    @property
    def shader_tag_select(self, string):
        self.shader_tag_select_back = EnumRegistry.id_for_str(string)

    @property
    def shader_tag_select(self) -> str:
        return EnumRegistry.str_for_id(self.shader_tag_select_back)    

    geometry_tag_select_back: EnumProperty(
        name='Category',
        description='Category to store the Node Tree',
        items=lambda _, __: [ EnumRegistry.replace_for_enum3(*e) for e in ConfigRegistry.get().geometry_tags() ]
    ) # type: ignore

    @property
    def geometry_tag_select(self, string):
        self.geometry_tag_select_back = EnumRegistry.id_for_str(string)

    @property
    def geometry_tag_select(self) -> str:
        return EnumRegistry.str_for_id(self.geometry_tag_select_back)        

    show_object_cleaners: BoolProperty(name='Object Cleanup', description='Toggle Object Cleanup Options', default=False) # type: ignore
    oc_remove_animation_data: BoolProperty(name='Remove animation Data', description='Remove animation Data from selected Meshes', default=False) # type: ignore
    oc_unparent: BoolProperty(name='Remove parent Empties', description='Remove all Parent Empties, leaving Transformation (Clear animation Data may be necessary)', default=True) # type: ignore
    oc_merge_objects: BoolProperty(name='Merge Objects', description='Merge Meshes in selected Hierarchy', default=False) # type: ignore

    show_mesh_cleaners: BoolProperty(name='Mesh Cleanup', description='Toggle Mesh Cleanup Options', default=False) # type: ignore
    mc_clear_custom_split_normals: BoolProperty(name='Clear custom split Normals', description='Clear these on all selected Meshes', default=True) # type: ignore
    mc_clear_sharp: BoolProperty(name='Clear Sharp', description='Clear sharp edges on all selected Meshes', default=True) # type: ignore
    mc_tris_to_quads: BoolProperty(name='Tris to Quads', description='Convert mesh triangles to quads', default=True) # type: ignore
    mc_set_auto_smooth: BoolProperty(name='Enabled Auto Smooth', description='Enable Auto Smooth and set Angle', default=True) # type: ignore
    mc_recalculate_normals_outside: BoolProperty(name='Recalculate Normals', description='Recalculate Normals to Outside on all selected Meshes', default=True) # type: ignore
    mc_join_vertices: BoolProperty(name='Join Vertices', description='Join Vertices close together, which isn\'t needed in Blender, but some 3D Formats', default=True) # type: ignore
    mc_limited_dissolve: BoolProperty(name='Limited Dissolve', description='Delete all unnesseary Edges and Vertices to get large N-Gons', default=False) # type: ignore

    mc_auto_smooth_angle: FloatProperty(name='Auto Smooth Angle', default=math.radians(30), subtype='ANGLE', precision=2) # type: ignore
    mc_join_vertices_distance: FloatProperty(name='Max Distance', description='Maximum Distance of Vertices when joining', default=0.0001, precision=5) # type: ignore
    mc_limited_dissolve_angle: FloatProperty(name='Dissolve Max Angle', description='Maximum Angle for Limited Dissolve', default=math.radians(0.5), subtype='ANGLE', precision=2) # type: ignore

    replace_mode: BoolProperty(name='Re-Place Mode', description='Replace target Object, copy Transformation', default=False) # type: ignore
    place_quick: BoolProperty(name='Quick Transform', description='Quick place Mode', default=True) # type: ignore
    place_create_copy: BoolProperty(name='Copy before Place', description='Create a Copy before placing', default=False) # type: ignore
    place_linked_copy: BoolProperty(name='Linked Copy', description='Create a linked Copy', default=True) # type: ignore
    place_auto_parent: BoolProperty(name='Auto Parent', description='Automatically set Target Object as Parent', default=False) # type: ignore

    geometry_node_filter: StringProperty(
        name='Geometry Node Filter', 
        description='Filter Names of Node Groups', 
        default='',
        options={'TEXTEDIT_UPDATE'}
    ) # type: ignore

    shader_node_filter: StringProperty(
        name='Shader Node Filter', 
        description='Filter Names of Node Groups', 
        default='',
        options={'TEXTEDIT_UPDATE'}
    ) # type: ignore

    import_3d_catalogs_objects: EnumProperty(
        name='Category',
        description='Category of Objects',
        items=lambda _, __: ResourceListsRegistry.get().col_or_object_catalogs(),
        update=lambda self, _: self.import_3d_catalog_update(0)
    ) # type: ignore

    import_3d_objects: EnumProperty(
        name='Object',
        description='Object to import',
        items=lambda self, _: ResourceListsRegistry.get().col_or_objs_by_catalog(self.import_3d_catalogs_objects)
    ) # type: ignore

    import_3d_catalogs_materials: EnumProperty(
        name='Category',
        description='Category of Materials',
        items=lambda _, __: ResourceListsRegistry.get().materials_catalogs(),
        update=lambda self, _: self.import_3d_catalog_update(1)
    ) # type: ignore

    import_3d_materials: EnumProperty(
        name='Material',
        description='Material to import',
        items=lambda self, _: ResourceListsRegistry.get().materials_by_catalog(self.import_3d_catalogs_materials)
    ) # type: ignore
 
    def import_3d_catalog_update(self, id: int):
        if id == 0:
            self.import_3d_objects = ResourceListsRegistry.get().col_or_objs_by_catalog(self.import_3d_catalogs_objects)[0][0]
        elif id == 1:
            self.import_3d_materials = ResourceListsRegistry.get().materials_by_catalog(self.import_3d_catalogs_materials)[0][0]


    material_import_prefer_existing: BoolProperty(name='Prefer existing Material', description='If the Material is already imported, use this instead of importing a Duplicate', default=True) # type: ignore
    material_import_linked: BoolProperty(name='Add Material linked', description='If true, the Material is linked, not appended', default=False) # type: ignore


    shader_nodes_by_catalog: CollectionProperty(type=AssetSubsetProperty) # type: ignore
    shader_nodes_by_category: CollectionProperty(type=AssetSubsetProperty) # type: ignore
    shader_nodes_by_toc: CollectionProperty(type=AssetSubsetProperty) # type: ignore
    geometry_nodes_by_catalog: CollectionProperty(type=AssetSubsetProperty) # type: ignore
    geometry_nodes_by_category: CollectionProperty(type=AssetSubsetProperty) # type: ignore
    geometry_nodes_by_toc: CollectionProperty(type=AssetSubsetProperty) # type: ignore
    materials_by_catalog: CollectionProperty(type=AssetSubsetProperty) # type: ignore
    materials_by_category: CollectionProperty(type=AssetSubsetProperty) # type: ignore
    materials_by_toc: CollectionProperty(type=AssetSubsetProperty) # type: ignore
    col_or_objs_by_catalog: CollectionProperty(type=AssetSubsetProperty) # type: ignore
    col_or_objs_by_category: CollectionProperty(type=AssetSubsetProperty) # type: ignore
    col_or_objs_by_toc: CollectionProperty(type=AssetSubsetProperty) # type: ignore


    def __update_properties(self, entries: List[Tuple[str, str, str]], props: bpy.props.CollectionProperty):
        """
        Fill single CollectionProperty(AssetSubsetProperty) with entries.
        """
        props.clear()
        for e in entries:
            a = props.add()
            a.id = e[0]
            a.text = e[1]


    def update_properties(self):
        """
        Fill the lists for all catalogs, categories and tocs for all kinds of assets.
        """
        self.material_node_section.section_id = 'M'
        self.geometry_node_section.section_id = 'G'
        self.object_collection_section.section_id = 'O'

        rl = ResourceListsRegistry.get()
        self.__update_properties(rl.shader_catalogs(), self.shader_nodes_by_catalog)
        self.__update_properties(rl.shader_categories(), self.shader_nodes_by_category)
        self.__update_properties(rl.shader_tocs(), self.shader_nodes_by_toc)
        self.__update_properties(rl.geometry_catalogs(), self.geometry_nodes_by_catalog)
        self.__update_properties(rl.geometry_categories(), self.geometry_nodes_by_category)
        self.__update_properties(rl.geometry_tocs(), self.geometry_nodes_by_toc)
        self.__update_properties(rl.materials_catalogs(), self.materials_by_catalog)
        self.__update_properties(rl.materials_categories(), self.materials_by_category)
        self.__update_properties(rl.materials_tocs(), self.materials_by_toc)
        self.__update_properties(rl.col_or_object_catalogs(), self.col_or_objs_by_catalog)
        self.__update_properties(rl.col_or_object_categories(), self.col_or_objs_by_category)
        self.__update_properties(rl.col_or_object_tocs(), self.col_or_objs_by_toc)


    def init(self):
        self.update_properties()


    @staticmethod
    def initialize():
        WindowManager.awp_properties = PointerProperty(type=Properties)
        bpy.context.window_manager.awp_properties.init()
        

    @staticmethod
    def get() -> 'Properties':
        return bpy.context.window_manager.awp_properties       


    @staticmethod
    def dispose():
        if WindowManager.awp_properties:
            del(WindowManager.awp_properties)
            