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

import traceback
import bpy

from bpy.types import Operator, UILayout
from bpy.props import StringProperty, BoolProperty, FloatProperty

from ..utils.dev import err, log_exception
from ..utils.blender import is_420_or_gt, set_active_object, SelectionHelper, is_410_or_gt


class ASSET_OT_import_cleaner(Operator):
    """
    Different batch tasks for object and mesh cleanup.
    """
    bl_idname = 'awp.import_cleaner'
    bl_label = ''
    bl_description = ''
    bl_options = {'REGISTER', 'UNDO'}


    # object or mesh
    mode: StringProperty(options={'HIDDEN'}) # type: ignore

    # Object related.
    remove_animation_data: BoolProperty(options={'HIDDEN'}) # type: ignore
    unparent: BoolProperty(options={'HIDDEN'}) # type: ignore
    merge_objects: BoolProperty(options={'HIDDEN'}) # type: ignore

    # Mesh related.
    clear_custom_split_normals: BoolProperty(options={'HIDDEN'}) # type: ignore
    clear_sharp: BoolProperty(options={'HIDDEN'}) # type: ignore
    tris_to_quads: BoolProperty(options={'HIDDEN'}) # type: ignore
    set_auto_smooth: BoolProperty(options={'HIDDEN'}) # type: ignore
    recalculate_normals_outside: BoolProperty(options={'HIDDEN'}) # type: ignore
    limited_dissolve: BoolProperty(options={'HIDDEN'}) # type: ignore
    join_vertices: BoolProperty(options={'HIDDEN'}) # type: ignore

    auto_smooth_angle: FloatProperty(options={'HIDDEN'}) # type: ignore
    join_vertices_distance: FloatProperty(options={'HIDDEN'}) # type: ignore
    limited_dissolve_angle: FloatProperty(options={'HIDDEN'}) # type: ignore


    @classmethod
    def description(cls, context, properties):
        return {
            'objects': 'Run selected cleanup Options on selected Objects',
            'meshes': 'Run selected cleanup Options on selected Meshes',
        }.get(properties.mode, '??')


    def execute(self, context: bpy.types.Context):
        # Must be in object mode.
        if context.mode != 'OBJECT':
            bpy.ops.object.mode_set(mode='OBJECT')

        # Store cursor location.
        old = bpy.context.scene.cursor.location[:]

        with SelectionHelper(True) as sh:
            # Run changes on selected objects.
            if self.mode == 'objects':
                if self.remove_animation_data:
                    try:
                        for o in sh.selected():
                            o.animation_data_clear()
                    except Exception as e:
                        log_exception(e, operator=self, context_msg=f'Failed to clear animation data for object: {o.name}')

                if self.unparent or self.merge_objects:
                    for m in sh.selected(True, 'MESH'):
                        try:
                            if m.parent and m.parent.type == 'EMPTY':
                                sh.select_only(m)
                                bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM')
                        except Exception as e:
                            log_exception(e, operator=self, context_msg=f'Failed to unparent object: {m.name}')

                    # Remove all empties
                    for e in sh.selected(True, 'EMPTY'):
                        bpy.data.objects.remove(e)

                    if self.merge_objects:
                        try:
                            meshes = sh.selected(True, 'MESH')
                            # Required for merging.
                            sh.select_only(meshes[0], True)
                            sh.select_by_type(True, 'MESH')
                            bpy.ops.object.join()
                        except Exception as e:
                            log_exception(e, operator=self, context_msg='Failed to merge objects')

            elif self.mode == 'meshes':
                for m in sh.selected(True, 'MESH'):
                    try: 
                        sh.select_only(m)

                        if self.clear_custom_split_normals:
                            bpy.ops.mesh.customdata_custom_splitnormals_clear()
                        if self.set_auto_smooth and not is_410_or_gt():
                            m.data.use_auto_smooth = True
                            m.data.auto_smooth_angle = self.auto_smooth_angle

                        bpy.ops.object.mode_set(mode='EDIT')
                        bpy.ops.mesh.select_all(action='SELECT')
                        if self.clear_sharp:
                            bpy.ops.mesh.mark_sharp(clear=True)  
                        if self.join_vertices:
                            bpy.ops.mesh.remove_doubles(threshold=self.join_vertices_distance)                               
                        if self.limited_dissolve:
                            bpy.ops.mesh.dissolve_limited(
                                angle_limit=self.limited_dissolve_angle,
                                delimit={'MATERIAL','SEAM','UV','SHARP'}
                            )
                        if self.tris_to_quads:
                            bpy.ops.mesh.tris_convert_to_quads(uvs=True, vcols=True, seam=True, sharp=True, materials=True) 
                        if self.recalculate_normals_outside:
                            bpy.ops.mesh.normals_make_consistent(inside=False)
                        bpy.ops.mesh.select_all(action='DESELECT')
                        bpy.ops.object.mode_set(mode='OBJECT')

                        # In case of the new behavior, we have to call is after the other
                        # mesh operations, as it seems to use sharp edges.
                        if self.set_auto_smooth:
                            # >= 4.1.0 smoothing is an operator now.
                            # > 4.2.0 uses this to insert geometry nodes.
                            if is_420_or_gt():
                                bpy.ops.object.shade_auto_smooth(
                                    use_auto_smooth=True,
                                    angle=self.auto_smooth_angle
                                )
                            elif is_410_or_gt():
                                # This was just in 4.1.0
                                bpy.ops.object.shade_smooth_by_angle(
                                    angle=self.auto_smooth_angle, 
                                    keep_sharp_edges=True
                                )
                    except Exception as e:
                        log_exception(e, operator=self, context_msg='Mesh cleanup failed')

                                        
        # Restore cursor location.
        bpy.context.scene.cursor.location = old

        return {'FINISHED'}


    @staticmethod
    def create_ui_objects(
        l: UILayout, 
        text: str, 
        icon: str, 
        remove_animation_data: bool = False, 
        unparent: bool = False, 
        merge_objects: bool = False
        ):
        operator = l.operator(ASSET_OT_import_cleaner.bl_idname, text=text, icon=icon) # type: ASSET_OT_import_cleaner
        operator.mode = 'objects'
        operator.remove_animation_data = remove_animation_data
        operator.unparent = unparent
        operator.merge_objects = merge_objects


    @staticmethod
    def create_ui_meshes(
        l: UILayout, 
        text: str, 
        icon: str, 
        clear_custom_split_normals: bool = False,
        clear_sharp: bool = False,
        tris_to_quads: bool = False,
        set_auto_smooth: bool = False,
        recalculate_normals_outside: bool = False,
        limited_dissolve: bool = False,
        join_vertices: bool = False,
        auto_smooth_angle: float = 0,
        join_vertices_distance: float = 0,
        limited_dissolve_angle: float = 0,
        ):
        operator = l.operator(ASSET_OT_import_cleaner.bl_idname, text=text, icon=icon) # type: ASSET_OT_import_cleaner
        operator.mode = 'meshes'
        operator.clear_custom_split_normals = clear_custom_split_normals
        operator.clear_sharp = clear_sharp
        operator.tris_to_quads = tris_to_quads
        operator.set_auto_smooth = set_auto_smooth
        operator.recalculate_normals_outside = recalculate_normals_outside
        operator.limited_dissolve = limited_dissolve
        operator.join_vertices = join_vertices
        operator.auto_smooth_angle = auto_smooth_angle
        operator.join_vertices_distance = join_vertices_distance
        operator.limited_dissolve_angle = limited_dissolve_angle

