using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;

/// <summary>
///     用于获得材质像素密度的方法 - 返回材质在一个MeshRenderer或者SkinnedMeshRenderer上大致像素/单位长度
/// </summary>
public class GetTextureDpi
{
    private readonly List<string> _prefabPaths = new List<string>();
    private readonly List<string> _scenePaths = new List<string>();
    private readonly List<TextureDpi> _textureDpi = new List<TextureDpi>();
    private Scene? _currentScene;
    private int _index;
    
    [MenuItem("ResourceTool/Get All Texture Dpi")]
    public static void GetAllTextureDpi()
    {
        var instance = new GetTextureDpi();
        instance.Start();
    }
    
    public GetTextureDpi()
    {
        var allPaths = AssetDatabase.GetAllAssetPaths();
        for (var i = 0; i < allPaths.Length; i++)
        {
            if (allPaths[i].StartsWith("Assets/"))
            {
                var assetType = AssetDatabase.GetMainAssetTypeAtPath(allPaths[i]);
                if (assetType == typeof(Texture2D))
                    _textureDpi.Add(new TextureDpi(allPaths[i]));
                else if (assetType == typeof(SceneAsset))
                    _scenePaths.Add(allPaths[i]);
                else if (assetType == typeof(GameObject))
                    _prefabPaths.Add(allPaths[i]);
            }
        }
    }

    public void Start()
    {
        _index = 0;
        EditorApplication.update = OnPrefabProcess;
    }

    private void OnPrefabProcess()
    {
        var finish = false;
        try
        {
            if (_index < _prefabPaths.Count)
            {
                if (!(AssetImporter.GetAtPath(_prefabPaths[_index]) is ModelImporter))
                {
                    var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(_prefabPaths[_index]);
                    if (prefab != null)
                        ReadFromOneGameObject(prefab, _prefabPaths[_index], false);
                }
            }
            else
            {
                finish = true;
            }
        }
        catch (Exception e)
        {
            Debug.LogError(e.ToString());
        }

        if (finish)
        {
            _index = 0;
            _currentScene = null;
            EditorApplication.update = OnSceneProcess;
        }
        else
        {
            Debug.Log(string.Format("完成预制物{0} / {1}", _index, _prefabPaths.Count));
            _index++;
        }
    }

    private void OnSceneProcess()
    {
        var finish = false;
        try
        {
            if (_index < _scenePaths.Count)
            {
                if (_currentScene == null)
                {
                    _currentScene = EditorSceneManager.OpenScene(_scenePaths[_index]);
                }
                else
                {
                    var gameObjects = _currentScene.Value.GetRootGameObjects();
                    for (var i = 0; i < gameObjects.Length; i++)
                        ReadFromOneGameObject(gameObjects[i], _scenePaths[_index], true);
                    System.Diagnostics.Debug.Assert(_currentScene != null, "_currentScene != null");
                    EditorSceneManager.CloseScene(_currentScene.Value, true);
                    _currentScene = null;
                }
            }
            else
            {
                finish = true;
            }
        }
        catch (Exception e)
        {
            Debug.LogError(e.ToString());
            _currentScene = null;
        }

        if (finish)
        {
            _currentScene = null;
            EditorApplication.update = null;
            WriteToFile();
        }
        // 如果是场景加载帧,则等候一帧再读取GameObject
        else if (_currentScene == null)
        {
            Debug.Log(string.Format("完成场景{0} / {1}", _index, _scenePaths.Count));
            _index++;
            _currentScene = null;
        }
    }

    private void WriteToFile()
    {
        var builder = new StringBuilder();
        builder.Append("Asset");
        builder.Append('\t');
        builder.Append("MaxDpi");
        builder.Append('\t');
        builder.Append("MinDpi");
        builder.Append('\t');
        builder.Append("Use In Prefab");
        builder.Append('\t');
        builder.Append("Use In Scene");
        for (var i = 0; i < _textureDpi.Count; i++)
            _textureDpi[i].WriteToBuilder(builder);
        var filePath = Application.dataPath + "/_Test/TextureDpi.txt";
        File.WriteAllText(filePath, builder.ToString());
        Debug.LogWarning("结果输出到文件 " + filePath);
    }

