using System;
using System.IO;
using System.Linq;
using BundleV2;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;

/// <summary>
///     通用的资源配置清理以及资源修改的类型
/// </summary>
public static class PurgeAssets
{
    [MenuItem("Bundle V2/Purge/Audio")]
    public static void PurgeAudio()
    {
        var audioList = (from assetPath in AssetDatabase.GetAllAssetPaths()
            where assetPath.StartsWith(AssetConst.nonInternalHeader)
            where EditorAssetConst.audioExtensions.Contains(Path.GetExtension(assetPath))
            let importer = AssetImporter.GetAtPath(assetPath) as AudioImporter
            where importer
            select importer).ToArray();
        var count = 0;
        for (var i = 0; i < audioList.Length; i++)
        {
            EditorUtility.DisplayProgressBar("Purge Audio", string.Format("{0} / {1}", i + 1, audioList.Length),
                (float) i / audioList.Length);
            var audioImporter = audioList[i];
            var dirty = false;
            var defaultSettings = audioImporter.defaultSampleSettings;
            var audioClip = AssetDatabase.LoadAssetAtPath<AudioClip>(audioImporter.assetPath);
            // 音乐或者环境音效
            if (audioClip.length > 10f)
            {
                if (defaultSettings.compressionFormat != AudioCompressionFormat.Vorbis)
                {
                    dirty = true;
                    defaultSettings.compressionFormat = AudioCompressionFormat.Vorbis;
                }

                if (defaultSettings.loadType != AudioClipLoadType.Streaming)
                {
                    dirty = true;
                    defaultSettings.loadType = AudioClipLoadType.Streaming;
                }
            }
            // 持续类技能或者语音
            else if (audioClip.length > 3f)
            {
                if (defaultSettings.compressionFormat != AudioCompressionFormat.ADPCM)
                {
                    dirty = true;
                    defaultSettings.compressionFormat = AudioCompressionFormat.ADPCM;
                }

                if (defaultSettings.loadType != AudioClipLoadType.CompressedInMemory)
                {
                    dirty = true;
                    defaultSettings.loadType = AudioClipLoadType.CompressedInMemory;
                }
            }
            // 瞬发技能
            else
            {
                if (defaultSettings.compressionFormat != AudioCompressionFormat.ADPCM)
                {
                    dirty = true;
                    defaultSettings.compressionFormat = AudioCompressionFormat.ADPCM;
                }

                if (defaultSettings.loadType != AudioClipLoadType.DecompressOnLoad)
                {
                    dirty = true;
                    defaultSettings.loadType = AudioClipLoadType.DecompressOnLoad;
                }
            }

            if (defaultSettings.quality != 0.7f)
            {
                dirty = true;
                defaultSettings.quality = 0.7f;
            }

            if (dirty)
            {
                count++;
                audioImporter.defaultSampleSettings = defaultSettings;
                audioImporter.SaveAndReimport();
            }
        }

        EditorUtility.ClearProgressBar();
        Debug.LogWarning(string.Format("Purge Audio process on {0} files!", count));
    }

    [MenuItem("ProTool/PurgeAssets/NormalMap", false, 4)]
    public static void PurgeNormalMaps()
    {
        FixRgb();
    }

    [MenuItem("Bundle V2/Purge/Materials")]
    public static void PurgeMaterials()
    {
        var materialPaths = (from materialPath in AssetDatabase.GetAllAssetPaths()
            where materialPath.IsCommonAssetPath()
            where ".mat".Equals(Path.GetExtension(materialPath), StringComparison.OrdinalIgnoreCase)
            select materialPath).ToArray();
        for (var i = 0; i < materialPaths.Length; i++)
        {
            var materialPath = materialPaths[i];
            EditorUtility.DisplayProgressBar("Purge Material " + materialPath,
                string.Format("{0} / {1}", i, materialPaths.Length), (float) i / materialPaths.Length);
            var material = AssetDatabase.LoadAssetAtPath<Material>(materialPath);
            if (material != null)
                PurgeOneMaterial(material);
        }

        EditorUtility.ClearProgressBar();
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
    }

    [MenuItem("ProTool/PurgeAssets/Components/Start", false, 4)]
    public static void PurgeComponentsStart()
    {
        var action = new PurgeComponentAction(false);
        action.Start();
    }

    [MenuItem("ProTool/PurgeAssets/Components/Continue", false, 4)]
    public static void PurgeComponentsDelta()
    {
        var action = new PurgeComponentAction(true);
        action.Start();
    }

    [MenuItem("ProTool/PurgeAssets/PlatformSettings", false, 4)]
    public static void PurgePlatformSettings()
    {
        var action = new PurgeTexturePlatform();
        action.Start();
    }

