using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine.UI;
using UnityEngine.Rendering;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
#endif

public class MissingSearcher : MonoBehaviour {
#if UNITY_EDITOR

    private Stopwatch stopwatch = new Stopwatch();
    private GameObject[] allObjectArray;
    private int allObjectIndex = 0;
    private List<Camera> cameraList = new List<Camera>();

    void Start(){
        ResetAllObjectArray();
    }

    void Update(){
        stopwatch.Start();
        try{
            while(stopwatch.ElapsedMilliseconds < 8){ // およそ 1/120
                UpdateSearch();
            }
        }catch (System.Exception e){
            UnityEngine.Debug.Log("MissingSearcher Update " + e);
            ResetAllObjectArray();
        }
        stopwatch.Stop();
        stopwatch.Reset();
    }

    private void ResetAllObjectArray(){
        allObjectIndex = 0;
        allObjectArray = UnityEngine.Resources.FindObjectsOfTypeAll<GameObject>();
        cameraList.Clear();
        foreach(var obj in allObjectArray){
            if(obj.GetComponent<Camera>() is var camera && camera != null && camera.cameraType == CameraType.Game){
                cameraList.Add(camera);
            }
        }
    }
    private void UpdateSearch(){
        if(allObjectIndex >= allObjectArray.Length){
            ResetAllObjectArray();
        }

        string path = AssetDatabase.GetAssetOrScenePath(allObjectArray[allObjectIndex]);
        while(!path.Contains(".unity") && ++allObjectIndex < allObjectArray.Length){
            path = AssetDatabase.GetAssetOrScenePath(allObjectArray[allObjectIndex]);
        }

        if(allObjectIndex >= allObjectArray.Length) return ;
        bool result = CheckInScene(allObjectArray[allObjectIndex], path, cameraList);
        if(result){
            EditorApplication.isPlaying = false;
        }
        ++allObjectIndex;
    }

    private static string[] extensions = { ".prefab", ".mat", ".controller", ".cs", ".shader", ".mask", ".asset" };
    /// <summary>
    /// Missingがあるアセットを検索してそのリストを表示する
    /// </summary>
    [MenuItem("Assets/MissingSearch")]
    private static void ShowMissingList(){
        // Missingがあるアセットを検索
        Search();
    }
    /// <summary>
    /// Missingがあるアセットを検索
    /// </summary>
    private static void Search(){
        // 全てのアセットのファイルパスを取得
        string[] allPaths = AssetDatabase.GetAllAssetPaths();
        int length = allPaths.Length;
        for(int i = 0; i < length; i++){
            // プログレスバーを表示
            EditorUtility.DisplayProgressBar("Search Missing", string.Format("{0}/{1}", i+1, length), (float)i / length);
            // Missing状態のプロパティを検索
            if(extensions.Contains(Path.GetExtension(allPaths[i]))){
                SearchMissing(allPaths[i]);
            }
        }
        // プログレスバーを消す
        EditorUtility.ClearProgressBar();
    }
    private static void SearchMissing(string path){
        // 指定パスのアセットを全て取得
        IEnumerable<UnityEngine.Object> assets = AssetDatabase.LoadAllAssetsAtPath(path);
        // 各アセットについて、Missingのプロパティがあるかチェック
        foreach(UnityEngine.Object obj in assets){
            if(obj == null) continue;
            if(obj.name == "Deprecated EditorExtensionImpl") continue;
            Check(obj, string.Format("{0} {1}", path, obj.name));
        }
    }

