import bpy
from .node_tree import _prefs, _print, BW_TREE_VERSION, BakeWrangler_Operator


# Panel displaying info about recipe version and containing update button
class BakeWrangler_RecipeInfo(bpy.types.Panel):
    '''Panel in node editor to show recipe information'''
    bl_label = "Recipe Info"
    bl_idname = "OBJECT_PT_BW_RecipeInfo"
    bl_space_type = 'NODE_EDITOR'
    bl_region_type = 'UI'
    bl_context = "area"
    bl_category = "Bake Wrangler"
    
    @classmethod
    def poll(cls, context):
        # Only display if the edited tree is of the correct type
        return (context.area and context.area.ui_type == 'BakeWrangler_Tree')

    def draw(self, context):
        tree = context.space_data.node_tree
        layout = self.layout
        if tree is None:
            layout.label(text="No recipe loaded")
            return
        tree_ver = getattr(tree, "tree_version", 0)
        curr_ver = BW_TREE_VERSION
        nodes = len(tree.nodes)
        
        col = layout.column()
        op = col.operator("bake_wrangler.show_log", icon='TEXT')
        op.tree = tree.name
        col.label(text="Recipe version: " + str(tree_ver))
        col.label(text="Add-on version: " + str(curr_ver))
        col.label(text="Nodes: " + str(nodes))
        
        if tree_ver != curr_ver:
            row = col.row()
            if tree_ver > curr_ver:
                row.label(text="Status: Add-on requires update")
            else:
                row.label(text="Status: Recipe requires update")
                op_row = col.row()
                if tree_ver >= 5:
                    op = op_row.operator("bake_wrangler_op.update_recipe", icon='FILE_REFRESH', text="Update Recipe")
                    op.tree = tree.name
                else:
                    op_row.operator("bake_wrangler_op.update_recipe", icon='CANCEL', text="Update Unavailable")
                    op_row.enabled = False
                    
                    
# Panel for automatic cage management tasks
class BakeWrangler_AutoCages(bpy.types.Panel):
    '''Panel in node editor to manage automatic cages'''
    bl_label = "Auto Cages"
    bl_idname = "OBJECT_PT_BW_AutoCages"
    bl_space_type = 'NODE_EDITOR'
    bl_region_type = 'UI'
    bl_context = "area"
    bl_category = "Bake Wrangler"
    
    @classmethod
    def poll(cls, context):
        # Only display if the edited tree is of the correct type
        return (context.area and context.area.ui_type == 'BakeWrangler_Tree')

    def draw(self, context):
        tree = context.space_data.node_tree
        layout = self.layout
        if tree is None:
            layout.label(text="No recipe loaded")
            return
        col = layout.column()
        op = col.operator("bake_wrangler.auto_cage_create")
        op.tree = tree.name
        op = col.operator("bake_wrangler.auto_cage_update")
        op.tree = tree.name
        op = col.operator("bake_wrangler.auto_cage_remove")
        op.tree = tree.name


# Show log file
class BakeWrangler_Operator_ShowLog(BakeWrangler_Operator, bpy.types.Operator):
    '''Show last log created by this recipe'''
    bl_idname = "bake_wrangler.show_log"
    bl_label = "Show Log"
    bl_options = {"REGISTER"}

    # Called either after invoke from UI or directly from script
    def execute(self, context):
        return {'FINISHED'}
        
    # Called from button press, set modifier key states
    def invoke(self, context, event):
        tree = bpy.data.node_groups[self.tree]
        if tree.last_log:
            bpy.ops.screen.area_dupli('INVOKE_DEFAULT')
            open_ed = bpy.context.window_manager.windows[len(bpy.context.window_manager.windows) - 1].screen.areas[0]
            open_ed.type = 'TEXT_EDITOR'
            log = bpy.data.texts.load(tree.last_log)
            open_ed.spaces[0].text = log
            open_ed.spaces[0].show_line_numbers = False
            open_ed.spaces[0].show_syntax_highlight = False
            return {'FINISHED'}
        else:
            self.report({'WARNING'}, "No log file set")
            return {'CANCELLED'}


