caches = {}

def isSimulationObject(obj):
    return obj.hi_phy.is_active and (obj.hi_phy.object_type == "AFFINE_BODY" or
                                     obj.hi_phy.object_type == "ELASTIC_ROD" or
                                     obj.hi_phy.object_type == "CLOTH" or
                                     obj.hi_phy.object_type == "DEFORMABLE_BODY" or
                                     obj.hi_phy.object_type == "SOFT_COLLIDER") ;

from ..cAPI import SimulationAPI
def createTransform(blender_transform):
    transform = SimulationAPI.TransformMatrix();
    transform.SetTransform(
        blender_transform[0][0], blender_transform[0][1], blender_transform[0][2], blender_transform[0][3],
        blender_transform[1][0], blender_transform[1][1], blender_transform[1][2], blender_transform[1][3],
        blender_transform[2][0], blender_transform[2][1], blender_transform[2][2], blender_transform[2][3])
    return transform

import numpy as np
import bmesh
def getSelectedPoints(obj, operator = None):
    if (obj.type == 'CURVES'):
        selection = np.zeros(len(obj.data.points), dtype=bool)
        obj.data.attributes[".selection"].data.foreach_get("value", selection)
        # Get indices of selected points
        selected_indices = np.where(selection)[0].tolist()
    elif (obj.type == 'MESH'):
        bm = bmesh.from_edit_mesh(obj.data);
        if not bm:
            if operator:
                operator.report({"WARNING"}, ("Can not get select points from mesh."))
            return None
        selected_indices = [v.index for v in bm.verts if v.select]
    else:
        if operator:
            operator.report({"ERROR"}, obj.type + " is not supported.")
        return None
    return selected_indices

import bpy
def GetSolvers():
    # Get all the solvers in the current scene
    solvers = []
    for obj in bpy.context.scene.objects:
        if (obj.hi_phy.is_active and (obj.hi_phy.object_type == "LAGRANGIAN_SOLVER" or
                                      obj.hi_phy.object_type == "MPM_SOLVER")):
            solvers.append(obj)
    return solvers

from ..cAPI import SimulationAPI
def getPositionArray(obj, transform = None, operator = None):
    n_points = obj.data.attributes.domain_size('POINT')
    points_data = np.zeros((n_points, 3), dtype=np.float32)
    obj.data.attributes['position'].data.foreach_get('vector', np.ravel(points_data))
    points = SimulationAPI.CreateVector3Array(n_points)
    SimulationAPI.CopyVector3Array(points, points_data)
    if transform:
        T = createTransform(transform)
        SimulationAPI.Utils.TransformPoints(points, T)
    return points

def createMeshBVH(obj, transform = None, radius_array = None, operator = None):
    if (not obj.type == 'MESH') :
        if operator:
            operator.report({"ERROR"}, "Binding mesh " + obj.name + " is not a triangle mesh")
        return None
    faces = obj.data.polygons
    n_faces = len(faces);
    # First check it is a triangle mesh
    for f in range(0, n_faces):
        if (not len(faces[f].vertices) == 3) :
            if operator:
                operator.report({"ERROR"}, obj.name + " is not a triangle mesh")
            return None
    tris = SimulationAPI.CreateIntArray(n_faces * 3)
    for f in range(0, n_faces):
        tris[f * 3 + 0] = faces[f].vertices[0];
        tris[f * 3 + 1] = faces[f].vertices[1];
        tris[f * 3 + 2] = faces[f].vertices[2];

    verts = getPositionArray(obj, transform, operator)

    radius = None;
    if (radius_array):
        if (len(radius_array) != len(verts)):
            if operator:
                operator.report({"ERROR"}, "Radius array does not match the number of verts for " + obj.name)
            return None
        radius = SimulationAPI.FloatArray(radius_array)
    # build bvh
    return SimulationAPI.Utils.CreateMeshBVH(tris, verts, radius)

def createCurvesBVH(obj, transform = None, radius_array = None, operator = None):
    if (not obj.type == 'CURVES') :
        if operator:
            operator.report({"ERROR"}, obj.name + " is not a curves object")
        return None
    n_curves = len(obj.data.curves)
    vert_counts = SimulationAPI.CreateIntArray(n_curves)
    offset = 0
    # blender stores the first offset as 0 there are totally n_curves + 1 offsets
    for i in range(n_curves):
        next_offset = obj.data.curve_offset_data[i + 1].value
        vert_counts[i] = next_offset - offset;
        offset = next_offset

    verts = getPositionArray(obj, transform, operator)

    radius = None;
    if (radius_array):
        if (len(radius_array) != len(verts)):
            if operator:
                operator.report({"ERROR"}, "Radius array does not match the number of verts for " + obj.name)
            return None
        radius = SimulationAPI.FloatArray(radius_array)
    # build bvh
    return SimulationAPI.Utils.CreateCurvesBVH(vert_counts, verts, radius)

def pointBVHProximity(points, bvh, radius, points_transform, mesh_transform, operator = None):
    n_points = len(points);
    found_proximity = SimulationAPI.CreateBoolArray(n_points)
    bind_points_on_mesh = SimulationAPI.CreateVector3Array(n_points)
    bind_face_ids = SimulationAPI.CreateIntArray(n_points)
    bind_barycentric_weights_on_mesh = SimulationAPI.CreateVector3Array(n_points)
    bind_local_coord = SimulationAPI.CreateVector3Array(n_points)
    if not SimulationAPI.Utils.BindPointsToMesh(points, bvh, found_proximity, bind_points_on_mesh, bind_face_ids, bind_barycentric_weights_on_mesh, bind_local_coord, points_transform, mesh_transform, radius):
        if operator:
            operator.report({"ERROR"}, "Point BVH proximity test has failed")
            return False
    return [found_proximity, bind_points_on_mesh, bind_face_ids, bind_barycentric_weights_on_mesh, bind_local_coord]

def force_dirty_scene():
    # Force a scene update
    bpy.context.view_layer.update()
    # Briefly change a setting to force a "dirty" state
    for scene in bpy.data.scenes:
        scene.use_fake_user = True
        scene.use_fake_user = False
