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

#if UNITY_EDITOR

using UnityEditor;

#endif

public static class GameObjectHelper
{

    public static List<MonoBehaviour> GetMonoBehavioursByInterface<T>() {
        List<MonoBehaviour> returnList = new List<MonoBehaviour>();
        MonoBehaviour[] monoBehaviours = GameObject.FindObjectsOfType<MonoBehaviour>();
        for (int i = 0; i < monoBehaviours.Length; i++) {
            if (monoBehaviours[i] is T) {
                returnList.Add(monoBehaviours[i]);
            }
        }
        return returnList;
    }


    //--------------------------------------- AA BOUNDING BOXES --------------------------------------
    //--------------------------------------- AA BOUNDING BOXES --------------------------------------
    #region AA BOUNDING BOXES

    //-------------- OVERLOADS ---------------
    public static Bounds GetAABoundingBox(GameObject from, bool recursive = true) {
        return CalcAABoundingBox(from, null, recursive);
    }

    public static Bounds GetAABoundingBox(GameObject from, GameObject exclude, bool recursive = true) {
        List<GameObject> excludeList = new List<GameObject>() { exclude };
        return CalcAABoundingBox(from, excludeList, recursive);
    }
    public static Bounds GetAABoundingBox(GameObject from, List<GameObject> excludeList, bool recursive = true) {
        return CalcAABoundingBox(from, excludeList, recursive);
    }

    public static Bounds GetAABoundingBox(GameObject from, GameObject[] excludeArray, bool recursive = true) {
        List<GameObject> excludeList = new List<GameObject>();
        excludeList.AddRange(excludeArray);
        return CalcAABoundingBox(from, excludeList, recursive);
    }
    //-------------- OVERLOADS ---------------




    //-------------- EXECUTION ---------------
    private static Bounds CalcAABoundingBox(GameObject from, List<GameObject> excludeList, bool recursive = true) {

        if (from == null)
            return new Bounds();

        Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
        Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

        Vector3[] worldPositions = GetAllVerticesWorldSpace(from, excludeList, recursive);
        if (worldPositions == null || worldPositions.Length == 0)
            return new Bounds();

        int i = worldPositions.Length;
        while (--i > -1) {
            Vector3 worldPos = worldPositions[i];
            min.x = Mathf.Min(min.x, worldPos.x); min.y = Mathf.Min(min.y, worldPos.y); min.z = Mathf.Min(min.z, worldPos.z);
            max.x = Mathf.Max(max.x, worldPos.x); max.y = Mathf.Max(max.y, worldPos.y); max.z = Mathf.Max(max.z, worldPos.z);
        }

        return new Bounds((min + max) / 2f, max - min);
    }
    //-------------- EXECUTION --------------- 


    #endregion
    //--------------------------------------- AA BOUNDING BOXES --------------------------------------
    //--------------------------------------- AA BOUNDING BOXES --------------------------------------















    //--------------------------------------- GET TRANSFORMED VERTICES WORLD SPACE --------------------------------------
    //--------------------------------------- GET TRANSFORMED VERTICES WORLD SPACE --------------------------------------
    #region GET TRANSFORMED VERTICES WORLD SPACE

