using UnityEngine;
using System.Collections.Generic;

public static partial class D2D_Helper
{
	public static int MeshVertexLimit = 65000;
	
	public static void GetPixelsBilinear(Texture2D t, int x, int y)
	{
		
	}
	
	public static void Swap<T>(ref T a, ref T b)
	{
		var c = b;
		
		b = a;
		a = c;
	}
	
	public static float Atan2(Vector2 xy)
	{
		return Mathf.Atan2(xy.x, xy.y);
	}
	
	public static void ResizeArrayTo<T>(List<T> array, int size, System.Func<int, T> newT, System.Action<T> removeT)
	{
		if (array != null)
		{
			while (array.Count < size)
			{
				array.Add(newT != null ? newT(array.Count) : default(T));
			}
			
			while (array.Count > size)
			{
				if (removeT != null)
				{
					removeT(array[array.Count - 1]);
				}
				
				array.RemoveAt(array.Count - 1);
			}
		}
	}
	
	private static HideFlags oldHideFlags;
	
	public static void BeginStealthSet(Object o)
	{
		if (o != null)
		{
			oldHideFlags = o.hideFlags;
			
			o.hideFlags = HideFlags.DontSave;
		}
	}
	
	public static void EndStealthSet(Object o)
	{
		if (o != null)
		{
			o.hideFlags = oldHideFlags;
		}
	}
	
	public static void StealthSet(MeshFilter mf, Mesh m)
	{
		if (mf != null && mf.sharedMesh != m)
		{
#if UNITY_EDITOR
			var hf = mf.hideFlags;
			
			mf.hideFlags  = HideFlags.DontSave;
			mf.sharedMesh = m;
			mf.hideFlags  = hf;
#else
			mf.sharedMesh = m;
#endif
		}
	}
	
	public static void StealthSet(MeshRenderer mr, Material m)
	{
		if (mr != null && mr.sharedMaterial != m)
		{
#if UNITY_EDITOR
			var hf = mr.hideFlags;
			
			mr.hideFlags      = HideFlags.DontSave;
			mr.sharedMaterial = m;
			mr.hideFlags      = hf;
#else
			mr.sharedMaterial = m;
#endif
		}
	}
	
	public static void SetPosition(Transform t, Vector3 v)
	{
		if (t != null)
		{
#if UNITY_EDITOR
			if (Application.isPlaying == false && t.position == v) return;
#endif
			t.position = v;
		}
	}
	
	public static void Destroy(Object o)
	{
#if UNITY_EDITOR
		if (Application.isPlaying == false)
		{
			Object.DestroyImmediate(o, true); return;
		}
#endif
		
		Object.Destroy(o);
	}
	
	public static void DestroyManaged(System.Action DestroyAction)
	{
		if (DestroyAction != null)
		{
#if UNITY_EDITOR
			var isPlaying = Application.isPlaying;
		
			UnityEditor.EditorApplication.delayCall += () =>
				{
					if (Application.isPlaying == isPlaying)
					{
						DestroyAction();
					}
				};
#else
			DestroyAction();
#endif
		}
	}
	
	public static void SetParent(Transform t, Transform newParent, bool keepLocalTransform = true)
	{
		if (t != null && t.parent != newParent)
		{
			if (keepLocalTransform == true)
			{
				var oldLocalPosition = t.localPosition;
				var oldLocalRotation = t.localRotation;
				var oldLocalScale    = t.localScale;
				
				t.parent        = newParent;
				t.localPosition = oldLocalPosition;
				t.localRotation = oldLocalRotation;
				t.localScale    = oldLocalScale;
			}
			else
			{
				t.parent = newParent;
			}
		}
	}
	
	public static void SetLocalPosition(Transform t, float x, float y, float z)
	{
		SetLocalPosition(t, new Vector3(x, y, z));
	}
	
	public static void SetLocalPosition(Transform t, Vector3 v)
	{
		if (t != null)
		{
#if UNITY_EDITOR
			if (Application.isPlaying == false && t.localPosition == v) return;
#endif
			t.localPosition = v;
		}
	}
	
	public static void SetLocalScale(Transform t, float v)
	{
		SetLocalScale(t, new Vector3(v, v, v));
	}
	
	public static void SetLocalScale(Transform t, Vector3 v)
	{
		if (t != null)
		{
#if UNITY_EDITOR
			if (Application.isPlaying == false && t.localScale == v) return;
#endif
			if (t.localScale == v) return;
			
			t.localScale = v;
		}
	}
	
	public static T GetComponentUpwards<T>(Transform transform, bool skipFirst = false)
		where T : Component
	{
		if (transform != null)
		{
			if (skipFirst == true)
			{
				transform = transform.parent;
			}
			
			while (transform != null)
			{
				var component = transform.GetComponent<T>();
				
				if (component != null) return component;
				
				transform = transform.parent;
			}
		}
		
		return null;
	}
	