# Generate auto cages
class BakeWrangler_Operator_AutoCageCreate(BakeWrangler_Operator, bpy.types.Operator):
    '''Create cages in current scene for objects in recipe that don't have a cage set.\nShift-Click to exclude hidden objects'''
    bl_idname = "bake_wrangler.auto_cage_create"
    bl_label = "Generate Cages"
    bl_options = {"REGISTER", "UNDO"}

    # Called either after invoke from UI or directly from script
    def execute(self, context):
        return {'FINISHED'}

    # Called from button press, set modifier key states
    def invoke(self, context, event):
        mod_shift = event.shift
        objs = get_auto_caged(bpy.data.node_groups[self.tree], mod_shift, context)
        if len(objs):
            # Check if cage collection exists and create it if needed
            if 'BW Cages' not in bpy.data.collections.keys():
                bpy.data.collections.new('BW Cages')
            # Check if cage collection is in current scene and link if needed
            if 'BW Cages' not in context.scene.collection.children.keys():
                context.scene.collection.children.link(bpy.data.collections['BW Cages'])
            bw_cages = bpy.data.collections['BW Cages'].objects
            # Create and link cages to the collection for all objects
            for obj in objs:
                if not obj[0].bw_auto_cage:
                    generate_auto_cage(obj[0], obj[1], obj[2], context)
                if obj[0].bw_auto_cage not in bw_cages.values():
                    bw_cages.link(obj[0].bw_auto_cage)
            return {'FINISHED'}
        else:
            self.report({'WARNING'}, "No objects with auto cages found")
            return {'CANCELLED'}


# Update auto cages
class BakeWrangler_Operator_AutoCageUpdate(BakeWrangler_Operator, bpy.types.Operator):
    '''Update cages in current scene for objects in recipe. Overwrites user changes if 'bw_cage' modifier has been removed.\nShift-Click to exclude hidden objects'''
    bl_idname = "bake_wrangler.auto_cage_update"
    bl_label = "Update Cages"
    bl_options = {"REGISTER", "UNDO"}

    # Called either after invoke from UI or directly from script
    def execute(self, context):
        return {'FINISHED'}

    # Called from button press, set modifier key states
    def invoke(self, context, event):
        mod_shift = event.shift
        objs = get_auto_caged(bpy.data.node_groups[self.tree], mod_shift, context)
        if len(objs):
            for obj in objs:
                if obj[0].bw_auto_cage:
                    cage = obj[0].bw_auto_cage
                    # If the modifier is still on the object just change it instead of making a new object
                    if "bw_cage" in cage.modifiers:
                        cage.modifiers["bw_cage"].strength = obj[1]
                        cage.data.auto_smooth_angle = obj[2]
                    elif 'BW Cages' in bpy.data.collections.keys():
                        bpy.data.collections['BW Cages'].objects.unlink(cage)
                        generate_auto_cage(obj[0], obj[1], obj[2], context)
            return {'FINISHED'}
        else:
            self.report({'WARNING'}, "No objects with auto cages found")
            return {'CANCELLED'}


# Remove auto cages
class BakeWrangler_Operator_AutoCageRemove(BakeWrangler_Operator, bpy.types.Operator):
    '''Remove cages in current scene for objects in recipe.\nShift-Click to exclude hidden objects'''
    bl_idname = "bake_wrangler.auto_cage_remove"
    bl_label = "Remove Cages"
    bl_options = {"REGISTER", "UNDO"}

    # Called either after invoke from UI or directly from script
    def execute(self, context):
        return {'FINISHED'}

    # Called from button press, set modifier key states
    def invoke(self, context, event):
        mod_shift = event.shift
        if 'BW Cages' in bpy.data.collections.keys():
            bw_cages = bpy.data.collections['BW Cages'].objects
            objs = context.scene.collection.all_objects
            for obj in objs:
                if obj.bw_auto_cage and (not mod_shift or obj.visible_get()):
                    bw_cages.unlink(obj.bw_auto_cage)
                    obj.bw_auto_cage = None
            if 'BW Cages' in context.scene.collection.children:
                context.scene.collection.children.unlink(bw_cages.id_data)
            return {'FINISHED'}
        else:
            self.report({'WARNING'}, "No objects with auto cages found")
            return {'CANCELLED'}
        

