using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
/// 
/// Utility script for finding missing references to objects.
/// 
public class MissingRefFinder
{
    private const string RootMenu = "Tools/Utility/Missing References Finder/";
    /// 
    /// Finds all missing references to objects in the currently loaded scene.
    /// 
    [MenuItem(RootMenu + "Search in scene", false, 50)]
    public static void FindMissingReferencesInCurrentScene()
    {
        var sceneObjects = GetSceneObjects();
        FindMissingReferences(EditorSceneManager.GetActiveScene().path, sceneObjects);
    }
    /// 
    /// Finds all missing references to objects in all enabled scenes in the project.
    /// This works by loading the scenes one by one and checking for missing object references.
    /// 
    [MenuItem(RootMenu + "Search in all scenes", false, 51)]
    public static void FindMissingReferencesInAllScenes()
    {
        foreach (var scene in EditorBuildSettings.scenes.Where(s => s.enabled))
        {
            EditorSceneManager.OpenScene(scene.path);
            FindMissingReferencesInCurrentScene();
        }
    }
    /// 
    /// Finds all missing references to objects in assets (objects from the project window).
    /// 
    [MenuItem(RootMenu + "Search in assets", false, 52)]
    public static void FindMissingReferencesInAssets()
    {
        var allAssets = AssetDatabase.GetAllAssetPaths().Where(path => path.StartsWith("Assets/")).ToArray();
        var objs = allAssets.Select(a => AssetDatabase.LoadAssetAtPath(a, typeof(GameObject)) as GameObject)
            .Where(a => a != null).ToArray();
        FindMissingReferences("Project", objs);
    }
    private static void FindMissingReferences(string context, IEnumerable gameObjects)
    {
        if (gameObjects == null)
            return;
        var missingObjs = new List();
        var missingScriptLog = new StringBuilder();
        var missingPropertyLog = new StringBuilder();
        foreach (var go in gameObjects)
        {
            var comps = go.GetComponents();
            foreach (var comp in comps)
            {
                // Missing components will be null, we can't find their type, etc.
                if (comp == null)
                {
                    missingObjs.Add(go);
                    missingScriptLog.AppendLine(GetMissingScriptLog(go, context));
                    continue;
                }
                SerializedObject so = new SerializedObject(comp);
                var sp = so.GetIterator();
                var objRefValueMethod = typeof(SerializedProperty).GetProperty("objectReferenceStringValue",
                    BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                // Iterate over the components' properties.
                while (sp.NextVisible(true))
                {
                    if (sp.propertyType == SerializedPropertyType.ObjectReference)
                    {
                        string objectReferenceStringValue = string.Empty;
                        if (objRefValueMethod != null)
                        {
                            objectReferenceStringValue = (string) objRefValueMethod.GetGetMethod(true).Invoke(sp, new object[] { });
                        }
                        if (sp.objectReferenceValue == null && 
                            (sp.objectReferenceInstanceIDValue != 0 || objectReferenceStringValue.StartsWith("Missing")))
                        {
                            missingObjs.Add(go);
                            missingPropertyLog.AppendLine(GetMissingPropertyLog(go, comp.GetType().Name, ObjectNames.NicifyVariableName(sp.name), context));
                        }
                    }
                }
            }
        }
        if (missingObjs.Count > 0)
        {
            var sb = new StringBuilder();
            if (missingScriptLog.Length > 0)
            {
                sb.AppendLine("");
                sb.AppendLine(missingScriptLog.ToString());
            }
            if (missingPropertyLog.Length > 0)
            {
                if (missingScriptLog.Length > 0)
                    sb.AppendLine();
                
                sb.AppendLine("");
                sb.AppendLine(missingPropertyLog.ToString());
            }
            Debug.LogError(sb.ToString());
            Selection.objects = missingObjs.ToArray();
        }
        else
        {
            Debug.Log("No missing script/property in current scene!");
        }
    }
    private static IEnumerable GetSceneObjects()
    {
        // Use this method since GameObject.FindObjectsOfType will not return disabled objects.
        return Resources.FindObjectsOfTypeAll()
            .Where(go => string.IsNullOrEmpty(AssetDatabase.GetAssetPath(go))
                         && go.hideFlags == HideFlags.None);
    }
    private readonly static string missingScriptFormat = "Missing Script in: [{1}] {0}";
    private static string GetMissingScriptLog(GameObject go, string context)
    {
        return string.Format(missingScriptFormat, GetFullPath(go), context);
    }
    private readonly static string missingPropertyFormat = "Missing Property in : [{3}] {0}. Component : {1}, Property : {2}";
    private static string GetMissingPropertyLog(GameObject go, string componentName, string propertyName, string context)
    {
        return string.Format(missingPropertyFormat, GetFullPath(go), componentName, propertyName, context);
    }
    private static string GetFullPath(GameObject go)
    {
        return go.transform.parent == null
            ? go.name
            : GetFullPath(go.transform.parent.gameObject) + "/" + go.name;
    }
}