    private void ReadFromOneGameObject(GameObject gameObject, string refName, bool isScene)
    {
        var meshFilters = gameObject.GetComponentsInChildren<MeshFilter>();
        for (var i = 0; i < meshFilters.Length; i++)
        {
            var mesh = meshFilters[i].sharedMesh;
            if (mesh != null)
            {
                var meshRenderer = meshFilters[i].GetComponent<MeshRenderer>();
                if (meshRenderer != null)
                {
                    var materials = meshRenderer.sharedMaterials;
                    for (var j = 0; j < materials.Length; j++)
                    {
                        if (materials[j] == null)
                            Debug.LogError("Material is Null " + refName);
                        else
                            ReadFromOneMaterial(materials[j], mesh, meshRenderer.transform.localToWorldMatrix, refName,
                            isScene);
                    }
                }
            }
        }
    }

    private void ReadFromOneMaterial(Material material, Mesh mesh, Matrix4x4 matrix, string refName, bool isScene)
    {
        if (material.shader != null)
        {
            var count = ShaderUtil.GetPropertyCount(material.shader);
            for (var i = 0; i < count; i++)
                if (ShaderUtil.GetPropertyType(material.shader, i) == ShaderUtil.ShaderPropertyType.TexEnv)
                {
                    var name = ShaderUtil.GetPropertyName(material.shader, i);
                    var texture = material.GetTexture(name) as Texture2D;
                    if (texture != null)
                    {
                        var record = GetTextureDpiRecord(texture);
                        if (record != null)
                        {
                            // 特殊处理无Scale的法线贴图
                            const string t4MNormalHeader = "_BumpSplat";
                            const string t4MColorHeader = "_Splat";
                            var scaleName = name.StartsWith(t4MNormalHeader) ? t4MColorHeader + name.Substring(t4MNormalHeader.Length) : name;
                            var scale = material.GetTextureScale(scaleName);
                            var dpi = GetTextureDpiByMesh(texture, scale, mesh, matrix);
                            record.maxDpi = record.maxDpi == null ? dpi : Mathf.Max(record.maxDpi.Value, dpi);
                            record.minDpi = record.minDpi == null ? dpi : Mathf.Min(record.minDpi.Value, dpi);
                            record.AddRef(refName, isScene);
                        }
                    }
                }
        }
    }

    private TextureDpi GetTextureDpiRecord(Object texture)
    {
        TextureDpi result = null;
        var assetPath = AssetDatabase.GetAssetPath(texture);
        if (!string.IsNullOrEmpty(assetPath))
            for (var i = 0; i < _textureDpi.Count; i++)
                if (_textureDpi[i].path.Equals(assetPath, StringComparison.OrdinalIgnoreCase))
                {
                    result = _textureDpi[i];
                    break;
                }

        return result;
    }

    public static float GetTextureDpiByMesh(Texture2D texture, Vector2 uvScale, Mesh mesh, Matrix4x4 meshMatrix)
    {
        var result = 0f;
        var importError = false;
        ModelImporter importer = null;
        if (!mesh.isReadable)
        {
            importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(mesh)) as ModelImporter;
            if (importer == null)
            {
                importError = true;
            }
            else
            {
                importer.isReadable = true;
                importer.SaveAndReimport();
            }
        }

