import bpy
from ..cAPI import SimulationAPI
from ..utils import exporter

dynamic_properties = []

# -------------------------------------------------------------------
# PropertyGroup: Each item in the list
# -------------------------------------------------------------------

class ExpressionAttributeItem(bpy.types.PropertyGroup):
    # The return type and dimension of the attribute is determined by the expression
    expression : bpy.props.StringProperty(
        name="Expression",
    )
    attribute : bpy.props.StringProperty(
        name="Attribute",
    )

class ObjectPointerItem(bpy.types.PropertyGroup):
    object: bpy.props.PointerProperty(
        name="Simulation Object",
        type=bpy.types.Object
    )
    weight: bpy.props.FloatProperty(
        name="Weight",
        default = 1.0
    )

def createDynamicProperty(property):
    if (property.type == SimulationAPI.Property.Type.Invalid):
        print("Unkown property type, skip")
        return None;
    if (property.type == SimulationAPI.Property.Type.Float):
        default_value=property.default_float
        unit = 'NONE'
        subtype='NONE'
        if (property.name == "solverScale"):
            default_value=100.;
        if (property.vector_type == SimulationAPI.VectorType.Model):
            default_value=property.default_float/100.;
            # unit='LENGTH'
            # subtype='DISTANCE'
        return [
            bpy.props.FloatProperty(
                default=default_value,
                name=property.name,
                precision=5,
                description=property.description,
                options=set() if property.is_static else {'ANIMATABLE'},
                unit=unit,
                subtype=subtype
                # todo: subtype
            ), property.tag
        ]
    elif (property.type == SimulationAPI.Property.Type.Int):
        return [bpy.props.IntProperty(
            default=property.default_int,
            name=property.name,
            description=property.description,
            options=set() if property.is_static else {'ANIMATABLE'}
            # todo: subtype
        ), property.tag]
    elif (property.type == SimulationAPI.Property.Type.Bool):
        return [bpy.props.BoolProperty(
            default=property.default_bool,
            name=property.name,
            description=property.description,
            options=set() if property.is_static else {'ANIMATABLE'}
            # todo: subtype
        ), property.tag]
    elif (property.type == SimulationAPI.Property.Type.Vector3):
        v3=property.default_vector3
        if (property.name == "gravityMap" or property.name == "gravity"):
            return [bpy.props.FloatVectorProperty(
                # switcg to z-up
                default=(v3[2], v3[0], v3[1]),
                name=property.name,
                precision=5,
                description=property.description,
                options=set() if property.is_static else {'ANIMATABLE'}
                # todo: subtype
            ), property.tag]
        else:
            return [bpy.props.FloatVectorProperty(
                default=(v3[0], v3[1], v3[2]),
                name=property.name,
                precision=5,
                description=property.description,
                options=set() if property.is_static else {'ANIMATABLE'}
                # todo: subtype
        ), property.tag]
    elif (property.type == SimulationAPI.Property.Type.String):
        return [bpy.props.StringProperty(
            default = property.default_string,
            name = property.name,
            description=property.description,
            options=set() if property.is_static else {'ANIMATABLE'}
            # todo: subtype
        ), property.tag]
    return None;

from ..utils import update_duo_sphere
def updt_grab_constraint_mesh(self, context):
    if not self.is_active or not self.object_type == "CONSTRAINT" or not self.constraint_type == SimulationAPI.ConstraintType.GrabConstraint:
        return;
    if self.grab_constraint_inner_radius > self.grab_constraint_outer_radius:
        self.grab_constraint_inner_radius = self.grab_constraint_outer_radius
    # Grab constraint use constraint_object0 to store itself
    update_duo_sphere.UpdateDuoSphere(self.constraint_object0, self.grab_constraint_inner_radius, self.grab_constraint_outer_radius)

obj_bools = {
    'NONE': "bool_obj_type_none",
    'LAGRANGIAN_SOLVER': "bool_obj_type_solver_lagrangian",
    'AFFINE_BODY': "bool_obj_type_affine_body",
    'CLOTH': "bool_obj_type_cloth",
    'ELASTIC_ROD': "bool_obj_type_elastic_rod",
    'DEFORMABLE_BODY': "bool_obj_type_deformable_body",
    'SOFT_COLLIDER': "bool_obj_type_soft_collider",
    'COLLIDER': "bool_obj_type_collider",
    'CONSTRAINT': "bool_obj_type_constraint",
    'FORCE_FIELD': "bool_obj_type_force_field",
    'MPM_SOLVER': "bool_obj_type_mpm_solver",
    'MPM_MESHLESS_PARTICLES': "bool_obj_type_mpm_meshless_particles",
    'CURVES_MOTION_MAPPER': "bool_obj_type_curves_motion_mapper",
}

