import bpy,bmesh
import mathutils
import bpy.types
from . import UTILS_functions, UTILS_icons_manager

class BALLANCE_OT_rail_uv(bpy.types.Operator):
    """Create a UV for rail"""
    bl_idname = "ballance.rail_uv"
    bl_label = "Create Rail UV"
    bl_options = {'UNDO'}

    uv_type: bpy.props.EnumProperty(
        name="Type",
        description="Define how to create UV",
        items=(
            ("POINT", "Point", "All UV will be created in a specific point"),
            ("UNIFORM", "Uniform", "All UV will be created within 1x1"),
            ("SCALE", "Scale", "Give a scale number to scale UV"),
            ("TT", "TT_ReflectionMapping", "The real internal process of Ballance rail")
            ),
    )

    projection_axis: bpy.props.EnumProperty(
        name="Projection axis",
        description="Projection axis",
        items=(
            ("X", "X axis", "X axis"),
            ("Y", "Y axis", "Y axis"),
            ("Z", "Z axis", "Z axis")
            ),
    )

    uv_scale : bpy.props.FloatProperty(
        name="Scale",
        description="The scale of UV",
        min=0.0,
        default=1.0,
    )

    @classmethod
    def poll(self, context):
        return _check_rail_target()

    def invoke(self, context, event):
        wm = context.window_manager
        return wm.invoke_props_dialog(self)

    def execute(self, context):
        if context.scene.BallanceBlenderPluginProperty.material_picker == None:
            UTILS_functions.show_message_box(("No specific material", ), "Lost parameter", UTILS_icons_manager.blender_error_icon)
        else:
            _create_rail_uv(self.uv_type, context.scene.BallanceBlenderPluginProperty.material_picker, self.uv_scale, self.projection_axis)
        return {'FINISHED'}

    def draw(self, context):
        layout = self.layout
        layout.prop(self, "uv_type")
        layout.prop(context.scene.BallanceBlenderPluginProperty, "material_picker")
        if self.uv_type == 'SCALE' or self.uv_type == 'UNIFORM':
            layout.prop(self, "projection_axis")        
        if self.uv_type == 'SCALE':
            layout.prop(self, "uv_scale")

# ====================== method

def _check_rail_target():
    for obj in bpy.context.selected_objects:
        if obj.type != 'MESH':
            continue
        if obj.mode != 'OBJECT':
            continue
        return True
    return False

def _get_distance(iterator):
    is_first_min = True
    is_first_max = True
    max_value = 0.0
    min_value = 0.0

    for item in iterator:
        if is_first_max:
            is_first_max = False
            max_value = item
        else:
            if item > max_value:
                max_value = item
        if is_first_min:
            is_first_min = False
            min_value = item
        else:
            if item < min_value:
                min_value = item

    return max_value - min_value

def _create_rail_uv(rail_type, material_pointer, scale_size, projection_axis):
    objList = []
    ignoredObj = []
    for obj in bpy.context.selected_objects:
        if obj.type != 'MESH':
            ignoredObj.append(obj.name)
            continue
        if obj.mode != 'OBJECT':
            ignoredObj.append(obj.name)
            continue
        if obj.data.uv_layers.active is None:
            # create a empty uv for it.
            obj.data.uv_layers.new(do_init=False)
        
        objList.append(obj)
    
    for obj in objList:
        mesh = obj.data

        # clean it material and set rail first
        obj.data.materials.clear()
        obj.data.materials.append(material_pointer)

        # copy mesh vec for scale or uniform mode
        vecList = mesh.vertices[:]
        real_scale = 1.0
        if rail_type == 'SCALE':
            real_scale = scale_size
        elif rail_type == 'UNIFORM':
            # calc proper scale
            if projection_axis == 'X':
                maxLength = max(
                    _get_distance(vec.co[1] for vec in vecList),
                    _get_distance(vec.co[2] for vec in vecList)
                )
            elif projection_axis == 'Y':
                maxLength = max(
                    _get_distance(vec.co[0] for vec in vecList),
                    _get_distance(vec.co[2] for vec in vecList)
                )
            elif projection_axis == 'Z':
                maxLength = max(
                    _get_distance(vec.co[0] for vec in vecList),
                    _get_distance(vec.co[1] for vec in vecList)
                )
            real_scale = 1.0 / maxLength

        # Blender 3.5 CHANGED: mesh.uv_layers.active.data -> mesh.uv_layers.active.uv
        # .uv -> .vector
        uv_layer = mesh.uv_layers.active.uv
        for poly in mesh.polygons:
            for loop_index in range(poly.loop_start, poly.loop_start + poly.loop_total):
                # get correspond vec index
                index = mesh.loops[loop_index].vertex_index
                if rail_type == 'POINT':
                    # set to 1 point
                    uv_layer[loop_index].vector[0] = 0
                    uv_layer[loop_index].vector[1] = 1
                elif rail_type == 'SCALE' or rail_type == 'UNIFORM':
                    # following xy -> uv scale
                    # 
                    # use Z axis: X->U Y->V
                    # use X axis: Y->U Z->V
                    # use Y axis: X->U Z->V
                    if projection_axis == 'X':
                        uv_layer[loop_index].vector[0] = vecList[index].co[1] * real_scale
                        uv_layer[loop_index].vector[1] = vecList[index].co[2] * real_scale
                    elif projection_axis == 'Y':
                        uv_layer[loop_index].vector[0] = vecList[index].co[0] * real_scale
                        uv_layer[loop_index].vector[1] = vecList[index].co[2] * real_scale
                    elif projection_axis == 'Z':
                        uv_layer[loop_index].vector[0] = vecList[index].co[0] * real_scale
                        uv_layer[loop_index].vector[1] = vecList[index].co[1] * real_scale
                elif rail_type == 'TT':
                    (uv_layer[loop_index].vector[0], uv_layer[loop_index].vector[1]) = _tt_reflection_mapping_compute(
                         vecList[index].co,
                         mesh.loops[loop_index].normal,
                         (0.0, 0.0, 0.0)
                    )

    if len(ignoredObj) != 0:
        UTILS_functions.show_message_box(
            ("Following objects are not processed due to they are not suit for this function now: ", ) + tuple(ignoredObj), 
            "Execution result", UTILS_icons_manager.blender_info_icon
        )

def _tt_reflection_mapping_compute(_point, _n, _refobj):
    # switch blender coord to virtools coord for convenient calc
    point = mathutils.Vector((_point[0], _point[2], _point[1]))
    n = mathutils.Vector((_n[0], _n[2], _n[1])).normalized()
    refobj = mathutils.Vector((_refobj[0], _refobj[2], _refobj[1]))

    p = (refobj - point).normalized()
    b=(((2*(p*n))*n)-p).normalized()
    
    # convert back to blender coord
    return ((b.x + 1.0) / 2.0, -(b.z + 1.0) / 2.0)