    /// <summary>
    /// TODO Unity4.3 See how we can grab vertices from combined static meshes!
    /// </summary>
    /// <param name="from"></param>
    /// <param name="excludeList"></param>
    /// <returns></returns>
    public static Vector3[] GetAllVerticesWorldSpace(GameObject from, List<GameObject> excludeList = null, bool recursive = true) {
        //lelijk, maar heb haast.
        MeshFilter[] filters = recursive ?
            (MeshFilter[])from.GetComponentsInChildren<MeshFilter>(true) :
            new MeshFilter[] { from.GetComponent<MeshFilter>() };


        if (filters.Length == 0)
            return new Vector3[0];

        List<MeshFilter> nonStatics = new List<MeshFilter>(filters);
        Mesh[] meshes = new Mesh[nonStatics.Count];
        //--------------- Get total vertice count --------------------
        int count = 0;
        int i = nonStatics.Count;
        while (--i > -1) {
            if (Application.isPlaying && (nonStatics[i].gameObject.isStatic))// || nonStatics[i].mesh == null || nonStatics[i].mesh.name.Contains("Combined Mesh")))
            {
                nonStatics[i] = null;
            } else if (excludeList != null && excludeList.Contains(filters[i].gameObject)) {
                nonStatics[i] = null;
            } else {
                Mesh mesh = nonStatics[i].sharedMesh;
                if (mesh != null) {
                    meshes[i] = mesh;
                    count += mesh.vertexCount;
                } else {
                    nonStatics[i] = null;
                }
            }
        }
        //--------------- Get total vertice count --------------------


        //--------------- Transform vertices --------------------
        Vector3[] result = new Vector3[count];
        int v = 0;
        for (i = 0; i < nonStatics.Count; i++) {
            MeshFilter filter = nonStatics[i];
            if (filter == null)
                continue;

            if (excludeList != null && excludeList.Contains(filter.gameObject))
                continue;

            Matrix4x4 itemMatrix = filter.transform.localToWorldMatrix;
            Vector3[] vertices = meshes[i].vertices;

            int j = vertices.Length;
            while (--j > -1) {
                Vector3 pos = itemMatrix.MultiplyPoint(vertices[j]);
                result[v++] = pos;
            }
        }
        //--------------- Transform vertices --------------------

        return result;
    }


    #endregion
    //--------------------------------------- GET TRANSFORMED VERTICES WORLD SPACE --------------------------------------
    //--------------------------------------- GET TRANSFORMED VERTICES WORLD SPACE --------------------------------------
			








    //--------------------------------------- GET TRANSFORMED VERTICES WORLD SPACE --------------------------------------
    //--------------------------------------- GET TRANSFORMED VERTICES WORLD SPACE --------------------------------------

    #region GET TRANSFORMED VERTICES WORLD SPACE

    public static Vector3[] GetAllVerticesWorldSpace(GameObject from, List<GameObject> excludeList = null) {
        MeshFilter[] filters = (MeshFilter[])from.GetComponentsInChildren<MeshFilter>();

        //--------------- Get total verice count --------------------
        int count = 0;
        for (int i = 0; i < filters.Length; i++) {
            if (excludeList != null && excludeList.Contains(filters[i].gameObject))
                continue;

            if(filters[i].sharedMesh != null)
                count += filters[i].sharedMesh.vertexCount;
        }
        //--------------- Get total verice count --------------------

        //--------------- Transform vertices --------------------
        Vector3[] result = new Vector3[count];
        int v = 0;
        for (int i = 0; i < filters.Length; i++) {
            MeshFilter filter = filters[i];
            if (filter.sharedMesh != null) {
                if (excludeList != null && excludeList.Contains(filter.gameObject))
                    continue;

                Matrix4x4 itemMatrix = filter.transform.localToWorldMatrix;
                Vector3[] vertices = filter.sharedMesh.vertices;

                int j = vertices.Length;
                while (--j > -1)
                    result[v++] = itemMatrix.MultiplyPoint(vertices[j]);
            }
        }
        //--------------- Transform vertices --------------------

        return result;
    }

    #endregion GET TRANSFORMED VERTICES WORLD SPACE

    //--------------------------------------- GET TRANSFORMED VERTICES WORLD SPACE --------------------------------------
    //--------------------------------------- GET TRANSFORMED VERTICES WORLD SPACE --------------------------------------













    //--------------------------------------- SEARCH CHILDREN --------------------------------------
    //--------------------------------------- SEARCH CHILDREN --------------------------------------

    #region SEARCH CHILDREN

    public static T GetComponentInChildren<T>(this GameObject go, bool includeInactive) where T: Component {
        if (go == null)
            return default(T);

        if (!includeInactive)
            return go.GetComponentInChildren<T>();

        T[] results = go.GetComponentsInChildren<T>(true);
        return results.Length > 0 ? results[0] : default(T);
    }


    public static GameObject FindChildByName(this GameObject go, string name) {
        if (go == null)
            return null;

        Transform[] results = go.GetComponentsInChildren<Transform>(true);
        for (int i = 0; i < results.Length; i++) {
            if (results[i].name.Equals(name)) {
                return results[i].gameObject;
            }
        }
        return null;
    }


    public static List<GameObject> FindChildrenByName(this GameObject go, string name) {
        if (go == null)
            return null;

        Transform[] results = (Transform[])go.GetComponentsInChildren<Transform>();
        List<GameObject> childrenWithName = new List<GameObject>();
        for (int i = 0; i < results.Length; i++) {
            if (results[i].name.Equals(name)) {
                childrenWithName.Add(results[i].gameObject);
            }
        }
        return childrenWithName;
    }

