import bpy
import mathutils
import bpy_extras
from bpy_extras import view3d_utils
from .paintModifier import apply_color
from .constants import image_name, image_width, image_height

class vPainterOperator(bpy.types.Operator):
    """Set brush color based on unmodified surface normal"""
    bl_idname = "paint.virtual_painter"
    bl_label = "Normal-Based Paint Color"
    bl_options = {'REGISTER', 'UNDO'}
    
#    normal_color = bpy.context.scene.normal_color_global
    normal_color = bpy.props.FloatVectorProperty(size=3, default=(1.0, 1.0, 1.0))
    
    _timer = None  # Timer for modal operator

    def invoke(self, context, event):
        if context.area.type == 'VIEW_3D':
            
            # create image buffer            
            if (image_name in bpy.data.images):
                img = bpy.data.images[image_name]
            else:
                img = bpy.data.images.new(name=image_name, width=image_width, height=image_height, alpha=True)
            
            # set buffer active for painting
            bpy.context.tool_settings.image_paint.canvas = img
            
            # add brush callback
            bpy.app.handlers.depsgraph_update_post.append(brushstroke_callback)
            print("callback added")
            
            # start timer
            self._timer = context.window_manager.event_timer_add(0.1, window=context.window)
            
            # start modal
            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "Please run this operator in the 3D View.")
            return {'CANCELLED'}
    
    def modal(self, context, event):
        # vPainter disabled?
        if not context.scene.vPainter_settings.is_vPainter_enabled:
            
            # remove image buffer
            img = bpy.data.images.get(image_name)
            bpy.data.images.remove(img)
            
            # remove brush callback
            if brushstroke_callback in bpy.app.handlers.depsgraph_update_post:
                print("callback removed")
                bpy.app.handlers.depsgraph_update_post.remove(brushstroke_callback)
            
            # stop timer
            context.window_manager.event_timer_remove(self._timer)
            return {'FINISHED'}
        
        if context.area:
            context.area.tag_redraw()
            
        # Stroke starts?
        if event.type == 'LEFTMOUSE' and event.value == 'PRESS' and context.area and context.area.type == 'VIEW_3D':
            print("painting begon")
            self.normal_color = self.get_normal_color(context, event)
            context.scene.normal_color_global = self.normal_color
            print("color retrieved")
            
        return {'PASS_THROUGH'}
    
    def get_normal_color(self, context, event):
        """Compute the normal at the painting location and convert it to a color."""
        rv3d = context.space_data.region_3d
        
        # Find the main 3D region ('WINDOW') in the 3D Viewport
        region = next((r for r in context.area.regions if r.type == 'WINDOW'), None)
        
        # Get mouse position in 3D space
        # Adjust mouse coordinates to match the main region
        mouse_coords = (
            event.mouse_x - region.x,
            event.mouse_y - region.y
        )
        view_vector = bpy_extras.view3d_utils.region_2d_to_vector_3d(region, rv3d, mouse_coords)
        ray_origin = bpy_extras.view3d_utils.region_2d_to_origin_3d(region, rv3d, mouse_coords)
        
        # Ensure there is a selected object
        selected_objects = context.selected_objects
        if not selected_objects:
            self.report({'WARNING'}, "No selected object to perform ray cast on.")
            return mathutils.Vector((1.0, 1.0, 1.0))  # Default white

        # Use the first selected object
        obj = selected_objects[0]
        
        # Perform a ray cast to find the hit location
        if obj.type == 'MESH':
            matrix_world = obj.matrix_world
            matrix_world_inv = matrix_world.inverted()

            # Transform ray origin and direction to local space
            ray_origin_local = matrix_world_inv @ ray_origin
            ray_target_local = matrix_world_inv @ (ray_origin + view_vector)
            ray_direction_local = (ray_target_local - ray_origin_local).normalized()

            # Perform the ray cast in local space
            result, location, normal, index = obj.ray_cast(
                ray_origin_local, ray_origin_local + ray_direction_local * 1000
            )
            print(normal)
            
            if result:
                # Compute the normal in world space
                normal_world = (matrix_world.to_3x3() @ normal).normalized()
                print(normal_world)
                # Map normal components [-1, 1] to [0, 1] for color
                color = (normal + mathutils.Vector((1.0, 1.0, 1.0))) * 0.5 
                print(color)
                return color

        return mathutils.Vector((1.0, 1.0, 1.0))  # Default white if no hit
    
def brushstroke_callback(scene, depsgraph):
    
    active_object = bpy.context.active_object
    tool_settings = bpy.context.tool_settings
    
    # Ensure callback is valid
    if not active_object or not tool_settings:
        return
    elif active_object.mode == 'TEXTURE_PAINT':
        if tool_settings.image_paint:
            print("painting ended")
            apply_color()

def register():
    bpy.utils.register_class(vPainterOperator)
    bpy.types.Scene.normal_color_global = bpy.props.FloatVectorProperty(size=3, default=(1.0, 1.0, 1.0))

def unregister():
    bpy.utils.unregister_class(vPainterOperator)
    del bpy.types.Scene.normal_color_global