    [MenuItem("ProTool/PurgeAssets/AssetNames", false, 4)]
    public static void PurgeAssetNames()
    {
        var assetPaths = (from assetPath in AssetDatabase.GetAllAssetPaths()
            where assetPath.IsCommonAssetPath()
            let extension = Path.GetExtension(assetPath)
            where !string.IsNullOrEmpty(extension) &&
                  !extension.Equals(".cs")
            let fileName = Path.GetFileName(assetPath)
            where !string.IsNullOrEmpty(fileName) &&
                  fileName.Contains(' ')
            select fileName).ToArray();
        for (var i = 0; i < assetPaths.Length; i++)
        {
            var assetPath = assetPaths[i];
            var dir = assetPath.MoveUp();
            var fileName = assetPath.Substring(dir.Length);
            fileName = fileName.Replace(' ', '_');
            fileName = dir.Open(fileName);
            AssetDatabase.MoveAsset(assetPath, fileName);
        }
    }

    [MenuItem("Bundle V2/Purge/Mesh Materials")]
    public static void PurgeMeshMaterials()
    {
        var meshMaterialAction = new PurgeMeshMaterialAction();
        meshMaterialAction.Start();
    }

    [MenuItem("Scene/Purge Components", false, 4)]
    public static void PurgeSceneComponents()
    {
        var rootObjects = SceneManager.GetActiveScene().GetRootGameObjects();
        for (var i = 0; i < rootObjects.Length; i++)
        {
            var rootObject = rootObjects[i];
            PurgeOneGameObject(rootObject.transform);
        }
    }

    private static bool ParticleHasExtraMaterial(ParticleSystemRenderer particle)
    {
        var result = false;
        for (var i = 2; i < particle.sharedMaterials.Length; i++)
            if (particle.sharedMaterials[i] == null)
            {
                result = true;
                break;
            }

        return result;
    }

    private static bool ParticleNotFunctional(ParticleSystemRenderer renderer, ParticleSystem particle)
    {
        var result = renderer.sharedMaterial == null;
        if (result)
            result = !particle.emission.enabled;
        return result;
    }

    public static bool PurgeOneGameObject(string assetPath)
    {
        var result = false;
        var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
        if (prefab != null)
        {
            var instance = Object.Instantiate(prefab);
            if (PurgeOneGameObject(instance.transform))
            {
                result = true;
                Debug.LogWarning("Purge Successful at " + assetPath);
                PrefabUtility.ReplacePrefab(instance, prefab, ReplacePrefabOptions.ReplaceNameBased);
            }
            else
            {
                EditorUtility.SetDirty(prefab);
            }

            Object.DestroyImmediate(instance);
        }

        return result;
    }

    public static bool PurgeOneGameObject(Transform root)
    {
        var components = root.GetComponents<Component>();
        var componentDirty = false;
        for (var i = 0; i < components.Length; i++)
            if (components[i] == null)
            {
                componentDirty = true;
                break;
            }

        if (componentDirty)
        {
            var serializedObject = new SerializedObject(root.gameObject);
            var property = serializedObject.FindProperty("m_Component");
            if (property.isArray && property.arraySize == components.Length)
            {
                for (var i = components.Length - 1; i >= 0; i--)
                    if (components[i] == null)
                        property.DeleteArrayElementAtIndex(i);
            }
            else
            {
                Debug.LogError("异常情况 - 序列化组件数目同实际组件不匹配!");
            }

            serializedObject.ApplyModifiedProperties();
        }

        var particleDirty = false;
        var particleRenderer = root.GetComponent<ParticleSystemRenderer>();
        var particleSystem = root.GetComponent<ParticleSystem>();
        if (particleRenderer != null && particleSystem != null)
        {
            if (ParticleNotFunctional(particleRenderer, particleSystem))
            {
                particleDirty = true;
                Object.DestroyImmediate(particleSystem);
            }
            else
            {
                if (ParticleHasExtraMaterial(particleRenderer))
                {
                    particleDirty = true;
                    var count = Mathf.Min(particleRenderer.sharedMaterials.Length, 2);
                    var array = new Material[count];
                    for (var j = 0; j < count; j++)
                        array[j] = particleRenderer.sharedMaterials[j];
                    particleRenderer.sharedMaterials = array;
                }

                if (particleRenderer.renderMode != ParticleSystemRenderMode.Mesh && particleRenderer.mesh != null)
                {
                    particleDirty = true;
                    particleRenderer.mesh = null;
                }
            }
        }

        var dirty = componentDirty || particleDirty;
        foreach (Transform child in root)
            if (PurgeOneGameObject(child))
                dirty = true;
        return dirty;
    }

