using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

namespace ImstkUnity
{
    /// <summary>
    /// This component represents connective tissue as a multitude of strands between
    /// opposing surfaces. Given two opposing geometries strands will be generated with
    /// configurable parameters. The generated object is physical and can be interacted
    /// with. The connective tissue will consist of multiple "strands" each going from 
    /// one of the reference objects to the other. Each strand will be made up of the give
    /// number of segments. The amount of strands is roughly NumberOfFaces(ObjectA) * strandsPerFace
    /// Note that increasing the density and/or the number of segments per strand will also
    /// increase the computational load to simulation this object.
    /// </summary>
    [RequireComponent(typeof(MeshFilter))]
    [RequireComponent(typeof(MeshRenderer))]
    public class ConnectiveTissue : ImstkBehaviour
    {

        /// <value>objectA and objectB are the objects that delimit the connective tissue</value>
        public Deformable objectA;
        public Deformable objectB;

        /// <value>
        /// <c>maxDistance</c> represents the maximum distance between A and B where conn
        /// connective tissue will be generated
        /// </value>
        public double maxDistance = 10;

        /// <value>
        /// <c>strandsPerFace</c> indicates the amount of strands total number will be roughly  
        /// NumberOfFaces(ObjectA) * strandsPerFace
        /// </value>
        public double strandsPerFace = 2.0;

        /// <value>
        /// <c>segmentsPerStrand</c> indicates how many subdivisions each strand has
        /// </value>
        public int segmentsPerStrand = 2;

        /// <value>
        /// <c>distanceStiffness</c> how much (or little) give the connective tissue has (0-infinity)
        /// </value>
        public double distanceStiffness = 10;

        private Mesh _mesh;
        private MeshFilter _meshFilter;
        private MeshRenderer _meshRenderer;

        private Imstk.PbdObject _connectiveTissue;
        private bool _needUV = true;

        private void Awake()
        {
            _meshFilter = GetComponent<MeshFilter>();
            _meshRenderer = GetComponent<MeshRenderer>();
        }

        private void Start()
        {

            if (objectA == null || objectB == null)
            {
                Debug.LogError("Connective Tissue needs two objects to span");
            }
        }

        protected override void OnImstkInit()
        {
            objectA.ImstkInit();
            objectB.ImstkInit();

            var aPbdObject = objectA.GetDynamicObject() as Imstk.PbdObject;
            var bPbdObject = objectB.GetDynamicObject() as Imstk.PbdObject;

            _connectiveTissue = Imstk.Utils.makeConnectiveTissue(aPbdObject, bPbdObject, SimulationManager.pbdModel,
                maxDistance, strandsPerFace, segmentsPerStrand);

            var visualGeometry = Imstk.Utils.CastTo<Imstk.PointSet>(_connectiveTissue.getPhysicsGeometry());
            _mesh = new Mesh();
            _mesh.name = "Connective Tissue Mesh (Imstk)";
            _mesh.MarkDynamic();
            GeomUtil.CopyMesh(visualGeometry.ToMesh(), _mesh);
            _meshFilter.mesh = _mesh;

            var config = SimulationManager.pbdModel.getConfig();
            config.enableConstraint(Imstk.PbdModelConfig.ConstraintGenType.Distance, distanceStiffness, 
                _connectiveTissue.getPbdBody().bodyHandle);
            SimulationManager.pbdModel.configure(config);

            // TODO refactor to move to simulation manager
            SimulationManager.sceneManager.getActiveScene().addSceneObject(GetSceneObject());
        }

        public void Update()
        {
            var visualGeometry = Imstk.Utils.CastTo<Imstk.PointSet>(_connectiveTissue.getPhysicsGeometry());
            _mesh.vertices = MathUtil.ToVector3Array(visualGeometry.getVertexPositions());
            if (_mesh.vertexCount > 0 && _needUV)
            {
                GenerateUVAndNormals(_mesh);
            } 
            _mesh.RecalculateBounds();
            UpdateNormals(_mesh);
            _mesh.MarkModified();
        }

        public Imstk.SceneObject GetSceneObject()
        {
            return Imstk.Utils.CastTo<Imstk.SceneObject>(_connectiveTissue);
        }

        private void GenerateUVAndNormals(Mesh mesh)
        {
            // For now keep them regular along one axis of the UV
            var indices = mesh.GetIndices(0);
            var vertices = mesh.vertices;
            var uvs = new Vector2[vertices.Length];
            var normals = new Vector3[vertices.Length];
            var tangents = new Vector4[vertices.Length];

            int pointsPerStrand = segmentsPerStrand + 1;
            for (int strand = 0; strand < mesh.vertices.Length/pointsPerStrand; ++strand)
            {
                float rand = Random.Range(0.1f, 0.9f);
                for (int i = 0; i < pointsPerStrand; ++i)
                {
                    Vector3 dir;
                    var index = strand * pointsPerStrand + i;
                    if (i < pointsPerStrand - 1)
                    {
                        dir = vertices[index + 1] - vertices[index];
                    }
                    else
                    {
                        dir = vertices[index] - vertices[index - 1];
                    }

                    uvs[index] = new Vector2(rand, (float)i / (float)pointsPerStrand);

                    // For now just connect through the origin
                    normals[index] = vertices[index].normalized;

                    // Just a guess for now
                    Vector3 tangent = Vector3.Cross(normals[index], dir).normalized;
                    tangents[index] = new Vector4(tangent.x, tangent.y, tangent.z, -1);
                }
            }
            
            mesh.SetUVs(0,uvs);
            mesh.SetNormals(normals);
            mesh.SetTangents(tangents);
            _needUV = false;
        }

        private void UpdateNormals(Mesh mesh)
        {
            var normals = new List<Vector3>();
            mesh.GetNormals(normals);

            var tangents = new List<Vector4>();
            mesh.GetTangents(tangents);

            int pointsPerStrand = segmentsPerStrand + 1;

            for (int strand = 0; strand < mesh.vertices.Length / pointsPerStrand; ++strand)
            {
                for (int i = 0; i < pointsPerStrand; ++i)
                {
                    Vector3 dir;
                    var index = strand * pointsPerStrand + i;
                    if (i < pointsPerStrand - 1)
                    {
                        dir = mesh.vertices[index + 1] - mesh.vertices[index];
                    }
                    else
                    {
                        dir = mesh.vertices[index] - mesh.vertices[index - 1];
                    }

                    normals[index] = mesh.vertices[index].normalized;

                    // Just a guess for now
                    Vector3 tangent = Vector3.Cross(normals[index], dir).normalized;
                    tangents[index] = new Vector4(tangent.x, tangent.y, tangent.z, -1);
                }
            }
            mesh.SetNormals(normals);
            mesh.SetTangents(tangents);
        }
    }
}