    private static bool CheckInScene(GameObject obj, string path, List<Camera> cameraList){
        bool result = false;
        string inScenePath = obj.name;
        Transform parent = obj.transform.parent;
        while(parent != null){
            inScenePath = string.Format("{0}/{1}", parent.name, inScenePath);
            parent = parent.parent;
        }
        path = string.Format(@"{0}
{1}", path, inScenePath);
        if(path.Contains("DebugOption")) return false;
        var components = obj.GetComponents<Component>();
        bool hasCanvasRenderer = false;
        bool hasGraphic = false;
        bool hasMask = false;
        bool hasRectMask = false;
        foreach(var component in components){
            if(component == null){
                result = true;
                UnityEngine.Debug.LogError(string.Format("{0} に不明なコンポーネントがあるので修正して下さい。", path));
            }
            if(component is CanvasRenderer){
                hasCanvasRenderer = true;
            }
            if(component is Graphic){
                hasGraphic = true;
            }
            if(component is SpriteRenderer spriteRenderer){
                if(spriteRenderer.sprite == null){
                    result = true;
                    UnityEngine.Debug.LogError(string.Format("{0} の SpriteRenderer の Sprite がNoneになっているので修正して下さい。", path));
                }else if(spriteRenderer.drawMode == SpriteDrawMode.Tiled){
                    var sprite = spriteRenderer.sprite;
                    if((sprite.texture.height <= 10 && spriteRenderer.size.y >= 1.0f) || (sprite.texture.width <= 10 && spriteRenderer.size.x >= 1.0f)){
                        result = true;
                        UnityEngine.Debug.LogError(string.Format("{0} の SpriteRenderer の DrawMode がTiledなのに {1} を使用しているので修正して下さい。", path, spriteRenderer.sprite.texture.name));
                    }
                    if(path.Contains("backGround_bank_b_ground_b")){
                        result = true;
                        UnityEngine.Debug.LogError(spriteRenderer.size.x * sprite.pixelsPerUnit + " " + sprite.texture.width + " " + (sprite.texture.width - sprite.border.x - sprite.border.z));
                    }
                    var triCount = (spriteRenderer.size.x * sprite.pixelsPerUnit - sprite.texture.width) / (sprite.texture.width - sprite.border.x - sprite.border.z) *
                        (spriteRenderer.size.y * sprite.pixelsPerUnit - sprite.texture.height) / (sprite.texture.height - sprite.border.y - sprite.border.w) * 2;
                    if(triCount >= 2000){
                        result = true;
                        UnityEngine.Debug.LogError(string.Format("{0} の SpriteRenderer の DrawMode がTiledなのに狭いBorder設定のSprite {1} が設定されており、ポリゴン数が {2}以上 になっているので修正して下さい。", path, spriteRenderer.sprite.texture.name, (int)triCount));
                    }
                }
                if(obj.activeInHierarchy){
                    if(Mathf.Approximately(spriteRenderer.color.a, 0.0f)){
                        var hasPerspectiveTransparent = false;
                        foreach(var camera in cameraList){
                            if(camera.orthographic){
                                if(camera.cullingMask == -1 || (camera.cullingMask & 1 << obj.layer) > 0){
                                    var cameraAreaSize = camera.orthographicSize * 2 * (camera.orthographicSize * 2 * (float)Screen.width / (float)Screen.height);
                                    var spriteAreaSize = spriteRenderer.bounds.size.x * spriteRenderer.bounds.size.y;
                                    if(spriteAreaSize >= cameraAreaSize * 0.2f){
                                        result = true;
                                        UnityEngine.Debug.LogError(string.Format("{0} の SpriteRenderer の Color.a が0なのに有効化されていて、カメラの{1}割を占めているので修正して下さい。", path, (int)(spriteAreaSize / cameraAreaSize * 10.0f)));
                                    }
                                }
                            }else{
                                hasPerspectiveTransparent = true;
                            }
                        }
                        if(hasPerspectiveTransparent){
                            result = true;
                            UnityEngine.Debug.LogError(string.Format("{0} の SpriteRenderer の Color.a が0なのに有効化されているので修正して下さい。", path));
                        }
                    }
                }
                foreach(var material in spriteRenderer.materials){
                    if(material.shader.name.Contains("Particle")
                        || material.shader.name.Contains("UGUI")){
                        result = true;
                        UnityEngine.Debug.LogError(string.Format("{0} の SpriteRenderer のMaterialに3D用以外のShader {1} が設定されているので修正して下さい。", path, material.shader.name));
                    }
                }
            }
            if(component is MeshRenderer meshRenderer){
                foreach(var material in meshRenderer.materials){
                    if(material.shader.name.Contains("Particle")
                        || material.shader.name.Contains("UGUI")){
                        result = true;
                        UnityEngine.Debug.LogError(string.Format("{0} の MeshRenderer のMaterialに3D用以外のShader {1} が設定されているので修正して下さい。", path, material.shader.name));
                    }
                }
            }
            if(component is Renderer renderer){
                foreach(var material in renderer.materials){
                    if(material.shader.name.Contains("UGUI")){
                        result = true;
                        UnityEngine.Debug.LogError(string.Format("{0} の Renderer のMaterialに3D用以外のShader {1} が設定されているので修正して下さい。", path, material.shader.name));
                    }
                }
            }
            if(component is Text text){
                if(!text.enabled){
                    result = true;
                    UnityEngine.Debug.LogError(string.Format("{0} に無効な Text があるので修正して下さい。", path));
                }
                if(text.font == null){
                    result = true;
                    UnityEngine.Debug.LogError(string.Format("{0} の Text の Font を設定下さい。", path));
                }
            }
            if(component is Image image){
                if(!image.enabled){
                    result = true;
                    UnityEngine.Debug.LogError(string.Format("{0} に無効な Image があるので修正して下さい。", path));
                }
                if(image.material != null && !image.material.shader.name.Contains("UI/Default") && !image.material.shader.name.Contains("UGUI") && !image.material.shader.name.Contains("Custom/Fade")){
                    result = true;
                    UnityEngine.Debug.LogError(string.Format("{0} の Image のMaterialにUI用以外のShader {1} が設定されているので修正して下さい。", path, image.material.shader.name));
                }
                if(image.type == Image.Type.Tiled){
                    var size = ((RectTransform)image.transform).sizeDelta;
                    if((image.sprite.texture.height <= 10 && size.y >= 100.0f) || (image.sprite.texture.width <= 10 && size.x >= 100.0f)){
                        result = true;
                        UnityEngine.Debug.LogError(string.Format("{0} の Image の ImageType がTiledなのに{1}を使用しているので修正して下さい。", path, image.sprite.texture.name));
                    }
                }
            }
            if(component is SortingGroup sortingGroup){
                if(!sortingGroup.enabled){
                    result = true;
                    UnityEngine.Debug.LogError(string.Format("{0} に無効な SortingGroup があるので修正して下さい。", path));
                }
            }
            if(component is Mask mask){
                hasMask = true;
                if(!mask.enabled || mask.transform.childCount <= 0){
                    result = true;
                    UnityEngine.Debug.LogError(string.Format("{0} に無効な Mask があるので修正して下さい。", path));
                }
            }
            if(component is RectMask2D rectMask){
                hasRectMask = true;
                if(!rectMask.enabled || rectMask.transform.childCount <= 0){
                    result = true;
                    UnityEngine.Debug.LogError(string.Format("{0} に無効な RectMask2D があるので修正して下さい。", path));
                }
            }
            if(component is Animator animator){
                if(animator.runtimeAnimatorController == null){
                    result = true;
                    UnityEngine.Debug.LogError(string.Format("{0} の Animator の Controller がNoneになっているので修正して下さい。", path));
                }
            }
            if(component is Outline outline){
                if(!outline.enabled){
                    result = true;
                    UnityEngine.Debug.LogError(string.Format("{0} に無効な Outline があるので修正して下さい。", path));
                }
            }
            if(component is Joint2D joint2d){
                if(!joint2d.enabled){
                    result = true;
                    UnityEngine.Debug.LogError(string.Format("{0} に無効な Joint2D があるので修正して下さい。", path));
                }
            }
            if(component is PlaySE playSE){
                if(!playSE.enabled){
                    result = true;
                    UnityEngine.Debug.LogError(string.Format("{0} に無効な PlaySE があるので修正して下さい。", path));
                }
            }
            if(component is RectTransform rectTransform){
                if(rectTransform.GetComponent<Canvas>() == null && !rectTransform.FindParentsComponent<Canvas>(canvas => {})){
                    result = true;
                    UnityEngine.Debug.LogError(string.Format("RectTransformをCanvasの外に配置しないで下さい。 {0}", path));
                }
                continue ;
            }
            if(component is Transform) continue ;
            result = result || Check(component, path);
        }
        if(hasCanvasRenderer && !hasGraphic){
            result = true;
            UnityEngine.Debug.LogError(string.Format("{0} に無駄な CanvasRenderer があるので修正して下さい。", path));
        }
        if(hasMask && hasRectMask){
            result = true;
            UnityEngine.Debug.LogError(string.Format("{0} に2種の Mask があるので修正して下さい。", path));
        }
        return result;
    }

    private static bool Check(UnityEngine.Object obj, string path){
        bool result = false;
        // SerializedObjectを通してアセットのプロパティを取得する
        SerializedObject sobj = new SerializedObject(obj);
        SerializedProperty property = sobj.GetIterator();
        while(property.Next(true)){
            // プロパティの種類がオブジェクト（アセット）への参照で、
            // その参照がnullなのにもかかわらず、参照先インスタンスIDが0でないものはMissing状態
            if(property.propertyType == SerializedPropertyType.ObjectReference &&
                property.objectReferenceValue == null &&
                property.objectReferenceInstanceIDValue != 0 && 
                property.name != "m_PrefabParentObject" &&
                property.name != "m_PrefabInternal" &&
                property.name != "m_CorrespondingSourceObject"){

                result = true;
                if(obj.GetType().Name.Contains("ParticleSystemRenderer") && property.name.Contains("m_Mesh")){
                    UnityEngine.Debug.LogError(string.Format("{0} の {1} の RenderMode を Mesh に変更してMissingを修正して下さい。", path, obj.GetType().Name));
                }else{
                    UnityEngine.Debug.LogError(string.Format("{0} の {1} にMissingがあるので修正して下さい。 {2}", path, obj.GetType().Name, property.name));
                }
            }
        }
        return result;
    }
#endif
}