
import bpy
import os
from os.path import abspath, join

from bpy.types import (AddonPreferences, PointerProperty, PropertyGroup)
from bpy.props import (BoolProperty, StringProperty, CollectionProperty, 
    EnumProperty, IntProperty)
    
from asset_library.constants import (DATA_TYPES, DATA_TYPE_ITEMS, 
    ICONS, RESOURCES_DIR, ADAPTER_DIR, ADAPTERS)

from asset_library.common.file_utils import import_module_from_path, norm_str
from asset_library.common.bl_utils import get_addon_prefs
#from asset_library.common.functions import get_catalog_path

from pathlib import Path
import importlib
import inspect


def update_library_config(self, context):
    print('update_library_config not yet implemented')

def update_library_path(self, context):
    prefs = get_addon_prefs()

    self['bundle_directory'] = str(self.library_path)

    if not self.custom_bundle_name:
        self['custom_bundle_name'] = self.name

    if not self.custom_bundle_directory:
        custom_bundle_dir = Path(prefs.bundle_directory, self.library_name).resolve()
        self['custom_bundle_directory'] = str(custom_bundle_dir)

    #if self.custom_bundle_directory:
    #    self['custom_bundle_directory'] = abspath(bpy.path.abspath(self.custom_bundle_directory))
    #else:
    #    bundle_directory = join(prefs.bundle_directory, norm_str(self.name))
    #    self['custom_bundle_directory'] = abspath(bundle_directory)

    self.set_library_path()

def update_all_library_path(self, context):
    #print('update_all_assetlib_paths')

    prefs = get_addon_prefs()

    #if self.custom_bundle_directory:
    #    self['custom_bundle_directory'] = abspath(bpy.path.abspath(self.custom_bundle_directory))

    for lib in prefs.libraries:
        update_library_path(lib, context)
        #lib.set_library_path()

def get_adapter_items(self, context):
    #prefs = get_addon_prefs()

    items = [('NONE', 'None', '', 0)]
    items += [(norm_str(a.name, format=str.upper), a.name, "", i+1) for i, a in enumerate(ADAPTERS)]
    return items

def get_library_items(self, context):
    prefs = get_addon_prefs()

    items = [('NONE', 'None', '', 0)]
    items += [(l.name, l.name, "", i+1) for i, l in enumerate(prefs.libraries) if l != self]

    return items

def get_store_library_items(self, context):
    #prefs = get_addon_prefs()

    #libraries = [l for l in prefs.libraries if l.merge_library == self.name]

    return  [(l.name, l.name, "", i) for i, l in enumerate([self] + self.merge_libraries)]


class AssetLibraryAdapters(PropertyGroup):
    parent = None

    def __iter__(self):
        return (getattr(self, p) for p in self.bl_rna.properties.keys() if p not in ('rna_type', 'name'))

'''
class ConformAssetLibrary(PropertyGroup):
    adapters : bpy.props.PointerProperty(type=AssetLibraryAdapters)
    adapter_name : EnumProperty(items=get_adapter_items)
    directory : StringProperty(
        name="Target Directory",
        subtype='DIR_PATH',
        default=''
    )

    template_image : StringProperty(default='', description='../{name}_image.png')
    template_video : StringProperty(default='', description='../{name}_video.mov')
    template_description : StringProperty(default='', description='../{name}_asset_description.json')
    
    #externalize_data: BoolProperty(default=False, name='Externalize Data')
    blend_depth: IntProperty(default=1, name='Blend Depth')

    @property
    def adapter(self):
        name = norm_str(self.adapter_name)
        if not hasattr(self.adapters, name):
            return

        return getattr(self.adapters, name)

    def to_dict(self):
        data = {p: getattr(self, p) for p in self.bl_rna.properties.keys() if p !='rna_type'}

        data['adapter'] = self.adapter.to_dict()
        data['adapter']['name'] = data.pop('adapter_name')

        del data['adapters']

        return data
'''