    public static void PurgeOneMaterial(Material material)
    {
        if (material.shader != null)
        {
            var shaderPropertiesCount = ShaderUtil.GetPropertyCount(material.shader);
            var shaderProperties = new string[shaderPropertiesCount];
            for (var i = 0; i < shaderPropertiesCount; i++)
                shaderProperties[i] = ShaderUtil.GetPropertyName(material.shader, i);
            var serializedObject = new SerializedObject(material);
            // ReSharper disable once ReplaceWithSingleAssignment.False
            var dirty = false;
            if (PurgeOneMaterialProperty(serializedObject, shaderProperties, "m_SavedProperties.m_TexEnvs"))
                dirty = true;
            if (PurgeOneMaterialProperty(serializedObject, shaderProperties, "m_SavedProperties.m_Floats"))
                dirty = true;
            if (PurgeOneMaterialProperty(serializedObject, shaderProperties, "m_SavedProperties.m_Colors"))
                dirty = true;
            if (dirty)
            {
                Debug.LogWarning("Purge Material " + AssetDatabase.GetAssetPath(material));
                serializedObject.ApplyModifiedProperties();
            }

            EditorUtility.SetDirty(material);
        }
    }

    private static bool PurgeOneMaterialProperty(SerializedObject serializedObject, string[] allowProperties,
        string propertyName)
    {
        var dirty = false;
        var properties = serializedObject.FindProperty(propertyName);
        if (properties != null && properties.isArray)
            for (var i = properties.arraySize - 1; i >= 0; i--)
            {
                var propName = properties.GetArrayElementAtIndex(i).displayName;
                var exist = allowProperties.Contain(propName);
                if (!exist)
                {
                    dirty = true;
                    properties.DeleteArrayElementAtIndex(i);
                }
            }

        return dirty;
    }

    public static void FixRgb()
    {
        var pngPaths = (from path in AssetDatabase.GetAllAssetPaths()
            let extension = Path.GetExtension(path)
            where !string.IsNullOrEmpty(extension) &&
                  extension.Equals(".png", StringComparison.InvariantCultureIgnoreCase)
            select path).ToArray();
        var helper = new DtxNormalFix(pngPaths);
        helper.Start();
    }

    private class DtxNormalFix
    {
        private readonly string[] _pngPaths;
        private int _index;

        public DtxNormalFix(string[] paths)
        {
            _pngPaths = paths;
        }

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

        private void OnEditorUpdate()
        {
            try
            {
                if (_index < _pngPaths.Length)
                {
                    var path = _pngPaths[_index];
                    NormalConvert.ConvertOneNormal(path);
                    _index++;
                    Debug.LogWarning("Finish " + _index + " / " + _pngPaths.Length);
                }
                else
                {
                    Debug.LogWarning("Finish All");
                    EditorApplication.update = null;
                }
            }
            catch (Exception e)
            {
                Debug.LogError(e.ToString());
                EditorApplication.update = null;
            }
        }
    }

    private class PurgeComponentAction : EditorPerFrameActionBase
    {
        private const string _deltaKey = "PurgeComponent";
        private readonly string[] _assetPaths;
        private int _index;

        public PurgeComponentAction(bool useDelta)
        {
            _assetPaths = (from assetPath in AssetDatabase.GetAllAssetPaths()
                where assetPath.IsCommonAssetPath()
                where ".prefab".Equals(Path.GetExtension(assetPath))
                orderby assetPath
                select assetPath).ToArray();
            if (useDelta)
                _index = PlayerPrefs.GetInt(_deltaKey, default(int));
            else
                PlayerPrefs.SetInt(_deltaKey, _index);
        }

        protected override int GetCurrentIndex()
        {
            return _index;
        }

        protected override int GetTotalCount()
        {
            return _assetPaths.Length;
        }

        protected override void PerFrameAction()
        {
            var assetPath = _assetPaths[_index];
            if (PurgeOneGameObject(assetPath))
            {
                AssetDatabase.SaveAssets();
                AssetDatabase.Refresh();
                // 需要高密度的清空,防止溢出
                Resources.UnloadUnusedAssets();
            }

            PlayerPrefs.SetInt(_deltaKey, _index);
            _index++;
        }
    }

    private class PurgeTexturePlatform : EditorPerFrameActionBase
    {
        private readonly string[] _assetPaths;
        private int _index;