def updt_bool_obj_type(hi_phy):
    obj_bool = obj_bools[hi_phy.object_type]
    for key, value in obj_bools.items():
        if value != obj_bool and getattr(hi_phy, value, True):
            setattr(hi_phy, value, False)
    if (not getattr(hi_phy, obj_bool)):
        setattr(hi_phy, obj_bool, True)

def updt_bool_obj_type_none(self, context):
    prop = self
    if self.bool_obj_type_none:
        prop.object_type = 'NONE'
    updt_bool_obj_type(prop)

def updt_bool_obj_type_solver_lagrangian(self,context):
    prop = self
    if self.bool_obj_type_solver_lagrangian:
        prop.object_type = 'LAGRANGIAN_SOLVER'
    updt_bool_obj_type(prop)

def updt_bool_obj_type_affine_body(self,context):
    prop = self
    if self.bool_obj_type_affine_body:
        prop.object_type = 'AFFINE_BODY'
    updt_bool_obj_type(prop)

def updt_bool_obj_type_cloth(self,context):
    prop = self
    if self.bool_obj_type_cloth:
        prop.object_type = 'CLOTH'
    updt_bool_obj_type(prop)

def updt_bool_obj_type_elastic_rod(self,context):
    prop = self
    if self.bool_obj_type_elastic_rod:
        prop.object_type = 'ELASTIC_ROD'
    updt_bool_obj_type(prop)

def updt_bool_obj_type_deformable_body(self,context):
    prop = self
    if self.bool_obj_type_deformable_body:
        prop.object_type = 'DEFORMABLE_BODY'
    updt_bool_obj_type(prop)

def updt_bool_obj_type_soft_collider(self,context):
    prop = self
    if self.bool_obj_type_soft_collider:
        prop.object_type = 'SOFT_COLLIDER'
    updt_bool_obj_type(prop)

def updt_bool_obj_type_collider(self,context):
    prop = self
    if self.bool_obj_type_collider:
        prop.object_type = 'COLLIDER'
    updt_bool_obj_type(prop)

def updt_bool_obj_type_constraint(self,context):
    prop = self
    if self.bool_obj_type_constraint:
        prop.object_type = 'CONSTRAINT'
    updt_bool_obj_type(prop)

def updt_bool_obj_type_force_field(self,context):
    prop = self
    if self.bool_obj_type_force_field:
        prop.object_type = 'FORCE_FIELD'
    updt_bool_obj_type(prop)

def updt_bool_obj_type_mpm_solver(self,context):
    prop = self
    if self.bool_obj_type_mpm_solver:
        prop.object_type = 'MPM_SOLVER'
    updt_bool_obj_type(prop)

def updt_bool_obj_type_mpm_meshless_particles(self,context):
    prop = self
    if self.bool_obj_type_mpm_meshless_particles:
        prop.object_type = 'MPM_MESHLESS_PARTICLES'
    updt_bool_obj_type(prop)

def updt_bool_obj_type_curves_motion_mapper(self,context):
    prop = self
    if self.bool_obj_type_curves_motion_mapper:
        prop.object_type = 'CURVES_MOTION_MAPPER'
    updt_bool_obj_type(prop)