class AssetLibrary(PropertyGroup):
    name : StringProperty(name='Name', default='Action Library', update=update_library_path)
    id : StringProperty()
    auto_bundle : BoolProperty(name='Auto Bundle', default=False)
    expand : BoolProperty(name='Expand', default=False)
    use : BoolProperty(name='Use', default=True, update=update_library_path)
    data_type : EnumProperty(name='Type', items=DATA_TYPE_ITEMS, default='COLLECTION')
    
    template_image : StringProperty(default='', description='../{name}_image.png')
    template_video : StringProperty(default='', description='../{name}_video.mov')
    template_description : StringProperty(default='', description='../{name}_asset_description.json')

    bundle_directory : StringProperty(
        name="Bundle Directory",
        subtype='DIR_PATH',
        default=''
    )

    use_custom_bundle_directory : BoolProperty(default=False, update=update_library_path)
    custom_bundle_directory : StringProperty(
        name="Bundle Directory",
        subtype='DIR_PATH',
        default='',
        update=update_library_path
    )
    #use_merge : BoolProperty(default=False, update=update_library_path)

    use_custom_bundle_name : BoolProperty(default=False, update=update_library_path)
    custom_bundle_name : StringProperty(name='Merge Name', update=update_library_path)
    #merge_library : EnumProperty(name='Merge Library', items=get_library_items, update=update_library_path)
    #merge_name : StringProperty(name='Merge Name', update=update_library_path)

    #Library when adding an asset to the library if merge with another
    store_library: EnumProperty(items=get_store_library_items, name="Library")

    template: StringProperty()
    expand_extra : BoolProperty(name='Expand', default=False)
    blend_depth : IntProperty(name='Blend Depth', default=1)

    # source_directory : StringProperty(
    #     name="Path",
    #     subtype='DIR_PATH',
    #     default='',
    #     update=update_library_path
    # )


    #adapter : EnumProperty(items=adapter_ITEMS)
    adapters : bpy.props.PointerProperty(type=AssetLibraryAdapters)
    adapter_name : EnumProperty(items=get_adapter_items)
    parent : StringProperty()

    # data_file_path : StringProperty(
    #     name="Path",
    #     subtype='FILE_PATH',
    #     default='',
    # )

    #def __init__(self):
    #    self.adapters.parent = self

    @property
    def merge_libraries(self):
       prefs = get_addon_prefs()
       return [l for l in prefs.libraries if l != self and (l.library_path == self.library_path)]

    @property
    def child_libraries(self):
       prefs = get_addon_prefs()
       return [l for l in prefs.libraries if l != self and (l.parent == self.name)]

    @property
    def data_types(self):
        data_type = self.data_type
        if data_type == 'FILE':
            data_type = 'COLLECTION'
        return f'{data_type.lower()}s'

    @property
    def adapter(self):
        name = norm_str(self.adapter_name)
        if not hasattr(self.adapters, name):
            return

        return getattr(self.adapters, name)

    @property
    def library(self):
        prefs = get_addon_prefs()
        asset_lib_ref = bpy.context.space_data.params.asset_library_ref

        #TODO work also outside asset_library_area
        if asset_lib_ref not in prefs.libraries:
            return None
        
        return prefs.libraries[asset_lib_ref]

    @property
    def library_path(self):
        prefs = get_addon_prefs()

        library_name = self.library_name
        #if not self.use_custom_bundle_name:
        #    library_name = norm_str(library_name)

        if self.use_custom_bundle_directory:
            return Path(self.custom_bundle_directory).resolve()
        else:
            library_name = norm_str(library_name)
            return Path(prefs.bundle_directory, library_name).resolve()

    @property
    def library_name(self):
        if self.use_custom_bundle_name:
            return self.custom_bundle_name
        
        return self.name

    def clear_library_path(self):
        #print('Clear Library Path', self.name)

        prefs = bpy.context.preferences
        libs = prefs.filepaths.asset_libraries

        #path = self.library_path.as_posix()
        
        for l in reversed(libs):
            #lib_path = Path(l.path).resolve().as_posix()

            prev_name = self.get('asset_library') or self.library_name

            #print(l.name, prev_name)

            if (l.name == prev_name):
                index = list(libs).index(l)
                try:
                    bpy.ops.preferences.asset_library_remove(index=index)
                    return
                except AttributeError:
                    pass
        
        #print('No library removed')
    
    def set_dict(self, data, obj=None):
        """"Recursive method to set all attribute from a dict to this instance"""

        if obj is None:
            obj = self

        # Make shure the input dict is not modidied
        data = data.copy()

        #print(obj)

        for key, value in data.items():
            if isinstance(value, dict):

                if 'name' in value:
                    setattr(obj, f'{key}_name', value.pop('name'))

                #print('Nested value', getattr(obj, key))
                self.set_dict(value, obj=getattr(obj, key))
            
            elif key in obj.bl_rna.properties.keys():
                if key == 'id':
                    value = str(value)
                
                elif key == 'custom_bundle_name':
                    if not 'use_custom_bundle_name' in data.values():
                        obj["use_custom_bundle_name"] = True
                
                elif isinstance(value, str):
                    value = os.path.expandvars(value)
                    value = os.path.expanduser(value)

                #print('set attr', key, value)
                setattr(obj, key, value)
                #obj[key] = value
            
            else:
                print(f'Prop {key} of {obj} not exist')

        self['bundle_directory'] = str(self.library_path)

        if not self.custom_bundle_name:
            self['custom_bundle_name'] = self.name

        # self.adapter_name = data['adapter']
        # if not self.adapter:
        #     print(f"No adapter named {data['adapter']}")
        #     return
        

        # for key, value in data.items():
        #     if key == 'options':
        #         for k, v in data['options'].items():
        #             setattr(self.adapter, k, v)
        #     elif key in self.bl_rna.properties.keys():
        #         if key == 'id':
        #             value = str(value)
                
        #         if key == 'custom_bundle_name':
        #             if not 'use_custom_bundle_name' in data.values():
        #                 self["use_custom_bundle_name"] = True

        #         self[key] = value

    def to_dict(self):
        data = {p: getattr(self, p) for p in self.bl_rna.properties.keys() if p !='rna_type'}
        data['adapter'] = self.adapter.to_dict()
        #data['adapter'] = data.pop('adapter_name')
        data['adapter']['name'] = data.pop('adapter_name')
        del data['adapters']

        #data['conform'] = self.conform.to_dict()

        return data

    def set_library_path(self):
        '''Update the Blender Preference Filepaths tab with the addon libraries'''

        prefs = bpy.context.preferences

        name = self.library_name
        prev_name = self.get('asset_library') or name

        lib = prefs.filepaths.asset_libraries.get(prev_name)
        lib_path = self.library_path

        #print('name', name)
        #print('lib', lib)
        #print('lib_path', lib_path)
        #print('self.merge_library ', self.merge_library)
        #print('prev_name', prev_name)
        #print('\nset_library_path')
        #print(f'{self.name=}, {prev_name=}, {lib_path=}, {self.use}')

        if not lib_path:
            self.clear_library_path()
            return
        
        if not self.use:
            if all(not l.use for l in self.merge_libraries):
                self.clear_library_path()
            return

        # Create the Asset Library Path
        if not lib:
            try:
                bpy.ops.preferences.asset_library_add(directory=str(lib_path))
            except AttributeError:
                return
                
            lib = prefs.filepaths.asset_libraries[-1]
            
        lib.name = name

        self['asset_library'] = name
        lib.path = str(lib_path)
    
    @property
    def is_user(self):
        prefs = get_addon_prefs()
        return self in prefs.user_libraries.values()

    @property
    def is_env(self):
        prefs = get_addon_prefs()
        return self in prefs.env_libraries.values()

    
    def add_row(self, layout, data=None, prop=None, label='',
            boolean=None, factor=0.39):
        '''Act like the use_property_split but with much more control'''

        enabled = True
        split = layout.split(factor=factor, align=True)

        row = split.row(align=False)
        row.use_property_split = False
        row.alignment= 'RIGHT'
        row.label(text=str(label))
        if boolean:
            boolean_data = self
            if isinstance(boolean, (list, tuple)):
                boolean_data, boolean = boolean

            row.prop(boolean_data, boolean, text='')
            enabled = getattr(boolean_data, boolean)

        row = split.row(align=True)
        row.enabled = enabled

        if isinstance(data, str):
            row.label(text=data)
        else:
            row.prop(data or self, prop, text='')
        
        return split
        

    def draw_operators(self, layout):
        row = layout.row(align=True)
        row.alignment = 'RIGHT'
        row.prop(self, 'adapter_name', text='')
        row.prop(self, 'auto_bundle', text='', icon='UV_SYNC_SELECT')

        row.operator("assetlib.diff", text='', icon='FILE_REFRESH').name = self.name

        op = row.operator("assetlib.bundle", icon='MOD_BUILD', text='')
        op.name = self.name

        layout.separator(factor=3)

    """
    def draw_extra(self, layout):
        #box = layout.box()

        col = layout.column(align=False)

        row = col.row(align=True)
        row.use_property_split = False
        #row.alignment = 'LEFT'
        icon = "DISCLOSURE_TRI_DOWN" if self.expand_extra else "DISCLOSURE_TRI_RIGHT"
        row.label(icon='BLANK1')
        subrow = row.row(align=True)
        subrow.alignment = 'LEFT'
        subrow.prop(self, 'expand_extra', icon=icon, emboss=False, text="Conform Options")
        #row.prop(self, 'expand_extra', text='', icon="OPTIONS", emboss=False)
        #row.prop(self, 'expand_extra', emboss=False, text='Options')
        #row.label(text='Conform Options')
        subrow = row.row(align=True)
        subrow.alignment = 'RIGHT'
        subrow.prop(self.conform, "adapter_name", text='')

        op = subrow.operator('assetlib.diff', text='', icon='FILE_REFRESH')#, icon='MOD_BUILD'
        op.name = self.name
        op.conform = True

        op = subrow.operator('assetlib.generate_previews', text='', icon='SEQ_PREVIEW')#, icon='MOD_BUILD'
        op.name = self.name
        #op.conform = True

        op = subrow.operator('assetlib.bundle', text='', icon='MOD_BUILD')#, icon='MOD_BUILD'
        op.name = self.name
        op.directory = self.conform.directory
        op.conform = True

        subrow.label(icon='BLANK1')
        #subrow.separator(factor=3)

        if self.expand_extra and self.conform.adapter:
            col.separator()
            self.conform.adapter.draw_prefs(col)    

            col.separator()
            col.separator()
            #row = layout.row(align=True)
            #row.label(text='Conform Library')
            col.prop(self.conform, "directory")
            col.prop(self.conform, "blend_depth")
            #col.prop(self.conform, "externalize_data")
            subcol = col.column(align=True)
            subcol.prop(self.conform, "template_description", text='Template Description', icon='COPY_ID')
            subcol.prop(self.conform, "template_image", text='Template Image', icon='COPY_ID')
            subcol.prop(self.conform, "template_video", text='Template Video', icon='COPY_ID')

                      

        col.separator()
        """


    def draw(self, layout):
        prefs = get_addon_prefs()
        #box = layout.box()

        row = layout.row(align=True)
        #row.use_property_split = False

        #row.alignment = 'LEFT'
        icon = "DISCLOSURE_TRI_DOWN" if self.expand else "DISCLOSURE_TRI_RIGHT"
        row.prop(self, 'expand', icon=icon, emboss=False, text='')

        if self.is_user:
            row.prop(self, 'use', text='')
            row.prop(self, 'data_type', icon_only=True, emboss=False)
            row.prop(self, 'name', text='')

            self.draw_operators(row)

            index = prefs.user_libraries.index(self)
            row.operator("assetlib.remove_user_library", icon="X", text='', emboss=False).index = index

        else:
            row.prop(self, 'use', text='')
            row.label(icon=ICONS[self.data_type])
            #row.label(text=self.name)
            subrow = row.row(align=True)
            subrow.alignment = 'LEFT'
            subrow.prop(self, 'expand', emboss=False, text=self.name)
            #row.separator_spacer()

            self.draw_operators(row)

            sub_row = row.row()
            sub_row.enabled = False
            sub_row.label(icon='FAKE_USER_ON')

        if self.expand:
            col = layout.column(align=False)
            col.use_property_split = True
            #row = col.row(align=True)

            row = self.add_row(col, 
                prop="custom_bundle_name", 
                boolean="use_custom_bundle_name", 
                label='Custom Bundle Name')

            row.enabled = not self.use_custom_bundle_directory

            prop = "bundle_directory"
            if self.use_custom_bundle_directory:
                prop = "custom_bundle_directory"

            self.add_row(col, prop=prop,
                boolean="use_custom_bundle_directory", 
                label='Custom Bundle Directory',
            )

            col.prop(self, "blend_depth")

            subcol = col.column(align=True)
            subcol.prop(self, "template_description", text='Template Description', icon='COPY_ID')
            subcol.prop(self, "template_image", text='Template Image', icon='COPY_ID')
            subcol.prop(self, "template_video", text='Template Video', icon='COPY_ID')


            if self.adapter:
                col.separator()
                self.adapter.draw_prefs(col)
        
            for lib in self.child_libraries:
                lib.draw(layout)

            col.separator()