    public static Transform FindChild(Transform findIn, string name, bool strictMatch = false) {
        return FindChild(findIn, new string[] { name }, strictMatch);
    }

    public static Transform FindChild(Transform findIn, string[] names, bool strictMatch = false) {
        foreach (string name in names) {
            Transform result = findIn.Find(name);
            if (result != null)
                return result;

            //Als een directe hit niet gemaakt is, gaan we naar een match zoeken
            if (!strictMatch) {
                string nameLower = name.ToLower();
                foreach (Transform child in findIn) {
                    if (child.name.ToLower().IndexOf(nameLower) != -1)
                        return child;
                }
            }

            //Als er niets gevonden is komt ie hier; hier kijken we naar alle subchildren
            foreach (Transform child in findIn) {
                result = FindChild(child, names, strictMatch);
                if (result != null)
                    return result;
            }
        }

        return null;
    }

    public static Transform[] FindChildren(Transform findIn, string name, bool strictMatch = false) {
        return FindChildren(findIn, new string[] { name }, strictMatch);
    }

    public static Transform[] FindChildren(Transform findIn, string[] names, bool strictMatch = false) {
        List<Transform> result = new List<Transform>();
        if (strictMatch) {
            foreach (Transform child in findIn) {
                foreach (string name in names) {
                    if (child.name == name)
                        result.Add(child);
                }

                result.AddRange(FindChildren(child, names, strictMatch));
            }
        } else {
            foreach (Transform child in findIn) {
                foreach (string name in names) {
                    string nameLower = name.ToLower();
                    if (child.name.ToLower().IndexOf(nameLower) != -1)
                        result.Add(child);
                }

                result.AddRange(FindChildren(child, names, strictMatch));
            }
        }

        return result.ToArray();
    }

    public static Transform[] GetAllChildren(Transform from) {
        List<Transform> result = new List<Transform>();
        Transform trans = from.transform;

        int i = trans.childCount;
        while (--i > -1) {
            Transform child = trans.GetChild(i);
            result.Add(child);
            result.AddRange(GetAllChildren(child));
        }

        return result.ToArray();
    }

    #endregion SEARCH CHILDREN

    //--------------------------------------- SEARCH CHILDREN --------------------------------------
    //--------------------------------------- SEARCH CHILDREN --------------------------------------











    //--------------------------------------- SEARCH CHILDREN COMPONENTS --------------------------------------
    //--------------------------------------- SEARCH CHILDREN COMPONENTS --------------------------------------

    #region SEARCH CHILDREN COMPONENTS

    public static T FindChildComponentByName<T>(this GameObject go, string name) where T : Component {
        Component[] results = go.GetComponentsInChildren(typeof(T));
        for (int i = 0; i < results.Length; i++) {
            if (results[i].name.Equals(name)) {
                return (T)results[i].GetComponent(typeof(T));
            }
        }
        return null;
    }

    #endregion SEARCH CHILDREN COMPONENTS

    //--------------------------------------- SEARCH CHILDREN COMPONENTS --------------------------------------
    //--------------------------------------- SEARCH CHILDREN COMPONENTS --------------------------------------









    //--------------------------------------- SET HIDEFLAGS --------------------------------------
    //--------------------------------------- SET HIDEFLAGS --------------------------------------
    #region SET HIDEFLAGS

    public static void SetHideFlagsRecursively(GameObject gameObject) {
        if (gameObject == null)
            return;
        SetHideFlagsRecursively(gameObject.transform, HideFlags.HideAndDontSave);
    }

    public static void SetHideFlagsRecursively(Transform transform) {
        SetHideFlagsRecursively(transform, HideFlags.HideAndDontSave);
    }

    public static void SetHideFlagsRecursively(Transform transform, HideFlags hideFlags) {
        if (transform == null)
            return;

        GameObject go = transform.gameObject;
        go.hideFlags = hideFlags;
        foreach (Transform child in transform)
            SetHideFlagsRecursively(child, hideFlags);

        Component[] components = go.GetComponents<Component>();
        foreach (Component comp in components)
            comp.hideFlags = hideFlags;
    }