        public PurgeTexturePlatform()
        {
            _assetPaths = (from assetPath in AssetDatabase.GetAllAssetPaths()
                where assetPath.IsCommonAssetPath()
                let assetType = AssetDatabase.GetMainAssetTypeAtPath(assetPath)
                where assetType == typeof(Texture2D) || assetType == typeof(Sprite)
                select assetPath).ToArray();
        }

        protected override int GetCurrentIndex()
        {
            return _index;
        }

        protected override int GetTotalCount()
        {
            return _assetPaths.Length;
        }

        protected override void PerFrameAction()
        {
            var assetPath = _assetPaths[_index];
            _index++;
            var importer = AssetImporter.GetAtPath(assetPath) as TextureImporter;
            if (importer != null)
            {
                importer.ClearPlatformTextureSettings(PlatformName.GetCurrentPlatformName());
                importer.SaveAndReimport();
            }
        }
    }

    private class PurgeMeshMaterialAction : EditorPerFrameActionBase
    {
        private readonly string[] _assetPaths;
        private readonly Material _dummy;
        private int _count;
        private int _index;

        public PurgeMeshMaterialAction()
        {
            _assetPaths = (from assetPath in AssetDatabase.GetAllAssetPaths()
                where assetPath.StartsWith(AssetConst.nonInternalHeader)
                where AssetConst.modelExtension.Equals(Path.GetExtension(assetPath),
                    StringComparison.OrdinalIgnoreCase)
                select assetPath).ToArray();
            _dummy = AssetDatabase.LoadAssetAtPath<Material>("Assets/Project/GameRes/Material/shadow1" +
                                                             AssetConst.materialExtension);
        }

        protected override int GetCurrentIndex()
        {
            return _index;
        }

        protected override int GetTotalCount()
        {
            return _assetPaths.Length;
        }

        protected override void PerFrameAction()
        {
            var assetPath = _assetPaths[_index];
            var importer = AssetImporter.GetAtPath(assetPath) as ModelImporter;
            if (importer && !importer.importMaterials)
            {
                _count++;
                importer.importMaterials = true;
                importer.materialName = ModelImporterMaterialName.BasedOnMaterialName;
                importer.materialLocation = ModelImporterMaterialLocation.InPrefab;
                importer.materialSearch = ModelImporterMaterialSearch.Local;
                // 刷新数据,以便或者真实的材质名词
                importer.SaveAndReimport();
                AddRemapMaterials(importer);
                Debug.LogWarning("Purge Fbx Material from not import for " + assetPath);
                importer.SaveAndReimport();
                Resources.UnloadUnusedAssets();
            }
            // else if (importer.materialLocation == ModelImporterMaterialLocation.InPrefab)
            // {
            //     var update = false;
            //     if (importer.materialName != ModelImporterMaterialName.BasedOnMaterialName ||
            //         importer.materialSearch != ModelImporterMaterialSearch.Local)
            //     {
            //         update = true;
            //     }
            //     else
            //     {
            //         var external = importer.GetExternalObjectMap();
            //         foreach (var keyValue in external)
            //             if (keyValue.Key.type == typeof(Material))
            //             {
            //                 var material = keyValue.Value as Material;
            //                 if (material)
            //                 {
            //                     if (!material.shader)
            //                     {
            //                         update = true;
            //                         break;
            //                     }
            //
            //                     if (material.shader.name == "Standard")
            //                     {
            //                         update = true;
            //                         break;
            //                     }
            //                 }
            //             }
            //     }
            //
            //     if (update)
            //     {
            //         importer.materialName = ModelImporterMaterialName.BasedOnMaterialName;
            //         importer.materialSearch = ModelImporterMaterialSearch.Local;
            //         importer.materialLocation = ModelImporterMaterialLocation.InPrefab;
            //         importer.SaveAndReimport();
            //         AddRemapMaterials(importer);
            //         Debug.LogWarning("Purge Fbx Material from not dummy for " + assetPath);
            //         importer.SaveAndReimport();
            //         Resources.UnloadUnusedAssets();
            //     }
            // }
            _index++;
        }

        private void AddRemapMaterials(ModelImporter importer)
        {
            var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(importer.assetPath);
            var rendererArray = prefab.GetComponentsInChildren<Renderer>(true);
            foreach (var renderer in rendererArray)
            {
                var materials = renderer.sharedMaterials;
                for (var i = 0; i < materials.Length; i++)
                    if (materials[i])
                    {
                        var name = materials[i].name;
                        importer.AddRemap(
                            new AssetImporter.SourceAssetIdentifier(typeof(Material), name), _dummy);
                    }
            }
        }

        protected override void OnFinish()
        {
            Debug.LogWarning("Purge Fbx Count: " + _count);
            base.OnFinish();
        }
    }
}