import bpy
import bmesh
import os
from ..utils import exporter
from ..utils import createTransform
from ..utils import motion_mapper_data_conversion
from .. import utils
from ..cAPI import SimulationAPI
import numpy as np

class HI_PHY_OT_MotionMapperMeshBinding(bpy.types.Operator):
    bl_idname = "hi_phy.motion_mapper_mesh_binding"
    bl_label = "Hi Phy Motion Mapper Mesh Binding"
    bl_options = {'REGISTER', 'UNDO_GROUPED'}
    motion_mapper : bpy.props.StringProperty(
        name='Motion Mapper'
    )
    guide : bpy.props.StringProperty(
        name='Guide Mesh'
    )
    driven : bpy.props.StringProperty(
        name='Driven Mesh'
    )
    binding_radius : bpy.props.FloatProperty(
        name='Binding Radius'
    )
    use_mask : bpy.props.BoolProperty(
        name='Use Mask'
    )
    mask_attribute : bpy.props.StringProperty(
        name='Mask Attribute'
    )
    def execute(self, context):
        motion_mapper_object = context.scene.objects[self.motion_mapper];
        motion_mapper_object.hi_phy.motion_mapper_is_bond = False;
        if (not motion_mapper_object.hi_phy):
            self.report({"ERROR"}, self.motion_mapper + " is not a hi_phy object.")
            return {'CANCELLED'}
        if (not self.guide in context.scene.objects):
            self.report({"ERROR"}, "No valid guide shape to bind")
            return {'CANCELLED'}
        if (not self.driven in context.scene.objects):
            self.report({"ERROR"}, "No valid driven shape to bind")
            return {'CANCELLED'}
        depsgraph = bpy.context.evaluated_depsgraph_get()
        # Guide is evaluated
        guide_orig = context.scene.objects[self.guide]
        guide = guide_orig.evaluated_get(depsgraph)
        if (guide.type != 'MESH'):
            self.report({"ERROR"}, "Guide shape is not a MESH object")
            return {'CANCELLED'}
        # Driven is not evaluated
        driven = context.scene.objects[self.driven]
        if (driven.type != 'MESH'):
            self.report({"ERROR"}, "Driven shape is not a MESH object")
            return {'CANCELLED'}
        motion_mapper = SimulationAPI.MeshMotionMapper();
        utils.mesh_motion_mappers[self.motion_mapper] = motion_mapper;
        # Get the topology of guide
        faces = guide.data.polygons
        n_faces = len(faces);
        # First check it is a triangle mesh
        face_data = np.zeros((n_faces, 3), dtype=np.int32)
        for f in range(0, n_faces):
            if (not len(faces[f].vertices) == 3) :
                self.report({"ERROR"}, "Guide shape is not a Triangke MESH object")
                return {'CANCELLED'}
            face_data[f][0] = faces[f].vertices[0]
            face_data[f][1] = faces[f].vertices[1]
            face_data[f][2] = faces[f].vertices[2]
        if not motion_mapper.SetGuideMeshTriangles(face_data):
            self.report({"ERROR"}, "Failed to set the triangle mesh")
            return {'CANCELLED'}
        n_verts = guide.data.attributes.domain_size('POINT')
        guide_positions = np.zeros((n_verts, 3), dtype=np.float32)
        guide.data.attributes['position'].data.foreach_get('vector', np.ravel(guide_positions))

        identity_transform = SimulationAPI.TransformMatrix();
        # We perform computation in guide space
        if not motion_mapper.SetGuidePosition(guide_positions, identity_transform):
            self.report({"ERROR"}, "Failed to set the guide positions")
            return {'CANCELLED'}
   
        n_verts = driven.data.attributes.domain_size('POINT')
        driven_positions = np.zeros((n_verts, 3), dtype=np.float32)
        driven.data.attributes['position'].data.foreach_get('vector', np.ravel(driven_positions))

        guide_transform = createTransform(guide.matrix_world)        
        driven_transform = createTransform(driven.matrix_world)
        driven_to_guide = guide_transform.Inverse() * driven_transform
        # transfrom the driven shape to the guide space
        if not motion_mapper.SetDrivenPosition(driven_positions, driven_to_guide):
            self.report({"ERROR"}, "Failed to set the driven positions")
            return {'CANCELLED'}
        if not motion_mapper.Bind(self.binding_radius):
            self.report({"ERROR"}, "Failed to bind the mesh")
            return {'CANCELLED'}
        # store the motion mapper data
        motion_mapper_data_conversion.storeMeshMotionMapperDataToBlender(motion_mapper_object, motion_mapper)
        return {'FINISHED'}

__CLASSES__ = [
    HI_PHY_OT_MotionMapperMeshBinding
]

def register():
    for class_ in __CLASSES__:
        bpy.utils.register_class(class_)


def unregister():
    for class_ in reversed(__CLASSES__):
        bpy.utils.unregister_class(class_)