    #endregion
    //--------------------------------------- SET HIDEFLAGS --------------------------------------
    //--------------------------------------- SET HIDEFLAGS --------------------------------------
			






    
    
    //--------------------------------------- SEARCH PARENTS --------------------------------------
    //--------------------------------------- SEARCH PARENTS --------------------------------------
    #region SEARCH PARENTS

    public static T GetComponentFromParent<T>(GameObject source) where T : MonoBehaviour {
        T result = source.GetComponent<T>();
        if (result != null)
            return result;

        Transform current = source.transform;
        do {
            result = current.GetComponent<T>();
            if (result != null)
                return result;

            current = current.parent;
        } while (current != null);

        return default(T);
    }

    #endregion
    //--------------------------------------- SEARCH PARENTS --------------------------------------
    //--------------------------------------- SEARCH PARENTS --------------------------------------
			











    //--------------------------------------- FIND HIDDEN GAMEOBJECTS --------------------------------------
    //--------------------------------------- FIND HIDDEN GAMEOBJECTS --------------------------------------

    #region FIND HIDDEN GAMEOBJECTS

    /// <summary>
    /// Safely Finds hidden objects using Resources.FindObjectsOfTypeAll.
    /// NOTE: Resources.FindObjectsOfTypeAll is NOT SAFE, because it also returns internal/non-scene gameobject (Like prefabs living in the Inspector!)
    /// Those internal/non-scene GameObjects will be filtered!
    /// </summary>
    public static T[] FindInActiveObjectsOfType<T>(Transform parent = null) where T : MonoBehaviour {
        List<T> result = new List<T>();
        T[] resources = (T[])Resources.FindObjectsOfTypeAll(typeof(T));

        foreach (T resource in resources) {
            bool parentFound = parent == null;
            while (parent != null) {
                if (resource.transform == parent) {
                    parentFound = true;
                    break;
                } else {
                    parent = parent.parent;
                }
            }
            if (parentFound) {
#if UNITY_EDITOR
                if (PrefabUtility.GetPrefabObject(resource.gameObject) == null)
                    result.Add(resource);
#else
                    result.Add(resource);
#endif
            }
        }

        return result.ToArray();
    }

    /// <summary>
    /// Safely Finds hidden objects using Resources.FindObjectsOfTypeAll.
    /// NOTE: Resources.FindObjectsOfTypeAll is NOT SAFE, because it also returns internal/non-scene gameobject (Like prefabs living in the Inspector!)
    /// Those internal/non-scene GameObjects will be filtered!
    /// </summary>
    public static T FindInActiveObjectOfType<T>(Transform parent = null) where T : MonoBehaviour {
        //Lets try the fast unsafe version first... Incase it finds something, we don't need to use the relative expensive "FindHiddenObjectsOfType"
        T result = (T)GameObject.FindObjectOfType(typeof(T));
        if (result)
            return result;

        //If the normal "GameObject.FindObjectOfType" could not find it, its probably hidden!
        T[] results = FindInActiveObjectsOfType<T>(parent);
        return results.Length > 0 ? results[results.Length - 1] : null; //Return the last in the array. If arraylength > 1,  those whom where instantiated most recently are those in the scene, not in the Inpector/interal!
    }



    public static GameObject[] FindGameObjectsByHideFlags(HideFlags hideFlags) {
        Transform[] allTransforms = Resources.FindObjectsOfTypeAll<Transform>();
        Transform[] filteredTransforms = Array.FindAll(allTransforms, (item => item.gameObject.hideFlags == hideFlags));
        GameObject[] filteredGameObjects = new GameObject[filteredTransforms.Length];

        for (int i = 0; i < filteredTransforms.Length; i++)
            filteredGameObjects[i] = filteredTransforms[i].gameObject;

        return filteredGameObjects;
    }
    #endregion FIND HIDDEN GAMEOBJECTS

    //--------------------------------------- FIND HIDDEN GAMEOBJECTS --------------------------------------
    //--------------------------------------- FIND HIDDEN GAMEOBJECTS --------------------------------------













    //--------------------------------------- FIND CHILDREN BY TAG --------------------------------------
    //--------------------------------------- FIND CHILDREN BY TAG --------------------------------------

    #region FIND CHILDREN BY TAG

