
import bpy
from .transform_utils import get_bone_matrix, transform_matrix
from mathutils import Matrix
#from bone_widget import ctx


def rename_shape(shape, bone, prefix=''):
    shape.name = shape.data.name = prefix + bone.name
    #print('Rename Shape', bone, shape.name)

def get_bone_from_shape(shape):
    armatures = [o for o in bpy.context.scene.objects if o.type == 'ARMATURE']
    return next((b for a in armatures for b in a.pose.bones if b.custom_shape is shape), None)

'''
def set_shape(bone, shape):
    shape.matrix_world = get_bone_matrix(bone)
    bone.custom_shape = shape
    ctx.rename_shape(shape, bone)
    ctx.link_to_widget_col(shape)
'''

def get_side(name, fallback=None):
    name = name.lower()
    if name.endswith(('.l', '_l')) or name.startswith(('l.', 'l_')):
        return 'LEFT'
    elif name.endswith(('.r', '_r')) or name.startswith(('r.', 'r_')):
        return 'RIGHT'

    return fallback

def get_flipped_name(name):
    import re

    def flip(match, start=False):
        if not match.group(1) or not match.group(2):
            return
        
        sides = {
            'R' : 'L',
            'r' : 'l',
            'L' : 'R',
            'l' : 'r',
        }

        if start:
            side, sep = match.groups()
            return sides[side] + sep
        else:
            sep, side, num = match.groups()
            return sep + sides[side] + (num or '')

    start_reg = re.compile(r'(l|r)([._-])', flags=re.I)

    if start_reg.match(name):
        flipped_name = start_reg.sub(lambda x: flip(x, True), name)
    else:
        flipped_name = re.sub(r'([._-])(l|r)(\.\d+)?$', flip, name, flags=re.I)

    return flipped_name

def get_flipped_bone(bone):
    flipped_name = get_flipped_name(bone.name)

    if flipped_name == bone.name:
        return
    
    return bone.id_data.pose.bones.get(flipped_name)

def link_to_col(shape, col):
    for c in shape.users_collection:
        c.objects.unlink(shape)

    col.objects.link(shape)

def custom_shape_matrix(bone):
    loc = bone.custom_shape_translation.copy()
    rot = bone.custom_shape_rotation_euler.copy()
    scale = bone.custom_shape_scale_xyz.copy()

    if bone.use_custom_shape_bone_size:
        loc /= bone.bone.length
    else:
        scale /= bone.bone.length

    return Matrix.LocRotScale(loc, rot, scale)

def apply_custom_shape_transform(bone):
    shape = bone.custom_shape

    if not shape:
        return

    if bpy.app.version_string < '3.0.0':
        scale = bone.custom_shape_scale

        if round(scale, 5) == 0:
            return
        
        if not bone.use_custom_shape_bone_size:
            scale /= bone.bone.length

        mat = transform_matrix(scale=(1/scale,)*3)

        shape.data.transform(mat)
        bone.custom_shape_scale = 1

    else:
        mat = custom_shape_matrix(bone)

        scale = (1, 1, 1)
        if round(mat.to_scale().length, 5) == 0:
            loc, rot, scale = mat.decompose()
            mat = Matrix.LocRotScale(loc, rot, (1, 1, 1))
            scale = bone.custom_shape_scale_xyz 

        shape.data.transform(mat)

        bone.custom_shape_translation = (0, 0, 0)
        bone.custom_shape_rotation_euler = (0, 0, 0)
        bone.custom_shape_scale_xyz = scale
    
def get_clean_shape(bone, shape, separate=True, rename=True, 
        col=None, match=True, prefix='', apply_transforms=True):

    #bone.custom_shape = None

    #Remove bone shape if no other bone are using it
    if bone.custom_shape:
        old_bones = get_bones(bone.custom_shape)
        old_bones.remove(bone)

        print('old_bones', old_bones)

        if old_bones:
            rename_shape(bone.custom_shape, old_bones[0], prefix=prefix)
        elif bone.custom_shape != shape:
            bpy.data.objects.remove(bone.custom_shape)

    if separate:
        shape = shape.copy()
        shape.data = shape.data.copy()

    bone.custom_shape = shape
    
    if apply_transforms:
        apply_custom_shape_transform(bone)
        bone.use_custom_shape_bone_size = True

    if match:
        shape.matrix_world = get_bone_matrix(bone)

    if rename:
        rename_shape(shape, bone, prefix=prefix)

    if col:
        link_to_col(shape, col)

    return shape


def get_bones(shape):
    armatures = [o for o in bpy.context.scene.objects if o.type == 'ARMATURE']
    return [b for a in armatures for b in a.pose.bones if b.custom_shape == shape]

def get_bone(shape):
    bones = get_bones(shape)
    if bones:
        return bones[0]

def symmetrize_bone_shape(bone, prefix=None):
    active_side = get_side(bone.name, 'LEFT')

    shape = bone.custom_shape
    flipped_bone = get_flipped_bone(bone)

    if not flipped_bone or not shape:
        return

    if bpy.app.version_string < '3.0.0':
        flipped_bone.custom_shape_scale = b.custom_shape_scale
    else:
        flipped_bone.custom_shape_translation = bone.custom_shape_translation
        flipped_bone.custom_shape_rotation_euler = bone.custom_shape_rotation_euler
        flipped_bone.custom_shape_scale_xyz = bone.custom_shape_scale_xyz

    flipped_shape = flipped_bone.custom_shape

    if flipped_shape:
        flipped_bone.custom_shape = None

        old_bone = get_bone(flipped_shape)
        if old_bone:
            rename_shape(flipped_shape, old_bone, prefix=prefix)
        else:
            bpy.data.objects.remove(flipped_shape)

    flipped_shape = shape.copy()
    flipped_shape.data = flipped_shape.data.copy()
    flipped_shape.data.transform(transform_matrix(scale=(-1, 1, 1)))

    flipped_bone.custom_shape = get_clean_shape(flipped_bone, flipped_shape, rename=True, separate=False, prefix=prefix, apply_transforms=False)


    flipped_bone.use_custom_shape_bone_size = bone.use_custom_shape_bone_size

    for c in shape.users_collection:
        c.objects.link(flipped_bone.custom_shape)
    
    return flipped_bone.custom_shape