# 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

from typing import List, Tuple, Union

from bpy.types import AddonPreferences, PropertyGroup
from bpy.props import StringProperty, FloatProperty, IntProperty, CollectionProperty, EnumProperty, BoolProperty

from .constants import config_file, uv_mappings, image_interpolations
from .registries.key_registry import KeyRegistry
from .registries.texture_registry import TEXTURE_PATH_BRUSH_GRAY, TEXTURE_PATH_EXTREME_PBR, TEXTURE_PATH_GRUNGE, TEXTURE_PATH_PBR, TEXTURE_PATH_IMAGE, texture_path_style_name
from .utils.io import os_path, read_json, unified_path, write_json
from .registries.texture_registry import TextureRegistry


class DirPathProperty(PropertyGroup):
    path: StringProperty(name='Folder', subtype='DIR_PATH') # type: ignore
    style: IntProperty(description='How folders are styled') # type: ignore
    prefix: StringProperty(
        name='Prefix', 
        description='Prefix in category listings',
        update=lambda self, _: PreferencesPanel.update_prefix(self.path, self.prefix)
    ) # type: ignore


class PreferencesPanel(AddonPreferences):
    bl_idname = __package__

    update_allowed = True

    preview_scale_pie: FloatProperty(name='Create Preview Size', default=6.0) # type: ignore
    preview_columns_pie: IntProperty(name='Create Columns', default=8) # type: ignore
    preview_scale_side: FloatProperty(name='Menu Preview Size', default=8.0) # type: ignore
    preview_scale_side_popup: FloatProperty(name='Menu Popup Preview Size', default=6.0) # type: ignore
    preview_render_size: IntProperty(name='Preview Image Render Size', default=512) # type: ignore

    node_visualizer_enabled: BoolProperty(
        name='Enable Node Visualizer', 
        description='Show Previews of Textures in the Node Editor (where possible)',
        default=True
    ) # type: ignore
    node_visualizer_snw_preview: BoolProperty(
        name='SNW Texture Previews', 
        description='Display Preview Images when for SNW specific Nodes', 
        default=True
    ) # type: ignore
    node_visualizer_size: IntProperty(
        name='Preview Dimension',
        description='Size in Pixels',
        default=64,
        min=32,
        max=256
    ) # type: ignore
    node_visualizer_recursion: IntProperty(
        name='Texture lookup Depth',
        description='Lookup Depth into Sub-Node Groups to look for Textures',
        default=2,
        min=0,
        max=3
    ) # type: ignore

    default_texture_mapping: EnumProperty(
        name='Default Texture Mapping', 
        items=uv_mappings,
        default='LBOX'
    ) # type: ignore

    default_image_interpolation: EnumProperty(
        name='Default Image Interpolation', 
        items=image_interpolations,
        default='Linear'
    ) # type: ignore

    default_bevel_samples: IntProperty(
        name='Enable/Disable Bevel in PBR/Grunge Nodes and set Samples (0=Disabled)',
        default=4,
        min=0,
        max=16,
    ) # type: ignore

    texture_dirs: CollectionProperty(type=DirPathProperty) # type: ignore


    def correct_path(self):
        if self.grunge_dir.startswith('//'):
            self.grunge_dir = os_path(self.grunge_dir)

        PreferencesPanel.settings_changed()


    def draw(self, context: bpy.types.Context):
        l = self.layout # type: bpy.types.UILayout
        c = l.column(align=False)
        c.use_property_decorate = True
        c.use_property_split = False

        b = c.box().column(align=True)
        b.use_property_split = True
        b.label(text='Display Options:')
        b.separator()
        b.prop(self, 'preview_scale_pie')
        b.prop(self, 'preview_columns_pie')
        b.prop(self, 'preview_scale_side')
        b.prop(self, 'preview_scale_side_popup')
        b.prop(self, 'preview_render_size')

        b = c.box().column(align=True)
        b.use_property_split = True
        b.label(text='Behavioral Options:')
        b.separator()
        b.prop(self, 'default_texture_mapping')
        b.prop(self, 'default_image_interpolation')
        b.prop(self, 'default_bevel_samples', slider=True)

        b = c.box().column(align=True)
        b.use_property_split = True
        b.label(text='Node Visualizer:')
        b.separator()
        b.prop(self, 'node_visualizer_enabled')
        b.prop(self, 'node_visualizer_snw_preview')
        b.prop(self, 'node_visualizer_size')
        b.prop(self, 'node_visualizer_recursion')

        b = c.box().column(align=True)
        b.use_property_split = True
        b.label(text='Shortcuts:')
        b.separator()
        KeyRegistry.instance().render_prefs(b)
        
        b = c.box().column(align=True)
        b.use_property_split = True
        b.label(text='Texture Library Paths:')
        b.separator()
        r = b.row(align=True, heading='Add texture directory')
        r.operator('snw.support_add_texture_directory', text='Add Grunge dir', icon='NODE_COMPOSITING').style = TEXTURE_PATH_GRUNGE
        r.operator('snw.support_add_texture_directory', text='Add Image dir', icon='IMAGE_DATA').style = TEXTURE_PATH_IMAGE
        r.operator('snw.support_add_texture_directory', text='Add PBR dir', icon='NODE_MATERIAL').style = TEXTURE_PATH_PBR
        r.operator('snw.support_add_texture_directory', text='Add ExtremePBR dir', icon='MATERIAL').style = TEXTURE_PATH_EXTREME_PBR
        r.operator('snw.support_add_texture_directory', text='Add Brush dir', icon='BRUSH_DATA').style = TEXTURE_PATH_BRUSH_GRAY

        for n in sorted(self.texture_dirs, key=lambda x: x.style):
            r = b.row(align=True) # type: bpy.types.UILayout
            sp = r.split(factor=0.8, align=True)
            sp1 = sp.split(factor=0.4)
            sp1.label(text=texture_path_style_name(n.style))
            sp1.label(text=n.path)
            sp.prop(n, 'prefix')
            op = r.operator('snw.support_manage_texture_directory', text='', icon='FILE_REFRESH')
            op.mode = 'rescan'
            op.path = n.path
            op = r.operator('snw.support_manage_texture_directory', text='', icon='REMOVE')
            op.mode = 'remove'
            op.path = n.path            

        b.label(text='First time adding a new texture folder may take some time as textures are indexed for faster lookup, see console for progress.', icon='LIGHT')


    @staticmethod
    def add_texture_directory(path: str, style: int, do_scan: bool = True, prefix: str = ''):
        """
        Called after used has selected a new directory.
        """
        # Make this path always looking the same.
        path = unified_path(os.path.abspath(path))

        prefs = PreferencesPanel.get()
        de = prefs.texture_dirs.add()
        de.path = path
        de.style = style
        de.prefix = prefix
        if do_scan:
            TextureRegistry.instance().scan_repository(path, prefix, style)
        PreferencesPanel.save_backup_config_file()


    @staticmethod
    def remove_texture_directory(path: str):
        """
        Remove the entry.
        """
        prefs = PreferencesPanel.get()
        for i, e in enumerate(prefs.texture_dirs):
            if e.path == path:
                prefs.texture_dirs.remove(i)
                break
        TextureRegistry.instance().remove_repository(path)
        PreferencesPanel.save_backup_config_file()


    @staticmethod
    def rescan_texture_directory(path: str):
        """
        Rescan the entry.
        """
        TextureRegistry.instance().rescan_repository(path)


    @staticmethod
    def rescan_texture_directories(style: int):
        """
        Rescan entries of given type.
        """
        e: DirPathProperty
        for e in PreferencesPanel.get().texture_dirs:
            if e.style == style:
                TextureRegistry.instance().rescan_repository(e.path)


    @staticmethod
    def update_prefix(path, new_prefix):
        if PreferencesPanel.update_allowed:
            TextureRegistry.instance().update_repository_prefix(path, new_prefix)
            PreferencesPanel.save_backup_config_file()        


    @staticmethod
    def load_backup_config_file(filename: Union[str, None], do_scan: bool = True):
        """
        Load settings from shadow (or dev file).
        """
        if not filename: # None by default, filled in case of development system.
            filename = PreferencesPanel.backup_config_file()
        if filename:
            try:
                PreferencesPanel.update_allowed = False
                data = read_json(filename)
                prefs = PreferencesPanel.get()
                prefs.preview_scale_pie = data.get('preview_scale_pie', 6.0)
                prefs.preview_columns_pie = data.get('preview_columns_pie', 8)
                prefs.preview_scale_side = data.get('preview_scale_side', 8.0)
                prefs.preview_scale_side_popup = data.get('preview_scale_side_popup', 6.0)
                prefs.preview_render_size = data.get('preview_render_size', 512)
                prefs.texture_dirs.clear()
                for d in data['texture_dirs']:
                    prefs.add_texture_directory(d['path'], d['style'], do_scan, d['prefix'])

            except Exception:
                pass # No error
            finally:
                PreferencesPanel.update_allowed = True


    @staticmethod
    def save_backup_config_file():
        """
        Store settings in shadow file, for later restoration.
        """
        prefs = PreferencesPanel.get()
        data = { 
            'preview_scale_pie': prefs.preview_scale_pie,
            'preview_columns_pie': prefs.preview_columns_pie,
            'preview_scale_side': prefs.preview_scale_side,
            'preview_scale_side_popup': prefs.preview_scale_side_popup,
            'preview_render_size': prefs.preview_render_size,
            'texture_dirs': [ { 'path': e.path, 'style': e.style, 'prefix': e.prefix } for e in prefs.texture_dirs ],
        }

        write_json(PreferencesPanel.backup_config_file(), data)


    @staticmethod
    def backup_config_file(): return config_file('config.json')

    
    @staticmethod
    def restore_dev():
        """
        Only used during development (settings get detroyed in IDE usage).
        """
        dev_config = config_file('config-dev.json')
        if os.path.exists(dev_config):
            prefs = PreferencesPanel.get()
            prefs.texture_dirs.clear()
            if not prefs.texture_dirs:
                PreferencesPanel.load_backup_config_file(dev_config, False)


    @staticmethod
    def get_texture_dirs() -> List[Tuple[str, str, int]]: # path, prefix, style
        dirs = PreferencesPanel.get().texture_dirs
        return [ ( d.path, d.prefix, d.style ) for d in dirs ]


    @staticmethod
    def get() -> 'PreferencesPanel':
        return bpy.context.preferences.addons[__package__].preferences
