/////////////////////////////////////////////////////////////////////////////////////////////////
// GF - Utils
// various utilitary functions
/////////////////////////////////////////////////////////////////////////////////////////////////
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine.Events;

public class GF
{
    /////////////////////////////////////////////////////////////////////////////////////////////////
    // Hierarchy
    /////////////////////////////////////////////////////////////////////////////////////////////////

    // DIRECT FUNCTIONS

    // returns first component found in current object or its full children hierarchy
    // MyComponentClass c = GetComponentInChildren<MyComponentClass>();

    // search for a component in a child with given name down the hierarchy
    public static T FindChildComponent<T>(Component comp, string name="") where T : Component
	{
		Transform child = FindChild(comp.transform, name);
		return child ? child.GetComponent<T>() : null;
	}

	// search for a child with that name down the hierarchy
	public static Transform FindChild(Transform _transform, string _name="")
	{
		Transform child = _transform.Find(_name);
		if(child) 
			return child;
		for(int i=0; i<_transform.childCount; i++)
		{
			child = FindChild(_transform.GetChild(i), _name);
			if(child)
				return child;
		}
		return null;
	}

	/*
	//@UNUSED
	public static List<Transform> GetChildrenList(Transform _transform, string _tag="", string _name="")
	{
		List<Transform> ret = new List<Transform>();
		foreach(Transform child in _transform) // @HM ugly code from net; would it even work?
		{
			bool acceptable = true;
			acceptable &= (_tag=="" || child.gameObject.tag == _tag);
			acceptable &= (_name=="" || string.Compare(child.gameObject.name, _name, true)==0);
			if(acceptable)
				ret.Add(child);
		}
		return ret;
	}
	*/
	
	// including deactivated ones, excluding parent
	public static List<Transform> GetAllChildrenList(Transform _transform, string _tag="", string _name=""/*, bool enabledOnly=true*/)
	{
		List<Transform> ret = new List<Transform>();
		Transform[] ts = _transform.gameObject.GetComponentsInChildren<Transform>();
		if (ts != null)
		{
			foreach (Transform child in ts) 
			{
				if (child != null && child != _transform)
				{
					bool acceptable = true;
					acceptable &= (_tag=="" || child.gameObject.tag == _tag);
					acceptable &= (_name=="" || string.Compare(child.gameObject.name, _name, true)==0);
					//acceptable &= (!enabledOnly || child.gameObject.enabled);
					if(acceptable)
						ret.Add(child);
				}
			}
		}
		return ret;
	}
	
	// sets given tag to all children with the name containing the given _subName (including deactivated ones, excluding parent)
	public static void SetChildrenTagsBySubName(Transform _transform, string _tag, string _subName, bool overwriteTag = false)
	{
		Transform[] ts = _transform.gameObject.GetComponentsInChildren<Transform>();
		if (ts != null)
		{
			foreach (Transform child in ts) 
			{
				if (child != null && child != _transform)
				{
					if(_subName == "" || child.gameObject.name.ToLower().Contains(_subName.ToLower()))
					{
						if(overwriteTag || child.gameObject.tag == "" || child.gameObject.tag == "Untagged")
							child.gameObject.tag = _tag;
					}
				}
			}
		}
	}
	
	public static Transform GetParent(Transform _transform, string _tag="", string _name="")
	{
		bool acceptable = true;
		acceptable &= (_tag=="" || _transform.gameObject.tag == _tag);
		acceptable &= (_name=="" || string.Compare(_transform.gameObject.name, _name, true)==0);
		if(acceptable)
			return _transform;
		else if(_transform.parent)
			return GetParent(_transform.parent, _tag, _name);
		else 
			return null;
	}
	
	public static Bounds GetGroupBound(Transform _transform)
	{
		Bounds combinedBound = new Bounds();
		var renderers = _transform.gameObject.GetComponentsInChildren<Renderer>();
		bool first = true;
		foreach(Renderer render in renderers) 
		{
			if(first)
			{
				combinedBound = render.bounds;
				first = false;
			}
			else
			{
				combinedBound.Encapsulate(render.bounds);
			}
			// Debug.Log(" >> " + render.name + " bs=" + combinedBound.size);
		}
		return combinedBound;
	}
	
	public static void MoveToLayer(Transform _transform, int _layer)
	{
		List<Transform> children = GetAllChildrenList(_transform);
		children.Add(_transform);
		foreach(var child in children)
		{
			child.gameObject.layer = _layer;
		}
	}

	public static void DestroyChildrenComponents(GameObject go, System.Type type)
	{
		Component[] components = go.GetComponentsInChildren(type);
		foreach(var c in components)
			Component.Destroy(c);
	}

/*	// NOTE this one cuts children too
	public static void DestroyChildren(Transform _transform, string _tag = "", string _name = "")
	{
		List<Transform> children = GetAllChildrenList(_transform, _tag, _name);
		foreach(var c in children)
		{
			GameObject.Destroy(c);
		}
	}
*/	

	
	/////////////////////////////////////////////////////////////////////////////////////////////////
	// Camera
	/////////////////////////////////////////////////////////////////////////////////////////////////
	public static void SetCameraAspect(Camera camera, float keepAspect)
	{
		float aspectRatio = Screen.width / ((float)Screen.height);
		float percentage = 1 - (aspectRatio / keepAspect);
		camera.rect = new Rect(0f, (percentage / 2), 1f, (1 - percentage));
	}
	