        if (importError)
        {
            Debug.LogError("模型无法读取,也无法正确获得Importer");
        }
        else
        {
            var triangles = mesh.triangles;
            var dpiArray = new TriangleDpi[triangles.Length / 3];
            if (dpiArray.Length > 0)
            {
                var verticles = mesh.vertices;
                var uv = mesh.uv;
                // 逐一扫描三角形,获得每个三角形的大致Dpi
                for (var i = 0; i < dpiArray.Length; i++)
                {
                    var id0 = triangles[i * 3];
                    var id1 = triangles[i * 3 + 1];
                    var id2 = triangles[i * 3 + 2];
                    // 各边长值必然是最极端数值,因此最大Dpi必然是三边最大值
                    var point0 = meshMatrix.MultiplyPoint3x4(verticles[id0]);
                    var point1 = meshMatrix.MultiplyPoint3x4(verticles[id1]);
                    var point2 = meshMatrix.MultiplyPoint3x4(verticles[id2]);
                    var a = Vector3.Distance(point0, point1);
                    var b = Vector3.Distance(point1, point2);
                    var c = Vector3.Distance(point2, point0);
                    if (a > 0 && b > 0 && c > 0)
                    {
                        var p = (a + b + c) * 0.5f;
                        var size = Mathf.Sqrt(p * (p - a) * (p - b) * (p - c));
                        var uv0 = uv[id0];
                        uv0.x *= uvScale.x * texture.width;
                        uv0.y *= uvScale.y * texture.height;
                        var uv1 = uv[id1];
                        uv1.x *= uvScale.x * texture.width;
                        uv1.y *= uvScale.y * texture.height;
                        var uv2 = uv[id2];
                        uv2.x *= uvScale.x * texture.width;
                        uv2.y *= uvScale.y * texture.height;
                        var dpi0 = Vector2.Distance(uv0, uv1) / a;
                        var dpi1 = Vector2.Distance(uv1, uv2) / b;
                        var dpi2 = Vector2.Distance(uv2, uv0) / c;
                        dpiArray[i] = new TriangleDpi((double) (dpi0 + dpi1 + dpi2) / 3f, size);
                    }
                    else
                    {
                        dpiArray[i] = new TriangleDpi(0d, 0d);
                    }
                }
            }

            var weight = 0d;
            for (var i = 0; i < dpiArray.Length; i++)
                weight += dpiArray[i].size;
            if (weight > 0f)
            {
                var dpi = 0d;
                for (var i = 0; i < dpiArray.Length; i++)
                    dpi += dpiArray[i].dpi * dpiArray[i].size / weight;
                result = (float) dpi;
            }
        }

        if (importer != null)
        {
            importer.isReadable = false;
            importer.SaveAndReimport();
        }

        return result;
    }

    private struct TriangleDpi
    {
        public readonly double dpi;
        public readonly double size;

        public TriangleDpi(double dpi, double size)
        {
            this.dpi = dpi;
            this.size = size;
        }
    }

    private class TextureDpi
    {
        public readonly string path;
        private readonly List<string> prefabRef = new List<string>();
        private readonly List<string> sceneRef = new List<string>();
        public float? maxDpi;
        public float? minDpi;

        public TextureDpi(string assetPath)
        {
            path = assetPath;
        }

        public void AddRef(string refName, bool isScene)
        {
            var list = isScene ? sceneRef : prefabRef;
            if (!list.Contains(refName))
                list.Add(refName);
        }

        public void WriteToBuilder(StringBuilder builder)
        {
            if (builder.Length > 0)
                builder.Append('\n');
            builder.Append(path);
            builder.Append('\t');
            if (maxDpi == null)
                builder.Append("N/A");
            else
                builder.Append(maxDpi.Value);
            builder.Append('\t');
            if (minDpi == null)
                builder.Append("N/A");
            else
                builder.Append(minDpi.Value);
            builder.Append('\t');
            if (prefabRef.Count < 1)
                builder.Append("N/A");
            else
                for (var i = 0; i < prefabRef.Count; i++)
                {
                    if (i > 0)
                        builder.Append("; ");
                    builder.Append(prefabRef[i]);
                }

            builder.Append('\t');
            if (sceneRef.Count < 1)
                builder.Append("N/A");
            else
                for (var i = 0; i < sceneRef.Count; i++)
                {
                    if (i > 0)
                        builder.Append("; ");
                    builder.Append(sceneRef[i]);
                }
        }
    }
}