class HiPhyProperties(bpy.types.PropertyGroup):
    bpy_type = bpy.types.Object

    is_active: bpy.props.BoolProperty(default=False)

    # object type
    items = [
        ('NONE', 'None', ''),
        ('LAGRANGIAN_SOLVER', 'Lagrangian Solver', ''),
        ('AFFINE_BODY', 'Affine Body', ''),
        ('CLOTH', 'Cloth', ''),
        ('ELASTIC_ROD', 'Elastic Rod', ''),
        ('DEFORMABLE_BODY', 'Deformable Body', ''),
        ('SOFT_COLLIDER', 'Soft Collider', ''),
        ('COLLIDER', 'Collider', ''),
        ('CONSTRAINT', 'Constraint', ''),
        ('FORCE_FIELD', 'Force Field', ''),
        ('MPM_SOLVER', 'MPM Solver', ''),
        ('MPM_MESHLESS_PARTICLES', 'MPM Meshless Particles', ''),
        ('CURVES_MOTION_MAPPER', 'Curves Motion Mapper', ''),
    ]

    object_type: bpy.props.EnumProperty(
        items=items,
        name='Object Type',
        default='NONE'
    ) #type: ignore

    # ------------ Buttons --------------------
    bool_obj_type_none: bpy.props.BoolProperty(
        name="None",
        description="None",
        default=True,
        update=updt_bool_obj_type_none
    ) #type: ignore

    bool_obj_type_solver_lagrangian: bpy.props.BoolProperty(
        name="Lagrangian Solver",
        description="Lagrangian Solver",
        default=False,
        update=updt_bool_obj_type_solver_lagrangian
    ) #type: ignore

    bool_obj_type_affine_body: bpy.props.BoolProperty(
        name="Affine Body",
        description="Affine Body",
        default=False,
        update=updt_bool_obj_type_affine_body
    ) #type: ignore

    bool_obj_type_cloth: bpy.props.BoolProperty(
        name="Cloth",
        description="Cloth",
        default=False,
        update=updt_bool_obj_type_cloth
    ) #type: ignore

    bool_obj_type_elastic_rod: bpy.props.BoolProperty(
        name="Elastic Rod",
        description="Elastic Rod",
        default=False,
        update=updt_bool_obj_type_elastic_rod
    ) #type: ignore

    bool_obj_type_deformable_body: bpy.props.BoolProperty(
        name="Deformable Body",
        description="Deformable Body",
        default=False,
        update=updt_bool_obj_type_deformable_body
    ) #type: ignore

    bool_obj_type_soft_collider: bpy.props.BoolProperty(
        name="Soft Collider",
        description="Soft Collider",
        default=False,
        update=updt_bool_obj_type_soft_collider
    ) #type: ignore

    bool_obj_type_collider: bpy.props.BoolProperty(
        name="Collider",
        description="Collider",
        default=False,
        update=updt_bool_obj_type_collider
    ) #type: ignore

    bool_obj_type_constraint: bpy.props.BoolProperty(
        name="Constraint",
        description="Constraint",
        default=False,
        update=updt_bool_obj_type_constraint
    ) #type: ignore

    bool_obj_type_force_field: bpy.props.BoolProperty(
        name="Force Field",
        description="Force Field",
        default=False,
        update=updt_bool_obj_type_force_field
    ) #type: ignore

    bool_obj_type_mpm_solver: bpy.props.BoolProperty(
        name="MPM Solver",
        description="MPM Solver",
        default=False,
        update=updt_bool_obj_type_mpm_solver
    ) #type: ignore

    bool_obj_type_mpm_meshless_particles: bpy.props.BoolProperty(
        name="MPM Meshless Particles",
        description="MPM Meshless Particles",
        default=False,
        update=updt_bool_obj_type_mpm_meshless_particles
    ) #type: ignore

    bool_obj_type_curves_motion_mapper: bpy.props.BoolProperty(
        name="Curves Motion Mapper",
        description="Curves Motion Mapper",
        default=False,
        update=updt_bool_obj_type_curves_motion_mapper
    ) #type: ignore


    ###---------- End Buttons -----------------


    mpm_materials = [
        ('SNOW', 'Snow', ''),
        ('SAND', 'Sand', '')
    ]

    mpm_material: bpy.props.EnumProperty(
        items=mpm_materials,
        name='Material',
        default='SNOW'
    )

    cloth_materials = [
        ('UVPANEL', 'UV Panel', ''),
        ('ELASTIC_MEMBRANE', 'Elastic Membrane', '')
    ]

    cloth_material: bpy.props.EnumProperty(
        items=cloth_materials,
        name='Material',
        default='UVPANEL'
    )

    deformable_body_solvers = [
        ('MGPCG', 'MultiGrid', ''),
        ('DPCG', 'Diagonal Preconditioned CG', '')
    ]

    deformable_body_solver: bpy.props.EnumProperty(
        items=deformable_body_solvers,
        name='Deformable Body Solver',
        default='MGPCG'
    )

    deformable_body_materials = [
        ('COROTATED_LINEAR_ELASTIC', 'Corotated Linear Elastic', ''),
        ('STABLE_NEOHOOKEAN', 'Stable Neo Hookean', '')
    ]

    deformable_body_material: bpy.props.EnumProperty(
        items=deformable_body_materials,
        name='Deformable Body Material',
        default='STABLE_NEOHOOKEAN'
    )
    constraint_type: bpy.props.IntProperty(
        default=0,
        name='Constraint type',
    )

    constraint_object0: bpy.props.PointerProperty(
        type=bpy.types.Object,
        name='Constrainted Object 0'
    )

    constraint_object1: bpy.props.PointerProperty(
        type=bpy.types.Object,
        name='Constrainted Object 1'
    )

    solver: bpy.props.PointerProperty(
        type=bpy.types.Object,
        name='Solver'
    )

    rest_shape: bpy.props.PointerProperty(
        type=bpy.types.Object,
        name='Rest Shape'
    )

    use_rest_shape_uv: bpy.props.BoolProperty(
        name="Use Rest Shape UV",
        description="Use Rest Shape UV",
        default=False
    )

    target_shape: bpy.props.PointerProperty(
        type=bpy.types.Object,
        name='Target Shape'
    )

    preload_reference_shape: bpy.props.PointerProperty(
        type=bpy.types.Object,
        name='Preload Reference Shape'
    )

    mesh_bone: bpy.props.PointerProperty(
        type=bpy.types.Object,
        name='Mesh Bone'
    )

    guide_shape: bpy.props.PointerProperty(
        type=bpy.types.Object,
        name='Guide Shape'
    )

    driven_shape: bpy.props.PointerProperty(
        type=bpy.types.Object,
        name='Driven Shape'
    )

    cache_file: bpy.props.StringProperty(
        default='sim_cache/sim_cache.hiphy',
        name='Cache File',
        subtype='FILE_PATH'
    )

    solver_is_exporting : bpy.props.BoolProperty(
        default=False,
        name='Solver Is Exporting',
    )

    in_memeory : bpy.props.BoolProperty(
        default=True,
        name='Simulate In Memory',
    )

    frame_range_simulation_start: bpy.props.IntProperty(
        default=0,
        name='Frame Start',
        options=set()
    )

    frame_range_simulation_end: bpy.props.IntProperty(
        default=50,
        name='Frame End',
        options=set()
    )

    auto_update_constraint : bpy.props.BoolProperty(
        default=True,
        name='Auto update constraint',
    )

    # For force field
    force_field_visualization : bpy.props.BoolProperty(
        default=True,
        name='Force Field Visualization',
    )

    force_field_visualization_x_min : bpy.props.FloatProperty(
        default=-1,
        name="Visualization Range X Min",
        precision=5,
    )

    force_field_visualization_x_max : bpy.props.FloatProperty(
        default=1,
        name="Visualization Range X Max",
        precision=5,
    )

    force_field_visualization_y_min : bpy.props.FloatProperty(
        default=-1,
        name="Visualization Range Y Min",
        precision=5,
    )

    force_field_visualization_y_max : bpy.props.FloatProperty(
        default=1,
        name="Visualization Range Y Max",
        precision=5,
    )

    force_field_visualization_z_min : bpy.props.FloatProperty(
        default=-1,
        name="Visualization Range Z Min",
        precision=5,
    )

    force_field_visualization_z_max : bpy.props.FloatProperty(
        default=1,
        name="Visualization Range Z Max",
        precision=5,
    )

    force_field_visualization_x_slice : bpy.props.IntProperty(
        default=5,
        name="Visualization Number of X Slice",
    )

    force_field_visualization_y_slice : bpy.props.IntProperty(
        default=5,
        name="Visualization Number of Y Slice",
    )

    force_field_visualization_z_slice : bpy.props.IntProperty(
        default=5,
        name="Visualization Number of Z Slice",
    )

    force_field_visualization_scale : bpy.props.FloatProperty(
        default=1,
        name="Visualization Scale",
    )

    force_field_visualization_live_update : bpy.props.BoolProperty(
        default=False,
        name="Live Update",
    )

    force_field_visualization_initialized : bpy.props.BoolProperty(
        default=False,
        name="Initialized",
    )

    force_field_object_list : bpy.props.CollectionProperty(
        type = ObjectPointerItem,
        name = "Apply to Objects"
    )

    force_field_object_index : bpy.props.IntProperty()

    grab_constraint_object_list : bpy.props.CollectionProperty(
        type = ObjectPointerItem,
        name = "Apply to Objects"
    )

    grab_constraint_object_index : bpy.props.IntProperty()

    grab_constraint_apply_to_all : bpy.props.BoolProperty(
        default=True,
        name="Apply to All",
    )

    grab_constraint_inner_radius : bpy.props.FloatProperty(
        default=0,
        min=0,
        name="Inner Radius",
        update=updt_grab_constraint_mesh
    )

    grab_constraint_outer_radius : bpy.props.FloatProperty(
        default=0,
        min=0,
        name="Outer Radius",
        update=updt_grab_constraint_mesh
    )

    expression_attribute_list : bpy.props.CollectionProperty(
        type = ExpressionAttributeItem,
        name = "Expression Attribute"
    )

    expression_attribute_index : bpy.props.IntProperty()

    expression_attribute_live_update : bpy.props.BoolProperty(
        default=False
    )

    constraint_live_update : bpy.props.BoolProperty(
        default=False,
        name="Live Update",
    )


    motion_mapper_binding_radius : bpy.props.FloatProperty(
        default=0.1,
        name="Binding Radius",
    )

    motion_mapper_use_id : bpy.props.BoolProperty(
        default=False,
        name="Use ID",
    )

    motion_mapper_id_attribute : bpy.props.StringProperty(
        name="ID Attribute",
    )

    motion_mapper_use_frame : bpy.props.BoolProperty(
        default=False,
        name="Use Frame",
    )

    motion_mapper_frame_attribute : bpy.props.StringProperty(
        default='hi_phy_frame_normal',
        name="Frame Normal Attribute",
    )

    motion_mapper_binding_radius : bpy.props.FloatProperty(
        default=0.1,
        name="Binding Radius",
    )

    motion_mapper_number_of_guides : bpy.props.IntProperty(
        default=4,
        name="Number of Guides",
    )

    motion_mapper_frame_modes = [
        ('BASE_NORMAL_TWIST', 'Base Normal + Twist', ''),
        ('FRAME_NORMAL', 'Frame Normal', '')
    ]

    motion_mapper_frame_mode : bpy.props.EnumProperty(
        items=motion_mapper_frame_modes,
        name='Frame Mode',
        default='FRAME_NORMAL'
    )

    motion_mapper_base_normal_attribute : bpy.props.StringProperty(
        default='hi_phy_base_normal',
        name="Base Normal",
    )

    motion_mapper_twist_attribute : bpy.props.StringProperty(
        default='hi_phy_twist',
        name="Twist Attribute",
    )

    motion_mapper_dynamic_parting : bpy.props.BoolProperty(
        default=False,
        name="Dynamic Parting",
    )

    motion_mapper_dynamic_parting_power : bpy.props.IntProperty(
        default=4,
        name="Dynamic Parting Power",
    )

    motion_mapper_live_update : bpy.props.BoolProperty(
        default=False,
        name="Live Update",
    )

    solver_affine_body_auto_key_transform : bpy.props.BoolProperty(
        default=False,
        name="Auto Key Affine Body Transform",
    )

    @classmethod
    def register(cls):
        for n in SimulationAPI.properties:
            p = SimulationAPI.properties[n]
            if (p.is_internal) :
                continue;
            global dynamic_properties
            dynamic_properties.append([p.index, n])
            dynamic_property = createDynamicProperty(p)
            if (dynamic_property) :
                setattr(cls, p.name, dynamic_property[0])
                if (p.is_mappable):
                    # also create the mappable related props
                    setattr(cls, p.name + "_use_attribute", bpy.props.BoolProperty(default=False, description="use attribute for this property"))
                    setattr(cls, p.name + "_attribute", bpy.props.StringProperty(default="", description="attribute to use"))
            dynamic_properties.sort(key = lambda x : x[0])

classes = (
    ObjectPointerItem,
    ExpressionAttributeItem,
    HiPhyProperties
)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    props = bpy.props.PointerProperty(type=HiPhyProperties)
    HiPhyProperties.bpy_type.hi_phy = props

def unregister():
    del HiPhyProperties.bpy_type.hi_phy
    for cls in classes:
        bpy.utils.unregister_class(cls)
