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

namespace UVC.Linq
{

    public static partial class GameObjectExtensions
    {
        /// 축(부모, 자식, 자식들, 조상/자손, 이전 형제/이후 형제 객체)을 기반으로 게임 객체를 순회합니다.
        /// <summary>이 GameObject의 부모 GameObject를 가져옵니다. 이 GameObject에 부모가 없으면 null을 반환합니다.</summary>
        public static GameObject Parent(this GameObject origin)
        {
            if (origin == null) return null;

            var parentTransform = origin.transform.parent;
            if (parentTransform == null) return null;

            return parentTransform.gameObject;
        }

        /// <summary>지정된 이름을 가진 첫 번째 자식 GameObject를 가져옵니다. 지정된 이름을 가진 GameObject가 없으면 null을 반환합니다.</summary>
        public static GameObject Child(this GameObject origin, string name)
        {
            if (origin == null) return null;

            var child = origin.transform.Find(name); // transform.find는 비활성 객체를 가져올 수 있습니다.
            if (child == null) return null;
            return child.gameObject;
        }

        /// <summary>자식 GameObject의 컬렉션을 반환합니다.</summary>
        public static ChildrenEnumerable Children(this GameObject origin)
        {
            return new ChildrenEnumerable(origin, false);
        }

        /// <summary>이 GameObject와 자식 GameObject를 포함하는 GameObject 컬렉션을 반환합니다.</summary>
        public static ChildrenEnumerable ChildrenAndSelf(this GameObject origin)
        {
            return new ChildrenEnumerable(origin, true);
        }

        /// <summary>이 GameObject의 조상 GameObject 컬렉션을 반환합니다.</summary>
        public static AncestorsEnumerable Ancestors(this GameObject origin)
        {
            return new AncestorsEnumerable(origin, false);
        }

        /// <summary>이 요소와 이 GameObject의 조상을 포함하는 GameObject 컬렉션을 반환합니다.</summary>
        public static AncestorsEnumerable AncestorsAndSelf(this GameObject origin)
        {
            return new AncestorsEnumerable(origin, true);
        }

        /// <summary>자손 GameObject의 컬렉션을 반환합니다.</summary>
        public static DescendantsEnumerable Descendants(this GameObject origin, Func<Transform, bool> descendIntoChildren = null)
        {
            return new DescendantsEnumerable(origin, false, descendIntoChildren);
        }

        /// <summary>이 GameObject와 이 GameObject의 모든 자손 GameObject를 포함하는 GameObject 컬렉션을 반환합니다.</summary>
        public static DescendantsEnumerable DescendantsAndSelf(this GameObject origin, Func<Transform, bool> descendIntoChildren = null)
        {
            return new DescendantsEnumerable(origin, true, descendIntoChildren);
        }

        /// <summary>이 GameObject 이전의 형제 GameObject 컬렉션을 반환합니다.</summary>
        public static BeforeSelfEnumerable BeforeSelf(this GameObject origin)
        {
            return new BeforeSelfEnumerable(origin, false);
        }

        /// <summary>이 GameObject와 이 GameObject 이전의 형제 GameObject를 포함하는 GameObject 컬렉션을 반환합니다.</summary>
        public static BeforeSelfEnumerable BeforeSelfAndSelf(this GameObject origin)
        {
            return new BeforeSelfEnumerable(origin, true);
        }

        /// <summary>이 GameObject 이후의 형제 GameObject 컬렉션을 반환합니다.</summary>
        public static AfterSelfEnumerable AfterSelf(this GameObject origin)
        {
            return new AfterSelfEnumerable(origin, false);
        }

        /// <summary>이 GameObject와 이 GameObject 이후의 형제 GameObject를 포함하는 GameObject 컬렉션을 반환합니다.</summary>
        public static AfterSelfEnumerable AfterSelfAndSelf(this GameObject origin)
        {
            return new AfterSelfEnumerable(origin, true);
        }

        // 수동 구조체 열거자를 구현합니다.

        public struct ChildrenEnumerable : IEnumerable<GameObject>
        {
            readonly GameObject origin;
            readonly bool withSelf;

            public ChildrenEnumerable(GameObject origin, bool withSelf)
            {
                this.origin = origin;
                this.withSelf = withSelf;
            }

            /// <summary>소스 컬렉션에서 지정된 컴포넌트의 컬렉션을 반환합니다.</summary>
            public OfComponentEnumerable<T> OfComponent<T>()
                where T : Component
            {
                return new OfComponentEnumerable<T>(ref this);
            }

            /// <summary>소스 컬렉션의 모든 GameObject를 안전하게(null 확인) 파괴합니다.</summary>
            /// <param name="useDestroyImmediate">에디터 모드인 경우 true이거나 !Application.isPlaying을 전달해야 합니다.</param>
            /// <param name="detachParent">parent = null로 설정합니다.</param>
            public void Destroy(bool useDestroyImmediate = false, bool detachParent = false)
            {
                var e = GetEnumerator();
                while (e.MoveNext())
                {
                    e.Current.Destroy(useDestroyImmediate, false);
                }
                if (detachParent)
                {
                    origin.transform.DetachChildren();
                    if (withSelf)
                    {
#if !(UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5)
                        origin.transform.SetParent(null);
#else
                        origin.transform.parent = null;
#endif
                    }
                }
            }

            /// <summary>소스 컬렉션의 모든 GameObject를 안전하게(null 확인) 파괴합니다.</summary>
            /// <param name="useDestroyImmediate">에디터 모드인 경우 true이거나 !Application.isPlaying을 전달해야 합니다.</param>
            public void Destroy(Func<GameObject, bool> predicate, bool useDestroyImmediate = false)
            {
                var e = GetEnumerator();
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (predicate(item))
                    {
                        item.Destroy(useDestroyImmediate, false);
                    }
                }
            }

            public Enumerator GetEnumerator()
            {
                // GetEnumerator 시점에만 GameObject가 파괴되었는지 확인합니다.
                return (origin == null)
                    ? new Enumerator(null, withSelf, false)
                    : new Enumerator(origin.transform, withSelf, true);
            }

