import bpy
import bmesh
import os
from ..cAPI import SimulationAPI
from .. import utils
from ..utils import create_binding_constraint
from ..utils import getSelectedPoints
import numpy as np

class HI_PHY_OT_ConvertEdgesToBindingConstraint(bpy.types.Operator):
    bl_idname = "hi_phy.convert_edges_to_binding_constraint"
    bl_label = "Hi Phy Convert Edges To Binding Constraint"
    bl_options = {'REGISTER', 'UNDO_GROUPED'}

    binding_simulation_object_1: bpy.props.StringProperty(name="Binding Simulation Object", default="")
    binding_simulation_object_2: bpy.props.StringProperty(name="Binding Simulation Object", default="")
    binding_tolerance: bpy.props.FloatProperty(name="Binding Tolerance", default=1e-4)

    binding_edges: bpy.props.StringProperty(name="Binding Edges Object", default="")
    override_rest_length: bpy.props.BoolProperty(name="Override Restlength")
    rest_length: bpy.props.FloatProperty(name="Rest Length")

    binding_option: bpy.props.EnumProperty(
        name="Binding Option",
        items=[
            ('OP_BindTwoObjects', "Bind Two Simulation Objects Using An Edge Object", "Bind Two Simulation Objects Using An Edge Object"),
            ('OP_BindWithin', "Bind Within A Single Object With Strayed Edges", "Bind Within A Single Object With Strayed Edges"),
        ],
        default='OP_BindWithin')

    def invoke(self, context, event):
        self.binding_simulation_object_1 = ""
        self.binding_simulation_object_2 = ""
        self.binding_edges = ""
        binding_option = "OP_BindWithin"
        for obj in bpy.context.selected_objects:
            if utils.isSimulationObject(obj) and self.binding_simulation_object_1 == "":
                self.binding_simulation_object_1 = obj.name
            elif utils.isSimulationObject(obj) and self.binding_simulation_object_2 == "":
                self.binding_simulation_object_2 = obj.name
            else:
                self.binding_edges = obj.name
        # if we have an edge object but no simulation object 2, we assume user wants to use the edge object to bind the single mesh
        if self.binding_edges != "" and self.binding_simulation_object_2 == "":
            self.binding_simulation_object_2 = self.binding_simulation_object_1

        if (self.binding_simulation_object_1 != "" and self.binding_simulation_object_2 != "" and self.binding_edges != ""):
            self.binding_option = "OP_BindTwoObjects"
        # we only allow bind within in edit mode
        if (bpy.context.object.mode == 'EDIT'):
            self.binding_option = 'OP_BindWithin'
        return context.window_manager.invoke_props_dialog(self, width=600)

    def draw(self, context):
        layout = self.layout
        if (bpy.context.object.mode == 'EDIT'):
            # this should always be true
            if (self.binding_option == 'OP_BindWithin') :
                layout.prop(self, "binding_simulation_object_1")
        if (bpy.context.object.mode == 'OBJECT'):
            layout.prop(self, "binding_option")
            if (self.binding_option == 'OP_BindTwoObjects') :
                layout.prop(self, "binding_simulation_object_1")
                layout.prop(self, "binding_simulation_object_2")
                layout.prop(self, "binding_edges")
                layout.prop(self, "binding_tolerance")
            else:
                layout.prop(self, "binding_simulation_object_1")
        layout.prop(self, "override_rest_length")
        if (self.override_rest_length):
            layout.prop(self, "rest_length")

    def execute(self, context):
        edges = None
        if (bpy.context.object.mode == 'EDIT'):
            # Try to get the selected edges
            obj = bpy.context.edit_object
            mesh = obj.data
            # Get BMesh from edit mode
            bm = bmesh.from_edit_mesh(mesh)
            # List of selected edges
            edges = [e.index for e in bm.edges if e.select]
        bk_mode = bpy.context.object.mode
        bpy.ops.object.mode_set(mode='OBJECT')
        depsgraph = bpy.context.evaluated_depsgraph_get()
        sim_obj_1 = bpy.data.objects[self.binding_simulation_object_1].evaluated_get(depsgraph)
        if (self.binding_option == 'OP_BindTwoObjects') :
            # evaluate it so we can get the correct topology after modifiers
            sim_obj_2 = bpy.data.objects[self.binding_simulation_object_2].evaluated_get(depsgraph)
            edge_obj = bpy.data.objects[self.binding_edges].evaluated_get(depsgraph)
            # check each type
            if sim_obj_1.type == 'MESH' and sim_obj_2.type == 'MESH':
                 if not create_binding_constraint.createMeshMeshWithEdge(sim_obj_1, sim_obj_2, edge_obj,
                                                                         self.binding_tolerance,
                                                                         sim_obj_1.name + "_to_" + sim_obj_2.name + "_binding_constraint",
                                                                         self.override_rest_length,
                                                                         self.rest_length,
                                                                         self):
                     bpy.ops.object.mode_set(mode=bk_mode)
                     return {'CANCELLED'}
            elif sim_obj_1.type == 'CURVES' and sim_obj_2.type == 'CURVES':
                 if not create_binding_constraint.createCurvesCurvesWithEdge(sim_obj_1, sim_obj_2, edge_obj,
                                                                             self.binding_tolerance,
                                                                             sim_obj_1.name + "_to_" + sim_obj_2.name + "_binding_constraint",
                                                                             self.override_rest_length,
                                                                             self.rest_length,
                                                                             self):
                     bpy.ops.object.mode_set(mode=bk_mode)
                     return {'CANCELLED'}
            elif sim_obj_1.type == 'MESH' and sim_obj_2.type == 'CURVES':
                 if not create_binding_constraint.createMeshCurvesWithEdge(sim_obj_1, sim_obj_2, edge_obj,
                                                                           self.binding_tolerance,
                                                                           sim_obj_1.name + "_to_" + sim_obj_2.name + "_binding_constraint",
                                                                           self.override_rest_length,
                                                                           self.rest_length,
                                                                           self):
                     bpy.ops.object.mode_set(mode=bk_mode)
                     return {'CANCELLED'}
            elif sim_obj_1.type == 'CURVES' and sim_obj_2.type == 'MESH':
                 if not create_binding_constraint.createMeshCurvesWithEdge(sim_obj_2, sim_obj_1, edge_obj,
                                                                           self.binding_tolerance,
                                                                           sim_obj_1.name + "_to_" + sim_obj_2.name + "_binding_constraint",
                                                                           self.override_rest_length,
                                                                           self.rest_length,
                                                                           self):
                     bpy.ops.object.mode_set(mode=bk_mode)
                     return {'CANCELLED'}
            else:
                self.report({"ERROR"}, "Only mesh and curves are supported for converting edges to binding constraints")
                bpy.ops.object.mode_set(mode=bk_mode)
                return {'CANCELLED'}
        if (self.binding_option == 'OP_BindWithin') :
            if sim_obj_1.type == 'MESH':
                 if not create_binding_constraint.createMeshWithinWithEdge(sim_obj_1, edges,
                                                                           sim_obj_1.name + "_to_" + sim_obj_1.name + "_binding_constraint",
                                                                           self.override_rest_length,
                                                                           self.rest_length,
                                                                           self):
                     bpy.ops.object.mode_set(mode=bk_mode)
                     return {'CANCELLED'}
            else:
                bpy.ops.object.mode_set(mode=bk_mode)
                self.report({"ERROR"}, "Only meshes support use internal edges to bind with itself")
                return {'CANCELLED'}

        bpy.ops.object.mode_set(mode=bk_mode)
        return {'FINISHED'}

__CLASSES__ = [
    HI_PHY_OT_ConvertEdgesToBindingConstraint
]

# Define the menu draw function
def menu_draw(self, context):
    # TODO: only add to the menu if the edited object is a valid simulation object
    layout = self.layout
    layout.operator_context = 'INVOKE_DEFAULT'
    layout.operator("hi_phy.create_binding_constraint")

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

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