	/////////////////////////////////////////////////////////////////////////////////////////////////
	// Geometry
	/////////////////////////////////////////////////////////////////////////////////////////////////
	public static int GetMaterialFromTriangleIndex(GameObject obj, int triangleIndex)
	{
		Renderer renderer = obj.GetComponent<Renderer>();
		if (renderer == null || renderer.sharedMaterial == null)
			return -1;
		
		Mesh mesh = obj.GetComponent<MeshFilter>().mesh;
		if(!mesh)
			return -1;
		for(int i=0; i < mesh.subMeshCount; i++) 
		{
			int[] tr = mesh.GetTriangles(i);
			for(int j=0; j < tr.Length; j++) 
			{
				if(tr[j] == triangleIndex) 
					return i;
			}
		}
		
		return -1;
	}

	public static Vector3 GetObjectBoundCenter(GameObject obj)
	{
		Bounds bounds = GetGroupBound(obj.transform);
/*
		Renderer renderer = obj.GetComponent<Renderer>();
		if(! renderer)
			return obj.transform.position;
		Bounds bounds = renderer.bounds;
*/
		return bounds.center;
	}
	
	public static float GetObjectBoundSizeMax(GameObject obj)
	{
		Bounds bounds = GetGroupBound(obj.transform);
/*
		Renderer renderer = obj.GetComponent<Renderer>();
		if(! renderer)
			return 0.0f;
		Bounds bounds = renderer.bounds;
*/
		Vector3 size = bounds.size;
		return Mathf.Max(size.x, Mathf.Max(size.y, size.z));
	}

	/////////////////////////////////////////////////////////////////////////////////////////////////
	// Ray casts
	/////////////////////////////////////////////////////////////////////////////////////////////////

	// custom function for intersecting a ray with a plane (the ray is looking down on the plane)
	// limited to 1000m away from ray origin
	public static Vector3 RayToHorizontalPlane(Ray ray, float planeHeight)
	{
		float h = ray.origin.y - planeHeight;
		float c = Vector3.Dot (ray.direction, -Vector3.up);
		float d = h / c;
		if(d <= 0.0f || d >= 1000.0f) // if inverted (like when looking up), return a far point in plane
		{
			Vector3 dir = ray.direction;
			dir.y = 0.0f;
			return ray.origin + dir * 1000.0f;
		}
		else
		{
			return ray.origin + ray.direction * d;
		}
	}
	
	public static Material GetMaterialFromRayCastHit(RaycastHit hit)
	{
		Renderer renderer = hit.collider.GetComponent<Renderer>();
		MeshCollider meshCollider = hit.collider as MeshCollider;
		
		if(renderer == null || renderer.sharedMaterial == null || meshCollider == null)
			return null;
		
		int materialIdx = -1;
		
		Mesh mesh = meshCollider.sharedMesh;
		int triangleIdx = hit.triangleIndex;
		int lookupIdx1 = mesh.triangles[triangleIdx * 3];
		int lookupIdx2 = mesh.triangles[triangleIdx * 3 + 1];
		int lookupIdx3 = mesh.triangles[triangleIdx * 3 + 2];
		
		int subMeshesNr = mesh.subMeshCount;
		for(int i=0; i<subMeshesNr; i++) 
		{
			int[] tr = mesh.GetTriangles(i);
			for(int j=0; j<tr.Length; j += 3) 
			{
				if(tr[j] == lookupIdx1 && tr[j+1] == lookupIdx2 && tr[j+2] == lookupIdx3) 
				{
					materialIdx = i;
					break;
				}
			}
			if(materialIdx != -1) 
				break;
		}
		
		if (materialIdx != -1) 
			return renderer.materials[materialIdx];
		else
			return null;
	}

	/////////////////////////////////////////////////////////////////////////////////////////////////
	// Lerps and blends
	/////////////////////////////////////////////////////////////////////////////////////////////////

	public static bool BlendValueToTarget(ref float value, float target, float speed = 1.0f)
	{
		if(value < target)
		{
			value += Time.deltaTime * speed;
			if(value > target)
				value = target;
			return true;
		}
		else if(value > target)
		{
			value -= Time.deltaTime * speed;
			if(value < target)
				value = target;
			return true;
		}
		return false;
	}

    public static bool BlendAngleToTarget(ref float value, float target, float speed = 1.0f)
    {
        float dif = GFMath.AngleDif(target, value);
        if (dif > 0)
        {
            dif -= Time.deltaTime * speed;
            if (dif < 0)
                value = target;
            else
                value = target - dif;
            return true;
        }
        else if (dif < 0)
        {
            dif += Time.deltaTime * speed;
            if (dif > 0)
                value = target;
            else
                value = target - dif;
            return true;
        }
        return false;
    }

    public static bool BlendVector3ToTarget(ref Vector3 value, Vector3 target, float speed = 1.0f)
    {
        Vector3 dir = target - value;
        float d = dir.magnitude;
        if(d > 0.0f)
        {
            dir /= d;
            d -= Time.deltaTime * speed;
            if (d < 0.0f)
            {
                d = 0;
                value = target;
                return true;
            }
            else
            {
                value = target - dir * d;
            }
        }
        return false;
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    // Arrays
    /////////////////////////////////////////////////////////////////////////////////////////////////
    public static T[] NewArray<T>(int count, T value)
    {
        T[] a = new T[count];
        for (int i = 0; i < count; i++)
            a[i] = value;
        return a;
    }

    public static T[] ExtendArray<T>(T[] a, int count, T value)
    {
        if (count < a.Length)
        {
            T[] b = new T[count];
            a.CopyTo(b, 0);
            return a;
        }
        else if (count > a.Length)
        {
            T[] b = new T[count];
            a.CopyTo(b, 0);
            for (int i = a.Length; i < count; i++)
                b[i] = value;
            return b;
        }
        else
        {
            return a;
        }
    }
}