#if UNITY_EDITOR
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;

namespace Coffee.UIExtensions
{
    public class MaterialResolver
    {
        static readonly StringBuilder s_StringBuilder = new StringBuilder();

        static readonly Dictionary<string, Material> s_MaterialMap = new Dictionary<string, Material>();

        public static Material GetOrGenerateMaterialVariant(Shader shader, params object[] append)
        {
            if (!shader)
            {
                return null;
            }

            Material mat = null;
            string variantName = GetVariantName(shader, append);
            if (s_MaterialMap.TryGetValue(variantName, out mat) && mat)
            {
                return mat;
            }

            string[] keywords = append.Where(x => 0 < (int)x)
                .Select(x => x.ToString().ToUpper())
                .ToArray();
            mat = GetMaterial(shader, append);
            if (mat)
            {
                if (!mat.shaderKeywords.OrderBy(x => x).SequenceEqual(keywords.OrderBy(x => x)))
                {
                    mat.shaderKeywords = keywords;
                    EditorUtility.SetDirty(mat);
                    if (!Application.isPlaying)
                    {
                        EditorApplication.delayCall += AssetDatabase.SaveAssets;
                    }
                }
                return mat;
            }

            if (s_MaterialMap.TryGetValue(variantName, out mat) && mat)
            {
                return mat;
            }

            Debug.Log("Generate material : " + variantName);
            mat = new Material(shader);
            mat.shaderKeywords = keywords;

            mat.name = variantName;
            mat.hideFlags |= HideFlags.NotEditable;
            s_MaterialMap[variantName] = mat;

            bool isMainAsset = append.Cast<int>().All(x => x == 0);
            EditorApplication.delayCall += () => SaveMaterial(mat, shader, isMainAsset);
            return mat;
        }

        static void SaveMaterial(Material mat, Shader shader, bool isMainAsset)
        {
            string materialPath = GetDefaultMaterialPath(shader);

#if UIEFFECT_SEPARATE
			string dir = Path.GetDirectoryName(materialPath);
			materialPath = Path.Combine(Path.Combine(dir, "Separated"), mat.name + ".mat");
			isMainAsset = true;
#endif
            if (isMainAsset)
            {
                Directory.CreateDirectory(Path.GetDirectoryName(materialPath));
                AssetDatabase.CreateAsset(mat, materialPath);
            }
            else
            {
                GetOrGenerateMaterialVariant(shader);
                mat.hideFlags |= HideFlags.HideInHierarchy;
                AssetDatabase.AddObjectToAsset(mat, materialPath);
            }
            AssetDatabase.SaveAssets();
        }

        public static Material GetMaterial(Shader shader, params object[] append)
        {
            string variantName = GetVariantName(shader, append);
            return AssetDatabase.FindAssets("t:Material " + Path.GetFileName(shader.name))
            .Select(x => AssetDatabase.GUIDToAssetPath(x))
            .SelectMany(x => AssetDatabase.LoadAllAssetsAtPath(x))
            .OfType<Material>()
            .FirstOrDefault(x => x.name == variantName);
        }

        public static string GetDefaultMaterialPath(Shader shader)
        {
            var name = Path.GetFileName(shader.name);
            return AssetDatabase.FindAssets("t:Material " + name)
            .Select(x => AssetDatabase.GUIDToAssetPath(x))
            .FirstOrDefault(x => Path.GetFileNameWithoutExtension(x) == name)
            ?? ("Assets/" + name + ".mat");
        }

        public static string GetVariantName(Shader shader, params object[] append)
        {
            s_StringBuilder.Length = 0;

#if UIEFFECT_SEPARATE
			s_StringBuilder.Append("[Separated] ");
#endif
            s_StringBuilder.Append(Path.GetFileName(shader.name));
            foreach (object mode in append.Where(x => 0 < (int)x))
            {
                s_StringBuilder.Append("-");
                s_StringBuilder.Append(mode.ToString());
            }
            return s_StringBuilder.ToString();
        }
    }
}
#endif