class Collections:
    '''Util Class to merge multiple collections'''

    collections = []

    def __init__(self, *collection):
        self.collections = collection

        for col in collection:
            #print('Merge methods')
            for attr in dir(col):
                if attr.startswith('_'):
                    continue

                value = getattr(col, attr)
                #if not callable(value):
                #    continue

                setattr(self, attr, value)
    
    def __contains__(self, item):
        if isinstance(item, str):
            return item in self.to_dict()
        else:
            return item in self

    def __iter__(self):
        return self.to_list().__iter__()

    def __getitem__(self, item):
        if isinstance(item, int):
            return self.to_list()[item]
        else:
            return self.to_dict()[item]
    
    def get(self, item, fallback=None):
        return self.to_dict().get(item) or fallback

    def to_dict(self):
        return {k:v for c in self.collections for k, v in c.items()}

    def to_list(self):
        return [v for c in self.collections for v in c.values()]

    def get_parent(self, item):
        for c in self.collections:
            if item in c.values():
                return c

    def index(self, item):
        c = self.get_parent(item)

        if not c:
            return item in self
        
        return list(c.values()).index(item)


#class AssetLibraryOptions(PropertyGroup):
#    pass


class AssetLibraryPrefs(AddonPreferences):
    bl_idname = __package__

    adapters = []

    #action : bpy.props.PointerProperty(type=AssetLibraryPath)
    #asset : bpy.props.PointerProperty(type=AssetLibraryPath)
    #adapters = {}
    author: StringProperty(default=os.getlogin())

    image_player: StringProperty(default='')
    video_player: StringProperty(default='')

    adapter_directory : StringProperty(
        name="Adapter Directory",
        subtype='DIR_PATH'
    )

    env_libraries : CollectionProperty(type=AssetLibrary)
    user_libraries : CollectionProperty(type=AssetLibrary)
    expand_settings: BoolProperty(default=False)
    bundle_directory : StringProperty(
        name="Path",
        subtype='DIR_PATH',
        default='',
        update=update_all_library_path
    )



    config_directory : StringProperty(
        name="Config Path",
        subtype='FILE_PATH',
        default=str(RESOURCES_DIR/"asset_library_config.json"),
        update=update_library_config
    )

    def load_adapters(self):
        from asset_library.adapters.adapter import AssetLibraryAdapter

        #global ADAPTERS

        print('\n------Load Adapters')
        
        ADAPTERS.clear()

        adapter_files = list(ADAPTER_DIR.glob('*.py'))
        if self.adapter_directory:
            user_adapter_dir = Path(self.adapter_directory)
            if user_adapter_dir.exists():
                adapter_files += list(user_adapter_dir.glob('*.py'))

        for adapter_file in adapter_files:
            mod = import_module_from_path(adapter_file)

            if adapter_file.stem.startswith('_'):
                continue
            
            #print(adapter_file)
            for name, obj in inspect.getmembers(mod):

                if not inspect.isclass(obj): 
                    continue

                #print(obj.__bases__)
                if not AssetLibraryAdapter in obj.__mro__:
                    continue
                
                # Non registering base adapter
                if obj is AssetLibraryAdapter or obj.name in (a.name for a in ADAPTERS): 
                    continue

                try:
                    print(f'Register Plugin {name}')
                    bpy.utils.register_class(obj)
                    setattr(AssetLibraryAdapters, norm_str(obj.name), bpy.props.PointerProperty(type=obj))
                    ADAPTERS.append(obj)

                except Exception as e:
                    print(f'Could not register adapter {name}')
                    print(e)

    @property
    def libraries(self):
        return Collections(self.env_libraries, self.user_libraries)

    def draw(self, context):
        prefs = get_addon_prefs()

        layout = self.layout
        #layout.use_property_split = True

        main_col = layout.column(align=False)

        box = main_col.box()
        row = box.row(align=True)
        icon = "DISCLOSURE_TRI_DOWN" if self.expand_settings else "DISCLOSURE_TRI_RIGHT"
        row.prop(self, 'expand_settings', icon=icon, emboss=False, text='')
        row.label(icon='PREFERENCES')
        row.label(text='Settings')
        #row.separator_spacer()
        subrow = row.row()
        subrow.alignment = 'RIGHT'
        subrow.operator("assetlib.reload_addon", text='Reload Addon')

        if prefs.expand_settings:
            col = box.column(align=True)
            col.use_property_split = True

            #col.prop(self, 'use_single_path', text='Single Path')
            col.prop(self, 'bundle_directory', text='Bundle Directory')

            col.separator()

            col.prop(self, 'adapter_directory')
            col.prop(self, 'config_directory')

            col.separator()

            #col.prop(self, 'template_description', text='Asset Description Template', icon='COPY_ID')

            #col.separator()

            #col.prop(self, 'template_image', text='Template Image', icon='COPY_ID')
            col.prop(self, 'image_player', text='Image Player') #icon='OUTLINER_OB_IMAGE'

            #col.separator()

            #col.prop(self, 'template_video', text='Template Video', icon='COPY_ID')
            col.prop(self, 'video_player', text='Video Player') #icon='FILE_MOVIE'

            col.separator()

            col.operator("assetlib.add_user_library", text='Bundle All Libraries', icon='MOD_BUILD')

        for lib in self.libraries:# list(self.env_libraries) + list(self.user_libraries):
            if lib.parent:
                continue

            box = main_col.box()
            lib.draw(box)

        row = main_col.row()
        row.alignment = 'RIGHT'
        row.operator("assetlib.add_user_library", icon="ADD", text='', emboss=False)


classes = [
    AssetLibraryAdapters,
    #ConformAssetLibrary,
    AssetLibrary,
    AssetLibraryPrefs,
]

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    
    prefs = get_addon_prefs()

    # Read Env and override preferences
    bundle_dir = os.getenv('ASSETLIB_BUNDLE_DIR')
    if bundle_dir:
        prefs['bundle_directory'] = os.path.expandvars(bundle_dir)

    config_dir = os.getenv('ASSETLIB_CONFIG_DIR')
    if config_dir:
        prefs['config_directory'] = os.path.expandvars(config_dir)

    adapter_dir = os.getenv('ASSETLIB_ADAPTER_DIR')
    if adapter_dir:
        prefs['adapter_directory'] = os.path.expandvars(adapter_dir)
    
    prefs.load_adapters()

def unregister():
    for cls in reversed(classes + ADAPTERS):
        bpy.utils.unregister_class(cls)

    ADAPTERS.clear()