	public static T GetOrAddComponent<T>(GameObject gameObject)
		where T : Component
	{
		if (gameObject != null)
		{
			var component = gameObject.GetComponent<T>();
			
			if (component == null) component = gameObject.AddComponent<T>();
			
			return component;
		}
		
		return null;
	}
	
	public static GameObject CloneGameObject(GameObject source, Transform parent, bool keepName = false)
	{
		if (source != null)
		{
			var clone = (GameObject)GameObject.Instantiate(source); if (clone == null) throw new System.NullReferenceException();
			
			if (parent   != null) SetParent(clone.transform, parent, true);
			if (keepName == true) clone.name = source.name;
			
			return clone;
		}
		
		return source;
	}
	
	public static GameObject CloneGameObject(GameObject source, Transform parent, Vector3 xyz, Quaternion rot, bool keepName = false)
	{
		if (source != null)
		{
			var clone = (GameObject)GameObject.Instantiate(source, xyz, rot); if (clone == null) throw new System.NullReferenceException();
			
			if (parent   != null) SetParent(clone.transform, parent, true);
			if (keepName == true) clone.name = source.name;
			
			return clone;
		}
		
		return source;
	}
	
	public static GameObject CreateGameObject(string name = "", Transform parent = null)
	{
		return CreateGameObject(name, parent, Vector3.zero, Quaternion.identity, Vector3.one);
	}
	
	public static GameObject CreateGameObject(string name, Transform parent, Vector3 position, Quaternion identity, Vector3 scale)
	{
		var gameObject = new GameObject(name);
		
		gameObject.transform.parent        = parent;
		gameObject.transform.localPosition = Vector3.zero;
		gameObject.transform.localRotation = Quaternion.identity;
		gameObject.transform.localScale    = Vector3.one;
		
		return gameObject;
	}
	
	public static T Clone<T>(T o, bool keepName = true)
		where T : Object
	{
		if (o != null)
		{
			var c = (T)Object.Instantiate(o);
			
			if (c != null && keepName == true) c.name = o.name;
			
			return c;
		}
		
		return null;
	}
	
	public static bool Enabled(Behaviour b)
	{
		return b != null && b.enabled == true && b.gameObject.activeInHierarchy == true;
	}
	
	public static float Divide(float a, float b)
	{
		return Zero(b) == false ? a / b : 0.0f;
	}
	
	public static Vector2 Divide(float xA, float yA, float xB, float yB)
	{
		return new Vector2(Divide(xA, xB), Divide(yA, yB));
	}
	
	public static float Reciprocal(float v)
	{
		return Zero(v) == false ? 1.0f / v : 0.0f;
	}
	
	public static Vector2 Reciprocal(Vector2 v)
	{
		return new Vector2(Reciprocal(v.x), Reciprocal(v.y));
	}
	
	public static Vector2 Reciprocal(float x, float y)
	{
		return new Vector2(Reciprocal(x), Reciprocal(y));
	}
	
	public static Vector3 Reciprocal(Vector3 v)
	{
		return new Vector3(Reciprocal(v.x), Reciprocal(v.y), Reciprocal(v.z));
	}
	
	public static Vector3 Reciprocal(float x, float y, float z)
	{
		return new Vector3(Reciprocal(x), Reciprocal(y), Reciprocal(z));
	}
	
	public static bool Zero(float v)
	{
		return Mathf.Approximately(v, 0.0f);
	}
	
	public static Matrix4x4 RotationMatrix(Quaternion q)
	{
		var matrix = Matrix4x4.TRS(Vector3.zero, q, Vector3.one);
		
		return matrix;
	}
	
	public static Matrix4x4 TranslationMatrix(Vector3 xyz)
	{
		return TranslationMatrix(xyz.x, xyz.y, xyz.z);
	}
	
	public static Matrix4x4 TranslationMatrix(float x, float y, float z)
	{
		var matrix = Matrix4x4.identity;
		
		matrix.m03 = x;
		matrix.m13 = y;
		matrix.m23 = z;
		
		return matrix;
	}
	
	public static Matrix4x4 ScalingMatrix(float xyz)
	{
		return ScalingMatrix(xyz, xyz, xyz);
	}
	
	public static Matrix4x4 ScalingMatrix(Vector3 xyz)
	{
		return ScalingMatrix(xyz.x, xyz.y, xyz.z);
	}
	
	public static Matrix4x4 ScalingMatrix(float x, float y, float z)
	{
		var matrix = Matrix4x4.identity;
		
		matrix.m00 = x;
		matrix.m11 = y;
		matrix.m22 = z;
		
		return matrix;
	}
	