# Return a list of objects that would get a cage auto generated
def get_auto_caged(tree, vis, context):
    nodes = tree.nodes
    objs = []
    for node in nodes:
        if node.bl_idname == 'BakeWrangler_Output_Image_Path':
            objs += node.get_unique_objects('TARGET', for_auto_cage=True)
    # Get a list of all objects in the scene and cull it down to only visible ones
    vl_objs = context.scene.collection.all_objects.values()
    if vis:
        vl_vis = []
        for obj in vl_objs:
            if obj.visible_get() and obj not in vl_vis:
                vl_vis.append(obj)
        vl_objs = vl_vis
    # Return a list of unique objects that are in the scene and visible and would have a cage
    objs_prune = []
    for obj in objs:
        if obj not in objs_prune and obj[0] in vl_objs:
            objs_prune.append(obj)
    return objs_prune


# Create an auto cage for the given mesh
def generate_auto_cage(mesh, cage_exp, smooth, context):
    # Create a copy of the base mesh with modifiers applied to use a the base cage
    cage = mesh.copy()
    cage.data = mesh.data.copy()
    cage.name = mesh.name + '.cage'
    cage.name = mesh.name + '.cage'
    cage.data.materials.clear()
    cage.data.polygons.foreach_set('material_index', [0] * len(cage.data.polygons))
    cage.display_type = 'WIRE'
    if cage not in bpy.data.collections['BW Cages'].objects.values():
        bpy.data.collections['BW Cages'].objects.link(cage)
    if len(cage.modifiers):
        prev_active = bpy.context.view_layer.objects.active
        bpy.context.view_layer.objects.active = cage
        for mod in cage.modifiers:
            if mod.show_render:
                try:
                    bpy.ops.object.modifier_apply(modifier=mod.name)
                except:
                    _print("Error applying modifier '%s' to object '%s'" % (mod.name, mesh.name))
                    bpy.ops.object.modifier_remove(modifier=mod.name)
            else:
                bpy.ops.object.modifier_remove(modifier=mod.name)
        bpy.context.view_layer.objects.active = prev_active
    # Expand cage on normals
    cage_disp = cage.modifiers.new("bw_cage", 'DISPLACE')
    cage_disp.strength = cage_exp
    cage_disp.direction = 'NORMAL'
    cage_disp.mid_level = 0.0
    cage_disp.show_in_editmode = True
    cage_disp.show_on_cage = True
    cage_disp.show_expanded = False
    # Smooth normals and clear sharps
    cage.data.use_auto_smooth = True
    cage.data.auto_smooth_angle = smooth
    for poly in cage.data.polygons:
        poly.use_smooth = True
    for edge in cage.data.edges:
        edge.use_edge_sharp = False
    # Link cage via property on mesh
    mesh.bw_auto_cage = cage


# Classes to register
classes = (
    BakeWrangler_RecipeInfo,
    BakeWrangler_AutoCages,
    BakeWrangler_Operator_ShowLog,
    BakeWrangler_Operator_AutoCageCreate,
    BakeWrangler_Operator_AutoCageUpdate,
    BakeWrangler_Operator_AutoCageRemove,
)


def register():
    # Add pointer to generated cage
    bpy.types.Object.bw_auto_cage = bpy.props.PointerProperty(name="Cage", description="Bake Wrangler auto generated cage", type=bpy.types.Object)
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)


def unregister():
    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)


if __name__ == "__main__":
    register()