            IEnumerator<GameObject> IEnumerable<GameObject>.GetEnumerator()
            {
                return GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            #region LINQ

            int GetChildrenSize()
            {
                return origin.transform.childCount + (withSelf ? 1 : 0);
            }

            public void ForEach(Action<GameObject> action)
            {
                var e = this.GetEnumerator();
                while (e.MoveNext())
                {
                    action(e.Current);
                }
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc(ref GameObject[] array)
            {
                var index = 0;

                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? GetChildrenSize() : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = item;
                }

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc(Func<GameObject, bool> filter, ref GameObject[] array)
            {
                var index = 0;
                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (!filter(item)) continue;

                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? GetChildrenSize() : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = item;
                }

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc<T>(Func<GameObject, T> selector, ref T[] array)
            {
                var index = 0;
                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? GetChildrenSize() : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = selector(item);
                }

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc<T>(Func<GameObject, bool> filter, Func<GameObject, T> selector, ref T[] array)
            {
                var index = 0;
                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (!filter(item)) continue;

                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? GetChildrenSize() : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = selector(item);
                }

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc<TState, T>(Func<GameObject, TState> let, Func<TState, bool> filter, Func<TState, T> selector, ref T[] array)
            {
                var index = 0;
                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    var state = let(item);

                    if (!filter(state)) continue;

                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? GetChildrenSize() : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = selector(state);
                }

                return index;
            }

            public GameObject[] ToArray()
            {
                var array = new GameObject[GetChildrenSize()];
                var len = ToArrayNonAlloc(ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public GameObject[] ToArray(Func<GameObject, bool> filter)
            {
                var array = new GameObject[GetChildrenSize()];
                var len = ToArrayNonAlloc(filter, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public T[] ToArray<T>(Func<GameObject, T> selector)
            {
                var array = new T[GetChildrenSize()];
                var len = ToArrayNonAlloc<T>(selector, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public T[] ToArray<T>(Func<GameObject, bool> filter, Func<GameObject, T> selector)
            {
                var array = new T[GetChildrenSize()];
                var len = ToArrayNonAlloc(filter, selector, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public T[] ToArray<TState, T>(Func<GameObject, TState> let, Func<TState, bool> filter, Func<TState, T> selector)
            {
                var array = new T[GetChildrenSize()];
                var len = ToArrayNonAlloc(let, filter, selector, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public GameObject First()
            {
                var e = this.GetEnumerator();
                if (e.MoveNext())
                {
                    return e.Current;
                }
                else
                {
                    throw new InvalidOperationException("시퀀스가 비어 있습니다.");
                }
            }

            public GameObject FirstOrDefault()
            {
                var e = this.GetEnumerator();
                return (e.MoveNext())
                    ? e.Current
                    : null;
            }

            #endregion

            public struct Enumerator : IEnumerator<GameObject>
            {
                readonly int childCount; // childCount는 GetEnumerator가 호출될 때 고정됩니다.

                readonly Transform originTransform;
                readonly bool canRun;

                bool withSelf;
                int currentIndex;
                GameObject current;

                internal Enumerator(Transform originTransform, bool withSelf, bool canRun)
                {
                    this.originTransform = originTransform;
                    this.withSelf = withSelf;
                    this.childCount = canRun ? originTransform.childCount : 0;
                    this.currentIndex = -1;
                    this.canRun = canRun;
                    this.current = null;
                }

                public bool MoveNext()
                {
                    if (!canRun) return false;

                    if (withSelf)
                    {
                        current = originTransform.gameObject;
                        withSelf = false;
                        return true;
                    }

                    currentIndex++;
                    if (currentIndex < childCount)
                    {
                        var child = originTransform.GetChild(currentIndex);
                        current = child.gameObject;
                        return true;
                    }

                    return false;
                }

                public GameObject Current { get { return current; } }
                object IEnumerator.Current { get { return current; } }
                public void Dispose() { }
                public void Reset() { throw new NotSupportedException(); }
            }

            public struct OfComponentEnumerable<T> : IEnumerable<T>
                where T : Component
            {
                ChildrenEnumerable parent;

                public OfComponentEnumerable(ref ChildrenEnumerable parent)
                {
                    this.parent = parent;
                }

                public OfComponentEnumerator<T> GetEnumerator()
                {
                    return new OfComponentEnumerator<T>(ref this.parent);
                }

                IEnumerator<T> IEnumerable<T>.GetEnumerator()
                {
                    return GetEnumerator();
                }

                IEnumerator IEnumerable.GetEnumerator()
                {
                    return GetEnumerator();
                }

                #region LINQ

                public void ForEach(Action<T> action)
                {
                    var e = this.GetEnumerator();
                    while (e.MoveNext())
                    {
                        action(e.Current);
                    }
                }

                public T First()
                {
                    var e = this.GetEnumerator();
                    if (e.MoveNext())
                    {
                        return e.Current;
                    }
                    else
                    {
                        throw new InvalidOperationException("시퀀스가 비어 있습니다.");
                    }
                }

                public T FirstOrDefault()
                {
                    var e = this.GetEnumerator();
                    return (e.MoveNext())
                        ? e.Current
                        : null;
                }

                public T[] ToArray()
                {
                    var array = new T[parent.GetChildrenSize()];
                    var len = ToArrayNonAlloc(ref array);
                    if (array.Length != len)
                    {
                        Array.Resize(ref array, len);
                    }
                    return array;
                }

                /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
                public int ToArrayNonAlloc(ref T[] array)
                {
                    var index = 0;
                    var e = this.GetEnumerator();
                    while (e.MoveNext())
                    {
                        if (array.Length == index)
                        {
                            var newSize = (index == 0) ? parent.GetChildrenSize() : index * 2;
                            Array.Resize(ref array, newSize);
                        }
                        array[index++] = e.Current;
                    }

                    return index;
                }

                #endregion
            }

            public struct OfComponentEnumerator<T> : IEnumerator<T>
                where T : Component
            {
                Enumerator enumerator; // 열거자는 변경 가능합니다.
                T current;

#if UNITY_EDITOR
                static List<T> componentCache = new List<T>(); // UNITY_EDITOR에서 할당 없음을 위함
#endif

                public OfComponentEnumerator(ref ChildrenEnumerable parent)
                {
                    this.enumerator = parent.GetEnumerator();
                    this.current = default(T);
                }

                public bool MoveNext()
                {
                    while (enumerator.MoveNext())
                    {
#if UNITY_EDITOR
                        enumerator.Current.GetComponents<T>(componentCache);
                        if (componentCache.Count != 0)
                        {
                            current = componentCache[0];
                            componentCache.Clear();
                            return true;
                        }
#else
                        
                        var component = enumerator.Current.GetComponent<T>();
                        if (component != null)
                        {
                            current = component;
                            return true;
                        }
#endif
                    }

                    return false;
                }

                public T Current { get { return current; } }
                object IEnumerator.Current { get { return current; } }
                public void Dispose() { }
                public void Reset() { throw new NotSupportedException(); }
            }
        }

        public struct AncestorsEnumerable : IEnumerable<GameObject>
        {
            readonly GameObject origin;
            readonly bool withSelf;

            public AncestorsEnumerable(GameObject origin, bool withSelf)
            {
                this.origin = origin;
                this.withSelf = withSelf;
            }

            /// <summary>소스 컬렉션에서 지정된 컴포넌트의 컬렉션을 반환합니다.</summary>
            public OfComponentEnumerable<T> OfComponent<T>()
                where T : Component
            {
                return new OfComponentEnumerable<T>(ref this);
            }

            /// <summary>소스 컬렉션의 모든 GameObject를 안전하게(null 확인) 파괴합니다.</summary>
            /// <param name="useDestroyImmediate">에디터 모드인 경우 true이거나 !Application.isPlaying을 전달해야 합니다.</param>
            public void Destroy(bool useDestroyImmediate = false)
            {
                var e = GetEnumerator();
                while (e.MoveNext())
                {
                    e.Current.Destroy(useDestroyImmediate, false);
                }
            }

            /// <summary>소스 컬렉션의 모든 GameObject를 안전하게(null 확인) 파괴합니다.</summary>
            /// <param name="useDestroyImmediate">에디터 모드인 경우 true이거나 !Application.isPlaying을 전달해야 합니다.</param>
            public void Destroy(Func<GameObject, bool> predicate, bool useDestroyImmediate = false)
            {
                var e = GetEnumerator();
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (predicate(item))
                    {
                        item.Destroy(useDestroyImmediate, false);
                    }
                }
            }

            public Enumerator GetEnumerator()
            {
                // GetEnumerator 시점에만 GameObject가 파괴되었는지 확인합니다.
                return (origin == null)
                    ? new Enumerator(null, null, withSelf, false)
                    : new Enumerator(origin, origin.transform, withSelf, true);
            }

            IEnumerator<GameObject> IEnumerable<GameObject>.GetEnumerator()
            {
                return GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            #region LINQ

            public void ForEach(Action<GameObject> action)
            {
                var e = this.GetEnumerator();
                while (e.MoveNext())
                {
                    action(e.Current);
                }
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc(ref GameObject[] array)
            {
                var index = 0;

                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? 4 : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = item;
                }

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc(Func<GameObject, bool> filter, ref GameObject[] array)
            {
                var index = 0;
                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (!filter(item)) continue;

                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? 4 : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = item;
                }

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc<T>(Func<GameObject, T> selector, ref T[] array)
            {
                var index = 0;
                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? 4 : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = selector(item);
                }

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc<T>(Func<GameObject, bool> filter, Func<GameObject, T> selector, ref T[] array)
            {
                var index = 0;
                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (!filter(item)) continue;

                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? 4 : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = selector(item);
                }

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc<TState, T>(Func<GameObject, TState> let, Func<TState, bool> filter, Func<TState, T> selector, ref T[] array)
            {
                var index = 0;
                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    var state = let(item);

                    if (!filter(state)) continue;

                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? 4 : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = selector(state);
                }

                return index;
            }

            public GameObject[] ToArray()
            {
                var array = new GameObject[4];
                var len = ToArrayNonAlloc(ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public GameObject[] ToArray(Func<GameObject, bool> filter)
            {
                var array = new GameObject[4];
                var len = ToArrayNonAlloc(filter, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public T[] ToArray<T>(Func<GameObject, T> selector)
            {
                var array = new T[4];
                var len = ToArrayNonAlloc<T>(selector, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public T[] ToArray<T>(Func<GameObject, bool> filter, Func<GameObject, T> selector)
            {
                var array = new T[4];
                var len = ToArrayNonAlloc(filter, selector, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public T[] ToArray<TState, T>(Func<GameObject, TState> let, Func<TState, bool> filter, Func<TState, T> selector)
            {
                var array = new T[4];
                var len = ToArrayNonAlloc(let, filter, selector, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public GameObject First()
            {
                var e = this.GetEnumerator();
                if (e.MoveNext())
                {
                    return e.Current;
                }
                else
                {
                    throw new InvalidOperationException("시퀀스가 비어 있습니다.");
                }
            }

            public GameObject FirstOrDefault()
            {
                var e = this.GetEnumerator();
                return (e.MoveNext())
                    ? e.Current
                    : null;
            }

            #endregion

            public struct Enumerator : IEnumerator<GameObject>
            {
                readonly bool canRun;

                GameObject current;
                Transform currentTransform;
                bool withSelf;

                internal Enumerator(GameObject origin, Transform originTransform, bool withSelf, bool canRun)
                {
                    this.current = origin;
                    this.currentTransform = originTransform;
                    this.withSelf = withSelf;
                    this.canRun = canRun;
                }

                public bool MoveNext()
                {
                    if (!canRun) return false;

                    if (withSelf)
                    {
                        // withSelf, origin 및 originTransform 사용
                        withSelf = false;
                        return true;
                    }

                    var parentTransform = currentTransform.parent;
                    if (parentTransform != null)
                    {
                        current = parentTransform.gameObject;
                        currentTransform = parentTransform;
                        return true;
                    }

                    return false;
                }

                public GameObject Current { get { return current; } }
                object IEnumerator.Current { get { return current; } }
                public void Dispose() { }
                public void Reset() { throw new NotSupportedException(); }
            }

            public struct OfComponentEnumerable<T> : IEnumerable<T>
                where T : Component
            {
                AncestorsEnumerable parent;

                public OfComponentEnumerable(ref AncestorsEnumerable parent)
                {
                    this.parent = parent;
                }

                public OfComponentEnumerator<T> GetEnumerator()
                {
                    return new OfComponentEnumerator<T>(ref parent);
                }

                IEnumerator<T> IEnumerable<T>.GetEnumerator()
                {
                    return GetEnumerator();
                }

                IEnumerator IEnumerable.GetEnumerator()
                {
                    return GetEnumerator();
                }

                #region LINQ

                public void ForEach(Action<T> action)
                {
                    var e = this.GetEnumerator();
                    while (e.MoveNext())
                    {
                        action(e.Current);
                    }
                }

                public T First()
                {
                    var e = this.GetEnumerator();
                    if (e.MoveNext())
                    {
                        return e.Current;
                    }
                    else
                    {
                        throw new InvalidOperationException("시퀀스가 비어 있습니다.");
                    }
                }

                public T FirstOrDefault()
                {
                    var e = this.GetEnumerator();
                    return (e.MoveNext())
                        ? e.Current
                        : null;
                }

                public T[] ToArray()
                {
                    var array = new T[4];
                    var len = ToArrayNonAlloc(ref array);
                    if (array.Length != len)
                    {
                        Array.Resize(ref array, len);
                    }
                    return array;
                }

                /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
                public int ToArrayNonAlloc(ref T[] array)
                {
                    var index = 0;
                    var e = this.GetEnumerator();
                    while (e.MoveNext())
                    {
                        if (array.Length == index)
                        {
                            var newSize = (index == 0) ? 4 : index * 2;
                            Array.Resize(ref array, newSize);
                        }
                        array[index++] = e.Current;
                    }

                    return index;
                }

                #endregion
            }

            public struct OfComponentEnumerator<T> : IEnumerator<T>
                where T : Component
            {
                Enumerator enumerator; // 열거자는 변경 가능합니다.
                T current;

#if UNITY_EDITOR
                static List<T> componentCache = new List<T>(); // UNITY_EDITOR에서 할당 없음을 위함
#endif

                public OfComponentEnumerator(ref AncestorsEnumerable parent)
                {
                    this.enumerator = parent.GetEnumerator();
                    this.current = default(T);
                }

                public bool MoveNext()
                {
                    while (enumerator.MoveNext())
                    {
#if UNITY_EDITOR
                        enumerator.Current.GetComponents<T>(componentCache);
                        if (componentCache.Count != 0)
                        {
                            current = componentCache[0];
                            componentCache.Clear();
                            return true;
                        }
#else
                        
                        var component = enumerator.Current.GetComponent<T>();
                        if (component != null)
                        {
                            current = component;
                            return true;
                        }
#endif
                    }

                    return false;
                }

                public T Current { get { return current; } }
                object IEnumerator.Current { get { return current; } }
                public void Dispose() { }
                public void Reset() { throw new NotSupportedException(); }
            }
        }

        public struct DescendantsEnumerable : IEnumerable<GameObject>
        {
            static readonly Func<Transform, bool> alwaysTrue = _ => true;

            readonly GameObject origin;
            readonly bool withSelf;
            readonly Func<Transform, bool> descendIntoChildren;

            public DescendantsEnumerable(GameObject origin, bool withSelf, Func<Transform, bool> descendIntoChildren)
            {
                this.origin = origin;
                this.withSelf = withSelf;
                this.descendIntoChildren = descendIntoChildren ?? alwaysTrue;
            }

            /// <summary>소스 컬렉션에서 지정된 컴포넌트의 컬렉션을 반환합니다.</summary>
            public OfComponentEnumerable<T> OfComponent<T>()
                where T : Component
            {
                return new OfComponentEnumerable<T>(ref this);
            }

            /// <summary>소스 컬렉션의 모든 GameObject를 안전하게(null 확인) 파괴합니다.</summary>
            /// <param name="useDestroyImmediate">에디터 모드인 경우 true이거나 !Application.isPlaying을 전달해야 합니다.</param>
            public void Destroy(bool useDestroyImmediate = false)
            {
                var e = GetEnumerator();
                while (e.MoveNext())
                {
                    e.Current.Destroy(useDestroyImmediate, false);
                }
            }

            /// <summary>소스 컬렉션의 모든 GameObject를 안전하게(null 확인) 파괴합니다.</summary>
            /// <param name="useDestroyImmediate">에디터 모드인 경우 true이거나 !Application.isPlaying을 전달해야 합니다.</param>
            public void Destroy(Func<GameObject, bool> predicate, bool useDestroyImmediate = false)
            {
                var e = GetEnumerator();
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (predicate(item))
                    {
                        item.Destroy(useDestroyImmediate, false);
                    }
                }
            }

            public Enumerator GetEnumerator()
            {
                // GetEnumerator 시점에만 GameObject가 파괴되었는지 확인합니다.
                if (origin == null)
                {
                    return new Enumerator(null, withSelf, false, null, descendIntoChildren);
                }

                InternalUnsafeRefStack refStack;
                if (InternalUnsafeRefStack.RefStackPool.Count != 0)
                {
                    refStack = InternalUnsafeRefStack.RefStackPool.Dequeue();
                    refStack.Reset();
                }
                else
                {
                    refStack = new InternalUnsafeRefStack(6);
                }

                return new Enumerator(origin.transform, withSelf, true, refStack, descendIntoChildren);
            }

            IEnumerator<GameObject> IEnumerable<GameObject>.GetEnumerator()
            {
                return GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            #region LINQ

            void ResizeArray<T>(ref int index, ref T[] array)
            {
                if (array.Length == index)
                {
                    var newSize = (index == 0) ? 4 : index * 2;
                    Array.Resize(ref array, newSize);
                }
            }

            void DescendantsCore(ref Transform transform, ref Action<GameObject> action)
            {
                if (!descendIntoChildren(transform)) return;

                var childCount = transform.childCount;
                for (int i = 0; i < childCount; i++)
                {
                    var child = transform.GetChild(i);
                    action(child.gameObject);
                    DescendantsCore(ref child, ref action);
                }
            }

            void DescendantsCore(ref Transform transform, ref int index, ref GameObject[] array)
            {
                if (!descendIntoChildren(transform)) return;

                var childCount = transform.childCount;
                for (int i = 0; i < childCount; i++)
                {
                    var child = transform.GetChild(i);
                    ResizeArray(ref index, ref array);
                    array[index++] = child.gameObject;
                    DescendantsCore(ref child, ref index, ref array);
                }
            }

            void DescendantsCore(ref Func<GameObject, bool> filter, ref Transform transform, ref int index, ref GameObject[] array)
            {
                if (!descendIntoChildren(transform)) return;

                var childCount = transform.childCount;
                for (int i = 0; i < childCount; i++)
                {
                    var child = transform.GetChild(i);
                    var childGameObject = child.gameObject;
                    if (filter(childGameObject))
                    {
                        ResizeArray(ref index, ref array);
                        array[index++] = childGameObject;
                    }
                    DescendantsCore(ref filter, ref child, ref index, ref array);
                }
            }

            void DescendantsCore<T>(ref Func<GameObject, T> selector, ref Transform transform, ref int index, ref T[] array)
            {
                if (!descendIntoChildren(transform)) return;

                var childCount = transform.childCount;
                for (int i = 0; i < childCount; i++)
                {
                    var child = transform.GetChild(i);
                    ResizeArray(ref index, ref array);
                    array[index++] = selector(child.gameObject);
                    DescendantsCore(ref selector, ref child, ref index, ref array);
                }
            }

            void DescendantsCore<T>(ref Func<GameObject, bool> filter, ref Func<GameObject, T> selector, ref Transform transform, ref int index, ref T[] array)
            {
                if (!descendIntoChildren(transform)) return;

                var childCount = transform.childCount;
                for (int i = 0; i < childCount; i++)
                {
                    var child = transform.GetChild(i);
                    var childGameObject = child.gameObject;
                    if (filter(childGameObject))
                    {
                        ResizeArray(ref index, ref array);
                        array[index++] = selector(childGameObject);
                    }
                    DescendantsCore(ref filter, ref selector, ref child, ref index, ref array);
                }
            }

            void DescendantsCore<TState, T>(ref Func<GameObject, TState> let, ref Func<TState, bool> filter, ref Func<TState, T> selector, ref Transform transform, ref int index, ref T[] array)
            {
                if (!descendIntoChildren(transform)) return;

                var childCount = transform.childCount;
                for (int i = 0; i < childCount; i++)
                {
                    var child = transform.GetChild(i);
                    var state = let(child.gameObject);
                    if (filter(state))
                    {
                        ResizeArray(ref index, ref array);
                        array[index++] = selector(state);
                    }
                    DescendantsCore(ref let, ref filter, ref selector, ref child, ref index, ref array);
                }
            }

            /// <summary>성능 최적화를 위해 내부 반복기를 사용합니다.</summary>
            /// <param name="action"></param>
            public void ForEach(Action<GameObject> action)
            {
                if (withSelf)
                {
                    action(origin);
                }
                var originTransform = origin.transform;
                DescendantsCore(ref originTransform, ref action);
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc(ref GameObject[] array)
            {
                var index = 0;
                if (withSelf)
                {
                    ResizeArray(ref index, ref array);
                    array[index++] = origin;
                }

                var originTransform = origin.transform;
                DescendantsCore(ref originTransform, ref index, ref array);

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc(Func<GameObject, bool> filter, ref GameObject[] array)
            {
                var index = 0;
                if (withSelf && filter(origin))
                {
                    ResizeArray(ref index, ref array);
                    array[index++] = origin;
                }
                var originTransform = origin.transform;
                DescendantsCore(ref filter, ref originTransform, ref index, ref array);

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc<T>(Func<GameObject, T> selector, ref T[] array)
            {
                var index = 0;
                if (withSelf)
                {
                    ResizeArray(ref index, ref array);
                    array[index++] = selector(origin);
                }
                var originTransform = origin.transform;
                DescendantsCore(ref selector, ref originTransform, ref index, ref array);

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc<T>(Func<GameObject, bool> filter, Func<GameObject, T> selector, ref T[] array)
            {
                var index = 0;
                if (withSelf && filter(origin))
                {
                    ResizeArray(ref index, ref array);
                    array[index++] = selector(origin);
                }
                var originTransform = origin.transform;
                DescendantsCore(ref filter, ref selector, ref originTransform, ref index, ref array);

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc<TState, T>(Func<GameObject, TState> let, Func<TState, bool> filter, Func<TState, T> selector, ref T[] array)
            {
                var index = 0;
                if (withSelf)
                {
                    var state = let(origin);
                    if (filter(state))
                    {
                        ResizeArray(ref index, ref array);
                        array[index++] = selector(state);
                    }
                }

                var originTransform = origin.transform;
                DescendantsCore(ref let, ref filter, ref selector, ref originTransform, ref index, ref array);

                return index;
            }

            public GameObject[] ToArray()
            {
                var array = new GameObject[4];
                var len = ToArrayNonAlloc(ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public GameObject[] ToArray(Func<GameObject, bool> filter)
            {
                var array = new GameObject[4];
                var len = ToArrayNonAlloc(filter, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public T[] ToArray<T>(Func<GameObject, T> selector)
            {
                var array = new T[4];
                var len = ToArrayNonAlloc<T>(selector, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public T[] ToArray<T>(Func<GameObject, bool> filter, Func<GameObject, T> selector)
            {
                var array = new T[4];
                var len = ToArrayNonAlloc(filter, selector, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public T[] ToArray<TState, T>(Func<GameObject, TState> let, Func<TState, bool> filter, Func<TState, T> selector)
            {
                var array = new T[4];
                var len = ToArrayNonAlloc(let, filter, selector, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public GameObject First()
            {
                var e = this.GetEnumerator();
                try
                {
                    if (e.MoveNext())
                    {
                        return e.Current;
                    }
                    else
                    {
                        throw new InvalidOperationException("시퀀스가 비어 있습니다.");
                    }
                }
                finally
                {
                    e.Dispose();
                }
            }

            public GameObject FirstOrDefault()
            {
                var e = this.GetEnumerator();
                try
                {
                    return (e.MoveNext())
                        ? e.Current
                        : null;
                }
                finally
                {
                    e.Dispose();
                }
            }

            #endregion

            internal class InternalUnsafeRefStack
            {
                public static Queue<InternalUnsafeRefStack> RefStackPool = new Queue<InternalUnsafeRefStack>();

                public int size = 0;
                public Enumerator[] array; // Pop = this.array[--size];

                public InternalUnsafeRefStack(int initialStackDepth)
                {
                    array = new GameObjectExtensions.DescendantsEnumerable.Enumerator[initialStackDepth];
                }

                public void Push(ref Enumerator e)
                {
                    if (size == array.Length)
                    {
                        Array.Resize(ref array, array.Length * 2);
                    }
                    array[size++] = e;
                }

                public void Reset()
                {
                    size = 0;
                }
            }

            public struct Enumerator : IEnumerator<GameObject>
            {
                readonly int childCount; // childCount는 GetEnumerator가 호출될 때 고정됩니다.

                readonly Transform originTransform;
                bool canRun;

                bool withSelf;
                int currentIndex;
                GameObject current;
                InternalUnsafeRefStack sharedStack;
                Func<Transform, bool> descendIntoChildren;

                internal Enumerator(Transform originTransform, bool withSelf, bool canRun, InternalUnsafeRefStack sharedStack, Func<Transform, bool> descendIntoChildren)
                {
                    this.originTransform = originTransform;
                    this.withSelf = withSelf;
                    this.childCount = canRun ? originTransform.childCount : 0;
                    this.currentIndex = -1;
                    this.canRun = canRun;
                    this.current = null;
                    this.sharedStack = sharedStack;
                    this.descendIntoChildren = descendIntoChildren;
                }

                public bool MoveNext()
                {
                    if (!canRun) return false;

                    while (sharedStack.size != 0)
                    {
                        if (sharedStack.array[sharedStack.size - 1].MoveNextCore(true, out current))
                        {
                            return true;
                        }
                    }

                    if (!withSelf && !descendIntoChildren(originTransform))
                    {
                        // 재사용
                        canRun = false;
                        InternalUnsafeRefStack.RefStackPool.Enqueue(sharedStack);
                        return false;
                    }

                    if (MoveNextCore(false, out current))
                    {
                        return true;
                    }
                    else
                    {
                        // 재사용
                        canRun = false;
                        InternalUnsafeRefStack.RefStackPool.Enqueue(sharedStack);
                        return false;
                    }
                }

                bool MoveNextCore(bool peek, out GameObject current)
                {
                    if (withSelf)
                    {
                        current = originTransform.gameObject;
                        withSelf = false;
                        return true;
                    }

                    ++currentIndex;
                    if (currentIndex < childCount)
                    {
                        var item = originTransform.GetChild(currentIndex);
                        if (descendIntoChildren(item))
                        {
                            var childEnumerator = new Enumerator(item, true, true, sharedStack, descendIntoChildren);
                            sharedStack.Push(ref childEnumerator);
                            return sharedStack.array[sharedStack.size - 1].MoveNextCore(true, out current);
                        }
                        else
                        {
                            current = item.gameObject;
                            return true;
                        }
                    }

                    if (peek)
                    {
                        sharedStack.size--; // Pop
                    }

                    current = null;
                    return false;
                }

                public GameObject Current { get { return current; } }
                object IEnumerator.Current { get { return current; } }

                public void Dispose()
                {
                    if (canRun)
                    {
                        canRun = false;
                        InternalUnsafeRefStack.RefStackPool.Enqueue(sharedStack);
                    }
                }

                public void Reset() { throw new NotSupportedException(); }
            }

            public struct OfComponentEnumerable<T> : IEnumerable<T>
                where T : Component
            {
                DescendantsEnumerable parent;

                public OfComponentEnumerable(ref DescendantsEnumerable parent)
                {
                    this.parent = parent;
                }

                public OfComponentEnumerator<T> GetEnumerator()
                {
                    return new OfComponentEnumerator<T>(ref parent);
                }

                IEnumerator<T> IEnumerable<T>.GetEnumerator()
                {
                    return GetEnumerator();
                }

                IEnumerator IEnumerable.GetEnumerator()
                {
                    return GetEnumerator();
                }

                #region LINQ

                public T First()
                {
                    var e = this.GetEnumerator();
                    try
                    {
                        if (e.MoveNext())
                        {
                            return e.Current;
                        }
                        else
                        {
                            throw new InvalidOperationException("시퀀스가 비어 있습니다.");
                        }
                    }
                    finally
                    {
                        e.Dispose();
                    }
                }

                public T FirstOrDefault()
                {
                    var e = this.GetEnumerator();
                    try
                    {
                        return (e.MoveNext())
                            ? e.Current
                            : null;
                    }
                    finally
                    {
                        e.Dispose();
                    }
                }

                /// <summary>성능 최적화를 위해 내부 반복기를 사용합니다.</summary>
                public void ForEach(Action<T> action)
                {
                    if (parent.withSelf)
                    {
                        T component = default(T);
#if UNITY_EDITOR
                        parent.origin.GetComponents<T>(componentCache);
                        if (componentCache.Count != 0)
                        {
                            component = componentCache[0];
                            componentCache.Clear();
                        }
#else
                        component = parent.origin.GetComponent<T>();
#endif

                        if (component != null)
                        {
                            action(component);
                        }
                    }

                    var originTransform = parent.origin.transform;
                    OfComponentDescendantsCore(ref originTransform, ref action);
                }


                public T[] ToArray()
                {
                    var array = new T[4];
                    var len = ToArrayNonAlloc(ref array);
                    if (array.Length != len)
                    {
                        Array.Resize(ref array, len);
                    }
                    return array;
                }

#if UNITY_EDITOR
                static List<T> componentCache = new List<T>(); // UNITY_EDITOR에서 할당 없음을 위함
#endif

                void OfComponentDescendantsCore(ref Transform transform, ref Action<T> action)
                {
                    if (!parent.descendIntoChildren(transform)) return;

                    var childCount = transform.childCount;
                    for (int i = 0; i < childCount; i++)
                    {
                        var child = transform.GetChild(i);

                        T component = default(T);
#if UNITY_EDITOR
                        child.GetComponents<T>(componentCache);
                        if (componentCache.Count != 0)
                        {
                            component = componentCache[0];
                            componentCache.Clear();
                        }
#else
                        component = child.GetComponent<T>();
#endif

                        if (component != null)
                        {
                            action(component);
                        }
                        OfComponentDescendantsCore(ref child, ref action);
                    }
                }

                void OfComponentDescendantsCore(ref Transform transform, ref int index, ref T[] array)
                {
                    if (!parent.descendIntoChildren(transform)) return;

                    var childCount = transform.childCount;
                    for (int i = 0; i < childCount; i++)
                    {
                        var child = transform.GetChild(i);
                        T component = default(T);
#if UNITY_EDITOR
                        child.GetComponents<T>(componentCache);
                        if (componentCache.Count != 0)
                        {
                            component = componentCache[0];
                            componentCache.Clear();
                        }
#else
                        component = child.GetComponent<T>();
#endif

                        if (component != null)
                        {
                            if (array.Length == index)
                            {
                                var newSize = (index == 0) ? 4 : index * 2;
                                Array.Resize(ref array, newSize);
                            }

                            array[index++] = component;
                        }
                        OfComponentDescendantsCore(ref child, ref index, ref array);
                    }
                }

                /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
                public int ToArrayNonAlloc(ref T[] array)
                {
                    var index = 0;
                    if (parent.withSelf)
                    {
                        T component = default(T);
#if UNITY_EDITOR
                        parent.origin.GetComponents<T>(componentCache);
                        if (componentCache.Count != 0)
                        {
                            component = componentCache[0];
                            componentCache.Clear();
                        }
#else
                        component = parent.origin.GetComponent<T>();
#endif

                        if (component != null)
                        {
                            if (array.Length == index)
                            {
                                var newSize = (index == 0) ? 4 : index * 2;
                                Array.Resize(ref array, newSize);
                            }

                            array[index++] = component;
                        }
                    }

                    var originTransform = parent.origin.transform;
                    OfComponentDescendantsCore(ref originTransform, ref index, ref array);

                    return index;
                }

                #endregion
            }

            public struct OfComponentEnumerator<T> : IEnumerator<T>
                where T : Component
            {
                Enumerator enumerator; // 열거자는 변경 가능합니다.
                T current;

#if UNITY_EDITOR
                static List<T> componentCache = new List<T>(); // UNITY_EDITOR에서 할당 없음을 위함
#endif

                public OfComponentEnumerator(ref DescendantsEnumerable parent)
                {
                    this.enumerator = parent.GetEnumerator();
                    this.current = default(T);
                }

                public bool MoveNext()
                {
                    while (enumerator.MoveNext())
                    {
#if UNITY_EDITOR
                        enumerator.Current.GetComponents<T>(componentCache);
                        if (componentCache.Count != 0)
                        {
                            current = componentCache[0];
                            componentCache.Clear();
                            return true;
                        }
#else
                        
                        var component = enumerator.Current.GetComponent<T>();
                        if (component != null)
                        {
                            current = component;
                            return true;
                        }
#endif
                    }

                    return false;
                }

                public T Current { get { return current; } }
                object IEnumerator.Current { get { return current; } }

                public void Dispose()
                {
                    enumerator.Dispose();
                }

                public void Reset() { throw new NotSupportedException(); }
            }
        }

        public struct BeforeSelfEnumerable : IEnumerable<GameObject>
        {
            readonly GameObject origin;
            readonly bool withSelf;

            public BeforeSelfEnumerable(GameObject origin, bool withSelf)
            {
                this.origin = origin;
                this.withSelf = withSelf;
            }

            /// <summary>소스 컬렉션에서 지정된 컴포넌트의 컬렉션을 반환합니다.</summary>
            public OfComponentEnumerable<T> OfComponent<T>()
                where T : Component
            {
                return new OfComponentEnumerable<T>(ref this);
            }

            /// <summary>소스 컬렉션의 모든 GameObject를 안전하게(null 확인) 파괴합니다.</summary>
            /// <param name="useDestroyImmediate">에디터 모드인 경우 true이거나 !Application.isPlaying을 전달해야 합니다.</param>
            public void Destroy(bool useDestroyImmediate = false)
            {
                var e = GetEnumerator();
                while (e.MoveNext())
                {
                    e.Current.Destroy(useDestroyImmediate, false);
                }
            }

            /// <summary>소스 컬렉션의 모든 GameObject를 안전하게(null 확인) 파괴합니다.</summary>
            /// <param name="useDestroyImmediate">에디터 모드인 경우 true이거나 !Application.isPlaying을 전달해야 합니다.</param>
            public void Destroy(Func<GameObject, bool> predicate, bool useDestroyImmediate = false)
            {
                var e = GetEnumerator();
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (predicate(item))
                    {
                        item.Destroy(useDestroyImmediate, false);
                    }
                }
            }

            public Enumerator GetEnumerator()
            {
                // GetEnumerator 시점에만 GameObject가 파괴되었는지 확인합니다.
                return (origin == null)
                    ? new Enumerator(null, withSelf, false)
                    : new Enumerator(origin.transform, withSelf, true);
            }

            IEnumerator<GameObject> IEnumerable<GameObject>.GetEnumerator()
            {
                return GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            #region LINQ

            public void ForEach(Action<GameObject> action)
            {
                var e = this.GetEnumerator();
                while (e.MoveNext())
                {
                    action(e.Current);
                }
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc(ref GameObject[] array)
            {
                var index = 0;

                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? 4 : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = item;
                }

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc(Func<GameObject, bool> filter, ref GameObject[] array)
            {
                var index = 0;
                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (!filter(item)) continue;

                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? 4 : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = item;
                }

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc<T>(Func<GameObject, T> selector, ref T[] array)
            {
                var index = 0;
                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? 4 : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = selector(item);
                }

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc<T>(Func<GameObject, bool> filter, Func<GameObject, T> selector, ref T[] array)
            {
                var index = 0;
                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (!filter(item)) continue;

                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? 4 : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = selector(item);
                }

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc<TState, T>(Func<GameObject, TState> let, Func<TState, bool> filter, Func<TState, T> selector, ref T[] array)
            {
                var index = 0;
                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    var state = let(item);

                    if (!filter(state)) continue;

                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? 4 : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = selector(state);
                }

                return index;
            }

            public GameObject[] ToArray()
            {
                var array = new GameObject[4];
                var len = ToArrayNonAlloc(ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public GameObject[] ToArray(Func<GameObject, bool> filter)
            {
                var array = new GameObject[4];
                var len = ToArrayNonAlloc(filter, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public T[] ToArray<T>(Func<GameObject, T> selector)
            {
                var array = new T[4];
                var len = ToArrayNonAlloc<T>(selector, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public T[] ToArray<T>(Func<GameObject, bool> filter, Func<GameObject, T> selector)
            {
                var array = new T[4];
                var len = ToArrayNonAlloc(filter, selector, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public T[] ToArray<TState, T>(Func<GameObject, TState> let, Func<TState, bool> filter, Func<TState, T> selector)
            {
                var array = new T[4];
                var len = ToArrayNonAlloc(let, filter, selector, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public GameObject First()
            {
                var e = this.GetEnumerator();
                if (e.MoveNext())
                {
                    return e.Current;
                }
                else
                {
                    throw new InvalidOperationException("시퀀스가 비어 있습니다.");
                }
            }

            public GameObject FirstOrDefault()
            {
                var e = this.GetEnumerator();
                return (e.MoveNext())
                    ? e.Current
                    : null;
            }

            #endregion

            public struct Enumerator : IEnumerator<GameObject>
            {
                readonly int childCount; // childCount는 GetEnumerator가 호출될 때 고정됩니다.
                readonly Transform originTransform;
                bool canRun;

                bool withSelf;
                int currentIndex;
                GameObject current;
                Transform parent;

                internal Enumerator(Transform originTransform, bool withSelf, bool canRun)
                {
                    this.originTransform = originTransform;
                    this.withSelf = withSelf;
                    this.currentIndex = -1;
                    this.canRun = canRun;
                    this.current = null;
                    this.parent = originTransform.parent;
                    this.childCount = (parent != null) ? parent.childCount : 0;
                }

                public bool MoveNext()
                {
                    if (!canRun) return false;

                    if (parent == null) goto RETURN_SELF;

                    currentIndex++;
                    if (currentIndex < childCount)
                    {
                        var item = parent.GetChild(currentIndex);

                        if (item == originTransform)
                        {
                            goto RETURN_SELF;
                        }

                        current = item.gameObject;
                        return true;
                    }

                RETURN_SELF:
                    if (withSelf)
                    {
                        current = originTransform.gameObject;
                        withSelf = false;
                        canRun = false; // 자신에게 도달했으므로 실행 완료.
                        return true;
                    }

                    return false;
                }

                public GameObject Current { get { return current; } }
                object IEnumerator.Current { get { return current; } }
                public void Dispose() { }
                public void Reset() { throw new NotSupportedException(); }
            }

            public struct OfComponentEnumerable<T> : IEnumerable<T>
                where T : Component
            {
                BeforeSelfEnumerable parent;

                public OfComponentEnumerable(ref BeforeSelfEnumerable parent)
                {
                    this.parent = parent;
                }

                public OfComponentEnumerator<T> GetEnumerator()
                {
                    return new OfComponentEnumerator<T>(ref parent);
                }

                IEnumerator<T> IEnumerable<T>.GetEnumerator()
                {
                    return GetEnumerator();
                }

                IEnumerator IEnumerable.GetEnumerator()
                {
                    return GetEnumerator();
                }

                #region LINQ

                public void ForEach(Action<T> action)
                {
                    var e = this.GetEnumerator();
                    while (e.MoveNext())
                    {
                        action(e.Current);
                    }
                }

                public T First()
                {
                    var e = this.GetEnumerator();
                    if (e.MoveNext())
                    {
                        return e.Current;
                    }
                    else
                    {
                        throw new InvalidOperationException("시퀀스가 비어 있습니다.");
                    }
                }

                public T FirstOrDefault()
                {
                    var e = this.GetEnumerator();
                    return (e.MoveNext())
                        ? e.Current
                        : null;
                }

                public T[] ToArray()
                {
                    var array = new T[4];
                    var len = ToArrayNonAlloc(ref array);
                    if (array.Length != len)
                    {
                        Array.Resize(ref array, len);
                    }
                    return array;
                }

                /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
                public int ToArrayNonAlloc(ref T[] array)
                {
                    var index = 0;
                    var e = this.GetEnumerator();
                    while (e.MoveNext())
                    {
                        if (array.Length == index)
                        {
                            var newSize = (index == 0) ? 4 : index * 2;
                            Array.Resize(ref array, newSize);
                        }
                        array[index++] = e.Current;
                    }

                    return index;
                }

                #endregion
            }

            public struct OfComponentEnumerator<T> : IEnumerator<T>
                where T : Component
            {
                Enumerator enumerator; // 열거자는 변경 가능합니다.
                T current;

#if UNITY_EDITOR
                static List<T> componentCache = new List<T>(); // UNITY_EDITOR에서 할당 없음을 위함
#endif

                public OfComponentEnumerator(ref BeforeSelfEnumerable parent)
                {
                    this.enumerator = parent.GetEnumerator();
                    this.current = default(T);
                }

                public bool MoveNext()
                {
                    while (enumerator.MoveNext())
                    {
#if UNITY_EDITOR
                        enumerator.Current.GetComponents<T>(componentCache);
                        if (componentCache.Count != 0)
                        {
                            current = componentCache[0];
                            componentCache.Clear();
                            return true;
                        }
#else
                        
                        var component = enumerator.Current.GetComponent<T>();
                        if (component != null)
                        {
                            current = component;
                            return true;
                        }
#endif
                    }

                    return false;
                }

                public T Current { get { return current; } }
                object IEnumerator.Current { get { return current; } }
                public void Dispose() { }
                public void Reset() { throw new NotSupportedException(); }
            }
        }

        public struct AfterSelfEnumerable : IEnumerable<GameObject>
        {
            readonly GameObject origin;
            readonly bool withSelf;

            public AfterSelfEnumerable(GameObject origin, bool withSelf)
            {
                this.origin = origin;
                this.withSelf = withSelf;
            }

            /// <summary>소스 컬렉션에서 지정된 컴포넌트의 컬렉션을 반환합니다.</summary>
            public OfComponentEnumerable<T> OfComponent<T>()
                where T : Component
            {
                return new OfComponentEnumerable<T>(ref this);
            }

            /// <summary>소스 컬렉션의 모든 GameObject를 안전하게(null 확인) 파괴합니다.</summary>
            /// <param name="useDestroyImmediate">에디터 모드인 경우 true이거나 !Application.isPlaying을 전달해야 합니다.</param>
            public void Destroy(bool useDestroyImmediate = false)
            {
                var e = GetEnumerator();
                while (e.MoveNext())
                {
                    e.Current.Destroy(useDestroyImmediate, false);
                }
            }

            /// <summary>소스 컬렉션의 모든 GameObject를 안전하게(null 확인) 파괴합니다.</summary>
            /// <param name="useDestroyImmediate">에디터 모드인 경우 true이거나 !Application.isPlaying을 전달해야 합니다.</param>
            public void Destroy(Func<GameObject, bool> predicate, bool useDestroyImmediate = false)
            {
                var e = GetEnumerator();
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (predicate(item))
                    {
                        item.Destroy(useDestroyImmediate, false);
                    }
                }
            }

            public Enumerator GetEnumerator()
            {
                // GetEnumerator 시점에만 GameObject가 파괴되었는지 확인합니다.
                return (origin == null)
                    ? new Enumerator(null, withSelf, false)
                    : new Enumerator(origin.transform, withSelf, true);
            }

            IEnumerator<GameObject> IEnumerable<GameObject>.GetEnumerator()
            {
                return GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            #region LINQ

            public void ForEach(Action<GameObject> action)
            {
                var e = this.GetEnumerator();
                while (e.MoveNext())
                {
                    action(e.Current);
                }
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc(ref GameObject[] array)
            {
                var index = 0;

                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? 4 : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = item;
                }

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc(Func<GameObject, bool> filter, ref GameObject[] array)
            {
                var index = 0;
                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (!filter(item)) continue;

                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? 4 : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = item;
                }

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc<T>(Func<GameObject, T> selector, ref T[] array)
            {
                var index = 0;
                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? 4 : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = selector(item);
                }

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc<T>(Func<GameObject, bool> filter, Func<GameObject, T> selector, ref T[] array)
            {
                var index = 0;
                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    if (!filter(item)) continue;

                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? 4 : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = selector(item);
                }

                return index;
            }

            /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
            public int ToArrayNonAlloc<TState, T>(Func<GameObject, TState> let, Func<TState, bool> filter, Func<TState, T> selector, ref T[] array)
            {
                var index = 0;
                var e = this.GetEnumerator(); // Dispose를 호출할 필요가 없습니다.
                while (e.MoveNext())
                {
                    var item = e.Current;
                    var state = let(item);

                    if (!filter(state)) continue;

                    if (array.Length == index)
                    {
                        var newSize = (index == 0) ? 4 : index * 2;
                        Array.Resize(ref array, newSize);
                    }
                    array[index++] = selector(state);
                }

                return index;
            }

            public GameObject[] ToArray()
            {
                var array = new GameObject[4];
                var len = ToArrayNonAlloc(ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public GameObject[] ToArray(Func<GameObject, bool> filter)
            {
                var array = new GameObject[4];
                var len = ToArrayNonAlloc(filter, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public T[] ToArray<T>(Func<GameObject, T> selector)
            {
                var array = new T[4];
                var len = ToArrayNonAlloc<T>(selector, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public T[] ToArray<T>(Func<GameObject, bool> filter, Func<GameObject, T> selector)
            {
                var array = new T[4];
                var len = ToArrayNonAlloc(filter, selector, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public T[] ToArray<TState, T>(Func<GameObject, TState> let, Func<TState, bool> filter, Func<TState, T> selector)
            {
                var array = new T[4];
                var len = ToArrayNonAlloc(let, filter, selector, ref array);
                if (array.Length != len)
                {
                    Array.Resize(ref array, len);
                }
                return array;
            }

            public GameObject First()
            {
                var e = this.GetEnumerator();
                if (e.MoveNext())
                {
                    return e.Current;
                }
                else
                {
                    throw new InvalidOperationException("시퀀스가 비어 있습니다.");
                }
            }

            public GameObject FirstOrDefault()
            {
                var e = this.GetEnumerator();
                return (e.MoveNext())
                    ? e.Current
                    : null;
            }

            #endregion

            public struct Enumerator : IEnumerator<GameObject>
            {
                readonly int childCount; // childCount는 GetEnumerator가 호출될 때 고정됩니다.
                readonly Transform originTransform;
                readonly bool canRun;

                bool withSelf;
                int currentIndex;
                GameObject current;
                Transform parent;

                internal Enumerator(Transform originTransform, bool withSelf, bool canRun)
                {
                    this.originTransform = originTransform;
                    this.withSelf = withSelf;
                    this.currentIndex = (originTransform != null) ? originTransform.GetSiblingIndex() + 1 : 0;
                    this.canRun = canRun;
                    this.current = null;
                    this.parent = originTransform.parent;
                    this.childCount = (parent != null) ? parent.childCount : 0;
                }

                public bool MoveNext()
                {
                    if (!canRun) return false;

                    if (withSelf)
                    {
                        current = originTransform.gameObject;
                        withSelf = false;
                        return true;
                    }

                    if (currentIndex < childCount)
                    {
                        current = parent.GetChild(currentIndex).gameObject;
                        currentIndex++;
                        return true;
                    }

                    return false;
                }

                public GameObject Current { get { return current; } }
                object IEnumerator.Current { get { return current; } }
                public void Dispose() { }
                public void Reset() { throw new NotSupportedException(); }
            }

            public struct OfComponentEnumerable<T> : IEnumerable<T>
                where T : Component
            {
                AfterSelfEnumerable parent;

                public OfComponentEnumerable(ref AfterSelfEnumerable parent)
                {
                    this.parent = parent;
                }

                public OfComponentEnumerator<T> GetEnumerator()
                {
                    return new OfComponentEnumerator<T>(ref this.parent);
                }

                IEnumerator<T> IEnumerable<T>.GetEnumerator()
                {
                    return GetEnumerator();
                }

                IEnumerator IEnumerable.GetEnumerator()
                {
                    return GetEnumerator();
                }

                #region LINQ

                public void ForEach(Action<T> action)
                {
                    var e = this.GetEnumerator();
                    while (e.MoveNext())
                    {
                        action(e.Current);
                    }
                }

                public T First()
                {
                    var e = this.GetEnumerator();
                    if (e.MoveNext())
                    {
                        return e.Current;
                    }
                    else
                    {
                        throw new InvalidOperationException("시퀀스가 비어 있습니다.");
                    }
                }

                public T FirstOrDefault()
                {
                    var e = this.GetEnumerator();
                    return (e.MoveNext())
                        ? e.Current
                        : null;
                }

                public T[] ToArray()
                {
                    var array = new T[4];
                    var len = ToArrayNonAlloc(ref array);
                    if (array.Length != len)
                    {
                        Array.Resize(ref array, len);
                    }
                    return array;
                }

                /// <summary>요소를 버퍼에 저장하고 크기를 반환합니다. 배열은 자동으로 확장됩니다.</summary>
                public int ToArrayNonAlloc(ref T[] array)
                {
                    var index = 0;
                    var e = this.GetEnumerator();
                    while (e.MoveNext())
                    {
                        if (array.Length == index)
                        {
                            var newSize = (index == 0) ? 4 : index * 2;
                            Array.Resize(ref array, newSize);
                        }
                        array[index++] = e.Current;
                    }

                    return index;
                }

                #endregion
            }

            public struct OfComponentEnumerator<T> : IEnumerator<T>
                where T : Component
            {
                Enumerator enumerator; // 열거자는 변경 가능합니다.
                T current;

#if UNITY_EDITOR
                static List<T> componentCache = new List<T>(); // UNITY_EDITOR에서 할당 없음을 위함
#endif

                public OfComponentEnumerator(ref AfterSelfEnumerable parent)
                {
                    this.enumerator = parent.GetEnumerator();
                    this.current = default(T);
                }

                public bool MoveNext()
                {
                    while (enumerator.MoveNext())
                    {
#if UNITY_EDITOR
                        enumerator.Current.GetComponents<T>(componentCache);
                        if (componentCache.Count != 0)
                        {
                            current = componentCache[0];
                            componentCache.Clear();
                            return true;
                        }
#else
                        
                        var component = enumerator.Current.GetComponent<T>();
                        if (component != null)
                        {
                            current = component;
                            return true;
                        }
#endif
                    }

                    return false;
                }

                public T Current { get { return current; } }
                object IEnumerator.Current { get { return current; } }
                public void Dispose() { }
                public void Reset() { throw new NotSupportedException(); }
            }
        }
    }
}