	public static float DampenFactor(float dampening, float elapsed)
	{
		return 1.0f - Mathf.Pow((float)System.Math.E, - dampening * elapsed);
	}
	
	public static Quaternion Dampen(Quaternion current, Quaternion target, float dampening, float elapsed, float minStep = 0.0f)
	{
		var factor   = DampenFactor(dampening, elapsed);
		var maxDelta = Quaternion.Angle(current, target) * factor + minStep * elapsed;
		
		return MoveTowards(current, target, maxDelta);
	}
	
	public static float Dampen(float current, float target, float dampening, float elapsed, float minStep = 0.0f)
	{
		var factor   = DampenFactor(dampening, elapsed);
		var maxDelta = Mathf.Abs(target - current) * factor + minStep * elapsed;
		
		return MoveTowards(current, target, maxDelta);
	}
	
	public static Vector3 Dampen3(Vector3 current, Vector3 target, float dampening, float elapsed, float minStep = 0.0f)
	{
		var factor   = DampenFactor(dampening, elapsed);
		var maxDelta = Mathf.Abs((target - current).magnitude) * factor + minStep * elapsed;
		
		return Vector3.MoveTowards(current, target, maxDelta);
	}
	
	public static Quaternion MoveTowards(Quaternion current, Quaternion target, float maxDelta)
	{
		var delta = Quaternion.Angle(current, target);
		
		return Quaternion.Slerp(current, target, Divide(maxDelta, delta));
	}
	
	public static float MoveTowards(float current, float target, float maxDelta)
	{
		if (target > current)
		{
			current = System.Math.Min(target, current + maxDelta);
		}
		else
		{
			current = System.Math.Max(target, current - maxDelta);
		}
		
		return current;
	}
	
	public static Vector3 ClosestPointToLineSegment(Vector3 a, Vector3 b, Vector3 point)
	{
		var l = (b - a).magnitude;
		var d = (b - a).normalized;
		
		return a + Mathf.Clamp(Vector3.Dot(point - a, d), 0.0f, l) * d;
	}
	
	public static Vector3 ClosestPointToTriangle(Vector3 a, Vector3 b, Vector3 c, Vector3 p)
	{
		var r  = Quaternion.Inverse(Quaternion.LookRotation(-Vector3.Cross(a - b, a - c)));
		var ra = r * a;
		var rb = r * b;
		var rc = r * c;
		var rp = r * p;
		
		var a2 = D2D_Helper.VectorXY(ra);
		var b2 = D2D_Helper.VectorXY(rb);
		var c2 = D2D_Helper.VectorXY(rc);
		var p2 = D2D_Helper.VectorXY(rp);
		
		if (PointLeftOfLine(a2, b2, p2) == true)
		{
			return ClosestPointToLineSegment(a, b, p);
		}
		
		if (PointLeftOfLine(b2, c2, p2) == true)
		{
			return ClosestPointToLineSegment(b, c, p);
		}
		
		if (PointLeftOfLine(c2, a2, p2) == true)
		{
			return ClosestPointToLineSegment(c, a, p);
		}
		
		var barycentric = GetBarycentric(a2, b2, c2, p2);
		
		return barycentric.x * a + barycentric.y * b + barycentric.z * c;
	}
	
	public static Vector3 GetBarycentric(Vector2 a, Vector2 b, Vector2 c, Vector2 p)
	{
		var barycentric = Vector3.zero;
		var v0          = b - a;
		var v1          = c - a;
		var v2          = p - a;
		var d00         = Vector2.Dot(v0, v0);
		var d01         = Vector2.Dot(v0, v1);
		var d11         = Vector2.Dot(v1, v1);
		var d20         = Vector2.Dot(v2, v0);
		var d21         = Vector2.Dot(v2, v1);
		var denom       = D2D_Helper.Reciprocal(d00 * d11 - d01 * d01);
		
		barycentric.y = (d11 * d20 - d01 * d21) * denom;
		barycentric.z = (d00 * d21 - d01 * d20) * denom;
		barycentric.x = 1.0f - barycentric.y - barycentric.z;
		
		return barycentric;
	}
	
	public static bool PointLeftOfLine(Vector2 a, Vector2 b, Vector2 p) // NOTE: CCW
	{
		return ((b.x - a.x) * (p.y - a.y) - (p.x - a.x) * (b.y - a.y)) >= 0.0f;
	}
	
	public static bool PointRightOfLine(Vector2 a, Vector2 b, Vector2 p) // NOTE: CCW
	{
		return ((b.x - a.x) * (p.y - a.y) - (p.x - a.x) * (b.y - a.y)) <= 0.0f;
	}
	
	public static Vector2 VectorXY(Vector3 xyz)
	{
		return new Vector2(xyz.x, xyz.y);
	}
}