using System;
using System.Collections;
using System.Collections.Generic;
//using System.Linq;

//using UnityEditor;
using UnityEngine;
//using UnityEngine.UI;
//using Uhity.Entities;

using URD = UnityEngine.Random;

public class SoftBodyPointData {
	public Vector3 targetPoint, normal;
	public Vector3 point, lastPoint;
	public Vector3 veloicty;
	public Vector3 force;
	public List<int> link;
	public float maxDis = 1;
}

public class SoftBody : MonoBehaviour {
	
	// public Mesh mesh;
	
	public SkinnedMeshRenderer sTarget;
	public MeshFilter  target;
	public MeshFilter  render;
	
	public Mesh tMesh, rMesh;
	
	
	public SoftBodyPointData[] data;
	
	public float 
		spring = 50f,
		drop = 0.5f,
		velocityDamp = 5,
		neighborSpring = 0.25f,
		
		maxDis = 0.15f;
	
	
	private Vector3[] vertices;
	
	// public int[][] link;
	
	// Start is called before the first frame update
	void Start() {
		// tMesh = target.mesh;
		rMesh = render.mesh;
		
		tMesh = new Mesh();
		
		data = InitSBP(rMesh);
		
		vertices = new Vector3[rMesh.vertices.Length];
	}
	
	private SoftBodyPointData[] InitSBP(Mesh mesh) {
		var rt = new SoftBodyPointData[mesh.vertices.Length];
		
		for(int i=0; i<mesh.vertices.Length; i++) {
			rt[i] = new SoftBodyPointData();
			rt[i].point = rt[i].lastPoint = transform.TransformPoint(mesh.vertices[i]);
			rt[i].veloicty = rt[i].force = Vector3.zero;
			rt[i].link = new List<int>();
			rt[i].maxDis = Mathf.Clamp01(mesh.colors[i].r - 0.15f);
		}
		
		for(int i=0; i<mesh.triangles.Length; i+=3) {
			var a = mesh.triangles[i];
			var b = mesh.triangles[i+1];
			var c = mesh.triangles[i+2];
			
			rt[a].link.Add(b);
			rt[a].link.Add(c);
			rt[b].link.Add(a);
			rt[b].link.Add(c);
			rt[c].link.Add(a);
			rt[c].link.Add(b);
		}
		
		return rt;
	}
	
	private void SoftBodyUpdate() {
		sTarget.BakeMesh(tMesh);
	
		Loop(UpdateCurrentPoint);
		Loop(UpdateForce);
		Loop(UpdateVelocity);
		Loop(UpdatePosition);
		Loop(UpdateRealPosition);
		
		Loop(ApplyRenderPoint);
		
		rMesh.vertices = vertices;
		// rMesh.RecalculateNormals();
		// render.mesh = rMesh;
	}
	
	private void UpdateCurrentPoint(int i) {
		data[i].targetPoint = transform.TransformPoint(tMesh.vertices[i]);
		data[i].normal = transform.TransformDirection(tMesh.normals[i]);
	}
	
	private void UpdateForce(int i) {
		var disp = data[i].targetPoint - data[i].lastPoint;
		data[i].force = disp * spring - Vector3.up * drop;
		
		data[i].force += -data[i].veloicty * velocityDamp;
		
		var k = 1f / data[i].link.Count;
		
		for(int j=0; j<data[i].link.Count; j++) {
			data[i].force += data[data[i].link[j]].veloicty * k * neighborSpring;
		}
	}
	
	private void UpdateVelocity(int i) {
		data[i].veloicty += data[i].force * Time.deltaTime;
	}
	
	private void UpdatePosition(int i) {
		data[i].point += data[i].veloicty * Time.deltaTime;
	}
	
	private void UpdateRealPosition(int i) {
		var disp = data[i].targetPoint - data[i].point;
		var dot = Vector3.Dot(data[i].normal, disp.normalized);
		var k = Mathf.Lerp(0.15f, 1, dot/2 + 0.5f);
		
		var change = Vector3.ClampMagnitude(disp, maxDis * data[i].maxDis * k);
		
		if(data[i].maxDis <= 0.05f)
			data[i].point = data[i].targetPoint;
		else
			data[i].point = data[i].targetPoint - change;
		
		data[i].veloicty = (data[i].point - data[i].lastPoint) / Time.deltaTime * data[i].maxDis;
		
		data[i].lastPoint = data[i].point;
	}
	
	private void ApplyRenderPoint(int i) {
		// b[i].position = current[i].point;
		vertices[i] = transform.InverseTransformPoint(data[i].point);
	}
	
	
	private void Loop(Action<int> Process) {
		for(int i=0; i<data.Length; i++) {
			Process(i);
		}
	}
	
	// Update is called once per frame
	void LateUpdate() {
		SoftBodyUpdate();
	}
}
