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):
        return [
            bpy.props.FloatProperty(
                default=property.default_float,
                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.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
        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;

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', ''),
    ]

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

    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_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'
    )

    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'
    )

    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()

    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",
    )

    @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)