    public static Transform FindChildByTag(Transform findIn, string tag, bool strictMatch = false) {
        return FindChildByTag(findIn, new string[] { tag }, strictMatch);
    }

    public static Transform FindChildByTag(Transform findIn, string[] tags, bool strictMatch = false) {
        foreach (string tag in tags) {
            //Als een directe hit niet gemaakt is, gaan we naar een match zoeken
            if (!strictMatch) {
                string tagLower = tag.ToLower();
                foreach (Transform child in findIn) {
                    if (child.tag.ToLower().IndexOf(tagLower) != -1)
                        return child;
                }
            }

            //Als er niets gevonden is komt ie hier; hier kijken we naar alle subchildren
            foreach (Transform child in findIn) {
                Transform result = FindChildByTag(child, tags, strictMatch);
                if (result != null)
                    return result;
            }

        }

        return null;
    }

    public static Transform[] FindChildrenByTag(Transform findIn, string tag, bool strictMatch = false) {
        return FindChildrenByTag(findIn, new string[] { tag }, strictMatch);
    }

    public static Transform[] FindChildrenByTag(Transform findIn, string[] tags, bool strictMatch = false) {
        List<Transform> result = new List<Transform>();
        if (strictMatch) {
            foreach (Transform child in findIn) {
                foreach (string tag in tags) {
                    if (child.CompareTag(tag))
                        result.Add(child);
                }

                result.AddRange(FindChildrenByTag(child, tags, strictMatch));
            }
        } else {
            foreach (Transform child in findIn) {
                foreach (string tag in tags) {
                    string tagLower = tag.ToLower();
                    if (child.tag.ToLower().IndexOf(tagLower) != -1)
                        result.Add(child);
                }

                result.AddRange(FindChildrenByTag(child, tags, strictMatch));
            }
        }

        return result.ToArray();
    }

    #endregion FIND CHILDREN BY TAG

    //--------------------------------------- FIND CHILDREN BY TAG --------------------------------------
    //--------------------------------------- FIND CHILDREN BY TAG --------------------------------------
















    //--------------------------------------- MISC --------------------------------------
    //--------------------------------------- MISC --------------------------------------

    #region MISC

    public static Transform AddChildContainer(Transform addTo, string newName) {
        Transform child = FindChild(addTo, newName, true);
        if (child == null) {
            GameObject childGO = new GameObject(newName);
            child = childGO.transform;
            child.parent = addTo;
            child.rotation = Quaternion.identity;
            child.position = Vector3.zero;
        }
        return child;
    }

    public static Transform[] SortChildren(Transform parent) {
        int iMax = parent.childCount;
        Transform[] children = new Transform[iMax];
        for (int i = 0; i < iMax; i++)
            children[i] = parent.GetChild(i);

        return SortChildren(children);
    }

    public static Transform[] SortChildren(Transform[] children) {
        Transform[] sortedTransforms = new Transform[children.Length];
        List<string> sortedNames = new List<string>(children.Length);

        foreach (Transform child in children)
            sortedNames.Add(child.name + child.GetInstanceID());

        sortedNames.Sort();

        for (int i = 0; i < sortedNames.Count; i++) {
            for (int j = 0; j < children.Length; j++) {
                Transform child = children[j];
                if (child != null && child.name + child.GetInstanceID() == sortedNames[i]) {
                    sortedTransforms[i] = child;
                    children[j] = null; //So this will be skipped next time!
                    break;
                }
            }
        }

        return sortedTransforms;
    }

    public static void SetCollidersEnabled(Transform transform, bool enabled) {
        if (transform == null)
            return;

        SetCollidersEnabled(transform.gameObject, enabled);
    }

    public static void SetCollidersEnabled(GameObject gameObject, bool enabled) {
        if (gameObject == null || gameObject.transform == null)
            return;

        Collider[] colliders = (Collider[])gameObject.GetComponentsInChildren<Collider>();
        foreach (Collider collider in colliders)
            collider.enabled = enabled;
    }

    internal static void ApplyLayerRecursive(GameObject go, int layer) {
        if (go == null)
            return;

        go.layer = layer;
        foreach (Transform child in go.transform)
            ApplyLayerRecursive(child.gameObject, layer);
    }

    #endregion MISC

    //--------------------------------------- MISC --------------------------------------
    //--------------------------------------- MISC --------------------------------------

}