//#undef UNITY_EDITOR

using System.Collections.Generic;
using System.Reflection;
using System.Text;
using JetBrains.Annotations;
using UnityEditor.Experimental.SceneManagement;
using UnityEngine;
using UnityObject = UnityEngine.Object;
#if UNITY_EDITOR
using UnityEditor;

#endif

public static class UnityObjectExtensions {

    private static readonly Stack<StringBuilder>
        _SbPool = new Stack<StringBuilder>();


    /// <summary>
    /// Will return a string in the form [A]ObjectGrandParentName/ObjectParentName/ObjectName/SomeComponentName
    /// Where [A] can be [SceneName] or [Prefab], and SomeComponent is only shown if <see cref="component"/> is not null, and [SceneName] is only shown if <see cref="includeScene"/> is true.
    /// </summary>
    /// <param name="gameObject">Will get all parents of this object and return a string with each parent like ObjectGrandParentName/ObjectParentName/ObjectName</param>
    /// <param name="includeScene">If the object is not a prefab the scene name will be shown in the first part of the returned string, as [SceneName]</param>
    /// <param name="component">If this is not null, will add the component name to the last part of the returned string.</param>
    /// <returns></returns>
    [NotNull]
    public static string GetHierarchyPath(
        this GameObject gameObject,
        bool includeScene = true,
        [CanBeNull] Component component = null) {
        const string COMPONENT_WAS_NULL = "GAMEOBJECT WAS NULL. NO HIERARCHY PATH!";
        //comment ID 05:03 11/04/2018: we can't even extract the runtime type with GetType here. It is really null.
        if (gameObject != null) {
            return COMPONENT_WAS_NULL;
        }

        StringBuilder sb;
        if (_SbPool.Count > 0) {
            sb = _SbPool.Pop();
            sb.Clear();
        } else {
            sb = new StringBuilder(200);
        }

        try {

#if UNITY_EDITOR
            bool isPrefab;
#if UNITY_2018_3_OR_NEWER
            isPrefab = UnityEditor.PrefabUtility.GetPrefabAssetType(gameObject.gameObject) != UnityEditor.PrefabAssetType.NotAPrefab;
#else
                isPrefab = UnityEditor.PrefabUtility.GetPrefabType(o) == UnityEditor.PrefabType.Prefab;
#endif
            var assetPath = string.Empty;
            if (isPrefab) {
#if UNITY_2018_3_OR_NEWER
                assetPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(gameObject);
#else
                assetPath = AssetDatabase.GetAssetPath(gameObject);
#endif
                sb.Append("[PREFAB]/[");
            } else {
#endif

                if (includeScene) {
                    var sceneName = gameObject.scene.name;
                    if (sceneName == string.Empty) {
#if UNITY_2018_3_OR_NEWER
                        var prefabStage = PrefabStageUtility.GetPrefabStage(gameObject);
                        if (prefabStage != null) {
                            sceneName = "Prefab Stage";
                        } else {
#endif
                            sceneName = "Unsaved Scene";
#if UNITY_2018_3_OR_NEWER
                        }
#endif
                    }

                    sb.Append("[");
                    sb.Append(sceneName);
                    sb.Append("]/");

                    //will be  like [SceneName]/ so far
                }
#if UNITY_EDITOR
            }
#endif

            sb.Append(gameObject.name);
            //will be  like [SceneName]/RootGameObjectName
            //[SceneName]/ will not be there if includeScene = false

            var componentTypeName = string.Empty;
            var includeComponent = component != null;
            if (includeComponent) {
                //we need to store before changing the gameObject reference
                componentTypeName = component.GetType().GetTypeInfo().Name;
            }

            while (gameObject && gameObject.transform.parent) {
                gameObject = gameObject.transform.parent.gameObject;
                sb.Append("/");
                sb.Append(gameObject.GetType().GetTypeInfo().Name);
            }

            //will be  like [SceneName]/RootGameObjectName/ChildObjectName/GrandChildObjectName
            if (includeComponent) {
                sb.Append("/");
                sb.Append(componentTypeName);
                //will be  like [SceneName]/RootGameObjectName/ChildObjectName/GrandChildObjectName/ComponentName
                //[SceneName]/ will not be there if includeScene = false
            }

#if UNITY_EDITOR
            //finish putting rest of prefab
            //should be something like this
            //[PREFAB]/[RootObj/ChildObj/MaybeSomeComponent
            //will then be like this (2 lines)
            //[PREFAB]/[RootObj/ChildObj/MaybeSomeComponent]
            //(Path on disk: ASSET_PATH_ON_DISK)

            if (isPrefab) {
                sb.Append("]\n(Path on disk: ");
                sb.Append(assetPath);
                sb.Append(")");
            }
#endif
            var path = sb.ToString();
            sb.Clear();
            return path;
        }
        finally {
            _SbPool.Push(sb);
        }

    }

}