using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEditor;
using UnityEngine;
using UnityEngine.Serialization;

[Serializable]
public class RuleAsset
{
    public string bundle;
    public string path;
}

[Serializable]
public class RuleBundle
{
    public string name;
    public string[] assets;
}

public struct ShaderKey
{
    public string sorted_keywords;
    public Shader shader;
}

public class MaterialFile
{
    public string sorted_keywords;
    public string path;
    public Shader shader;
    public bool needDeleted = true;
}

public class ShaderMat
{
    public bool isInAb = false;
    public List<string> mats = new List<string>();
}



[Serializable]
public class BuildRule 
{
    [Tooltip("是否各自打包为多份AssetBundle")]
    public bool isMultiPiece = false;

    [Tooltip("AssetBundle的名称")]
    public string assetBundleName;

    [Tooltip("搜索路径")] 
    public string searchPath;

    [Tooltip("搜索通配符，多个之间请用,(逗号)隔开")] 
    public string searchPattern = "*";

    [Tooltip("重复资源打进的包名")]
    public string sharedAssetBundle = "default_shared";

    [Tooltip("按照子目录分包")]
    public bool isSubDirectory = false;


    public string[] GetAssets()
    {
        var getFiles = new List<string>();
        if (File.Exists(searchPath))
        {
            if (BuildRules.ValidateAsset(searchPath))
            {
                getFiles.Add(searchPath);
               
            }   

        }
        else
        {
            var patterns = searchPattern.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            if (!Directory.Exists(searchPath))
            {
                Debug.LogWarning("Rule searchPath not exist:" + searchPath);
                return new string[0];
            }

            foreach (var item in patterns)
            {
                var files = Directory.GetFiles(searchPath, item, SearchOption.AllDirectories);
                foreach (var file in files)
                {
                    if (Directory.Exists(file)) continue;
                    //var ext = Path.GetExtension(file).ToLower();
                    //if ((ext == ".fbx" || ext == ".anim") && !item.Contains(ext)) continue;
                    if (!BuildRules.ValidateAsset(file)) continue;
                    var asset = file.Replace("\\", "/");
                    getFiles.Add(asset);
                }
            }
        }
       

        return getFiles.ToArray();
    }
}

[Serializable]
public class BuildRules : ScriptableObject
{
    private readonly Dictionary<string, string> _asset2Bundles = new Dictionary<string, string>();
    private readonly Dictionary<string, string> _asset2DuplicatedBundles = new Dictionary<string, string>();
    private readonly Dictionary<string, string[]> _conflicted = new Dictionary<string, string[]>();
    private readonly List<string> _duplicated = new List<string>();
    private readonly Dictionary<string, HashSet<string>> _tracker = new Dictionary<string, HashSet<string>>();
    public BuildRule shaderRule = new BuildRule() { assetBundleName="shader"};
    public BuildRule defaultRule = new BuildRule { assetBundleName = "default_pack", searchPath="Assets/AssetBundles"};


    public string refsPath = "Assets/AssetBundles/Refs";
    public string generatedPath = "Assets/AssetBundles/Generated/Refs";
    public List<BuildRule> rules = new List<BuildRule>();
    //public RuleAsset[] ruleAssets = new RuleAsset[0];
    //public RuleBundle[] ruleBundles = new RuleBundle[0];

    private bool m_enableHotfix = false;


    #region API

    public void Apply()
    {
        Clear();
        ReadConfig();
       
        CollectAssets();
        AnalysisAssets();
        OptimizeAssets();
        OptimizeMaterialKeywords();
        Save();
    }

    void ReadConfig()
    {
        m_enableHotfix = false;
    }

    public AssetBundleBuild[] GetBuilds()
    {
        return BuildRulesBundleDatas.Load().GetBuilds();
       
    }

    #endregion

    #region Private

    internal static bool ValidateAsset(string asset)
    {
        if (!asset.StartsWith("Assets/")) return false;

        var ext = Path.GetExtension(asset).ToLower();
        return ext != ".dll" && ext != ".cs" && ext != ".meta" && ext != ".js" && ext != ".boo";
    }

    internal static bool IsScene(string asset)
    {
        return asset.EndsWith(".unity");
    }

    private static bool IsMaterial(string asset)
    {
        return asset.EndsWith(".mat");
    }
    private static bool IsShader(string asset)
    {
        return asset.EndsWith(".shader") || asset.EndsWith(".hlsl") || asset.EndsWith(".cginc");
    }


    private static string RuledAssetBundleName(string name)
    {
        return name.Replace("\\", "/").ToLower();
    }

    private void Track(string asset, string bundle)
    {
        HashSet<string> assets;
        if (!_tracker.TryGetValue(asset, out assets))
        {
            assets = new HashSet<string>();
            _tracker.Add(asset, assets);
        }

        assets.Add(bundle);
        if (assets.Count > 1)
        {
            string bundleName;
            _asset2Bundles.TryGetValue(asset, out bundleName);
            if (string.IsNullOrEmpty(bundleName))
            {
                _duplicated.Add(asset);
            }
        }
    }

    private Dictionary<string, List<string>> GetBundles()
    {
        var bundles = new Dictionary<string, List<string>>();
        foreach (var item in _asset2Bundles)
        {
            var bundle = item.Value;
            List<string> list;
            if (!bundles.TryGetValue(bundle, out list))
            {
                list = new List<string>();
                bundles[bundle] = list;
            }

            if (!list.Contains(item.Key)) list.Add(item.Key);
        }

        return bundles;
    }

    private void Clear()
    {
        _tracker.Clear();
        _duplicated.Clear();
        _conflicted.Clear();
        _asset2Bundles.Clear();
    }

    private void Save()
    {
        var getBundles = GetBundles();
        var bundleData = BuildRulesBundleDatas.Load();
        var ruleBundles = new RuleBundle[getBundles.Count];
        var i = 0;
        foreach (var item in getBundles)
        {
            ruleBundles[i] = new RuleBundle
            {
                name = item.Key,
                assets = item.Value.ToArray()
            };
            i++;
        }
        bundleData.ruleBundles = ruleBundles;
        bundleData.Save();
        EditorUtility.ClearProgressBar();
    }

    private static string[] BlacklistKeywords = new string[] {
        "_SHADOWS_SCREEN",
        "_MAIN_LIGHT_SHADOWS_CASCADE",
        "LIGHTMAP_SHADOW_MIXING",
        "VERTEXLIGHT_ON",
        "DIRLIGHTMAP_COMBINED",
        "SPOT",
        "DYNAMICLIGHTMAP_ON",
        "_ADDITIONAL_LIGHTS_VERTEX",
        "_ADDITIONAL_LIGHT_SHADOWS",
        "_MIXED_LIGHTING_SUBTRACTIVE",
        "FOG_EXP",
        "FOG_EXP2",
    };

    private bool IsSkipKeyword(List<string> keywords)
    {
      
        foreach(var s in BlacklistKeywords)
        {
            if(keywords.Contains(s))
            {
                return true;
            }
        }
        return false;
    }
    static string dummyAutoPath = "Assets/URP/Shader/dummyMat/Auto";
    private string GetNewAutoName()
    {
        for(int i = 1;i<int.MaxValue;i++)
        {
            string path = dummyAutoPath + "/dummyMat_" + i + ".mat";
            if(!File.Exists(path))
            {
                return path;
            }
        }
        return "";
    }

    private void OptimizeMaterialKeywords()
    {
        var allAssets = _asset2Bundles.Keys.ToArray();
        int i, max;
       

        Dictionary<ShaderKey, ShaderMat> shaders = new Dictionary<ShaderKey, ShaderMat>();
        List<string> keywordsTemp = new List<string>();
        i = 0;
        max = allAssets.Length;
        foreach (var matPath in allAssets)
        {
            i++;
            if (!IsMaterial(matPath))
            {
                continue;
            }
            if (matPath.StartsWith(dummyAutoPath))
            {
                continue;
            }
            if (EditorUtility.DisplayCancelableProgressBar(string.Format("检测材质keywords {0}/{1}", i, max), matPath,
             i / (float)max)) break;
            var mat = AssetDatabase.LoadAssetAtPath<Material>(matPath);
            if (mat != null)
            {
                keywordsTemp.Clear();
                keywordsTemp.AddRange(mat.shaderKeywords);
                keywordsTemp.Sort();
                if(keywordsTemp.Count<=0)
                {
                    continue;
                }
                if(IsSkipKeyword(keywordsTemp))
                {
                    continue;
                }
                var sorted_keywords = string.Join(" ", keywordsTemp);
                ShaderMat ruleShader = null;
                ShaderKey key = new ShaderKey();
                key.shader = mat.shader;
                key.sorted_keywords = sorted_keywords;
                if (!shaders.TryGetValue(key, out ruleShader))
                {
                    ruleShader = new ShaderMat();
                    shaders[key] = ruleShader;
                }
                ruleShader.mats.Add(matPath);
                if (_asset2Bundles.TryGetValue(matPath, out string abPath) && abPath == shaderRule.assetBundleName)
                {
                    ruleShader.isInAb = true;
                }


            }
        }
        i = 0;
        max = shaders.Count;

        var oldPaths = AssetDatabase.FindAssets("", new string[] { dummyAutoPath })
            .Select(x => AssetDatabase.GUIDToAssetPath(x))
            .Select(x=> {
                MaterialFile ms = new MaterialFile();
                ms.path = x;
                var mat = AssetDatabase.LoadAssetAtPath<Material>(x);
                if(mat == null)
                {
                    ms.needDeleted = true;
                }
                else
                {
                    keywordsTemp.Clear();
                    keywordsTemp.AddRange(mat.shaderKeywords);
                    keywordsTemp.Sort();
                    ms.shader = mat.shader;
                    ms.sorted_keywords = string.Join(" ", keywordsTemp);
                }
               
               
                return ms;
            })
            .ToList();
        
        List<string> newDummyPath = new List<string>();
        int dummyIndex = 0;
        foreach (var kv in shaders)
        {
            if (EditorUtility.DisplayCancelableProgressBar(string.Format("创建dummymat {0}/{1}", i, max), kv.Key.sorted_keywords,
            i / (float)max)) break;
            i++;
            if (!kv.Value.isInAb)
            {
                var existMat = oldPaths.Find(x => x.sorted_keywords == kv.Key.sorted_keywords && x.shader == kv.Key.shader);
                if(existMat != null)
                {
                    existMat.needDeleted = false;
                    continue;
                }
                kv.Value.mats.Sort();
                Material oldMat = AssetDatabase.LoadAssetAtPath<Material>(kv.Value.mats[0]);
                dummyIndex++;


                string toPath = GetNewAutoName();
                bool needCreate = false;
                Material newMat = AssetDatabase.LoadAssetAtPath<Material>(toPath);
                if (newMat == null)
                {
                    newMat = new Material(kv.Key.shader);
                    needCreate = true;
                }
                newMat.shader = oldMat.shader;
                newMat.CopyPropertiesFromMaterial(oldMat);
                newMat.shaderKeywords = oldMat.shaderKeywords;
                var textureId = newMat.GetTexturePropertyNameIDs();
                foreach (var id in textureId)
                {
                    newMat.SetTexture(id, null);
                }

                if (needCreate)
                {
                    AssetDatabase.CreateAsset(newMat, toPath);
                }
                else
                {
                    EditorUtility.SetDirty(newMat);
                }
               
                _asset2Bundles[toPath] = shaderRule.assetBundleName;
                newDummyPath.Add(toPath);


            }
        }

        foreach (var oldPath in oldPaths)
        {
            if (oldPath.needDeleted)
            {
                if (File.Exists(oldPath.path))
                    AssetDatabase.DeleteAsset(oldPath.path);
            }
        }

    }

    private void OptimizeAssets()
    {
        int i = 0, max = _conflicted.Count;
        foreach (var item in _conflicted)
        {
            if (EditorUtility.DisplayCancelableProgressBar(string.Format("优化冲突{0}/{1}", i, max), item.Key,
                i / (float)max)) break;
            var list = item.Value;
            foreach (var asset in list)
                if (!IsScene(asset))
                    _duplicated.Add(asset);
            i++;
        }

        for (i = 0, max = _duplicated.Count; i < max; i++)
        {
            var item = _duplicated[i];
            if (EditorUtility.DisplayCancelableProgressBar(string.Format("优化冗余{0}/{1}", i, max), item,
                i / (float)max)) break;
            OptimizeAsset(item);
        }

        var allAssets = _asset2Bundles.Keys.ToArray();

        foreach(var asset in allAssets)
        {
            if(IsShader(asset))
            {
                _asset2Bundles[asset] = shaderRule.assetBundleName;
            }
        }

        

    }

    private void AnalysisAssets()
    {
        var getBundles = GetBundles();
        int i = 0, max = getBundles.Count;
        foreach (var item in getBundles)
        {
            var bundle = item.Key;
            if (EditorUtility.DisplayCancelableProgressBar(string.Format("分析依赖{0}/{1}", i, max), bundle,
                i / (float)max)) break;
            var assetPaths = getBundles[bundle];
            if (assetPaths.Exists(IsScene) && !assetPaths.TrueForAll(IsScene))
                _conflicted.Add(bundle, assetPaths.ToArray());
            var dependencies = AssetDatabase.GetDependencies(assetPaths.ToArray(), true);
            if (dependencies.Length > 0)
                foreach (var asset in dependencies)
                    if (ValidateAsset(asset))
                    {
                       
                       
                        Track(asset, bundle);
                    }
                       
            i++;
        }

       

       
    }

    private void CollectAssets()
    {
        EditorUtility.DisplayProgressBar("收集默认资源", defaultRule.searchPath,0f);
        ApplyRule(defaultRule);
        EditorUtility.DisplayProgressBar("收集shader资源", shaderRule.searchPath, 0f);
        ApplyRule(shaderRule);
        
        for (int i = 0, max = rules.Count; i < max; i++)
        {
            var rule = rules[i];
            if (EditorUtility.DisplayCancelableProgressBar(string.Format("收集目录资源{0}/{1}", i, max), rule.searchPath,
                i / (float)max))
                break;
            if(!File.Exists(rule.searchPath))
            {
                ApplyRule(rule);
            }
        }

        for (int i = 0, max = rules.Count; i < max; i++)
        {
            var rule = rules[i];
            if (EditorUtility.DisplayCancelableProgressBar(string.Format("收集单个资源{0}/{1}", i, max), rule.searchPath,
                i / (float)max))
                break;
            if (File.Exists(rule.searchPath))
            {
                ApplyRule(rule);
            }
        }

        var list = new List<RuleAsset>();
        foreach (var item in _asset2Bundles)
            list.Add(new RuleAsset
            {
                path = item.Key,
                bundle = item.Value
            });
        list.Sort((a, b) => string.Compare(a.path, b.path, StringComparison.Ordinal));
        //ruleAssets = list.ToArray();
    }

    private void OptimizeAsset(string asset)
    {
        if(IsScene(asset))
        {
            _asset2Bundles[asset] = RuledAssetBundleName(_asset2DuplicatedBundles[asset]) + "_scene";
        }
        else
        {
            _asset2Bundles[asset] = RuledAssetBundleName(_asset2DuplicatedBundles[asset]);
        }
       
    }

    private static bool IsYaml(string asset)
    {
        return asset.EndsWith(".yaml");
    }

   

    private void ApplyRule(BuildRule rule)
    {
        var assets = rule.GetAssets();
       
        List<string> realAssets = new List<string>();
        realAssets.AddRange(assets);
            
        if (rule.isMultiPiece)
        {
            foreach (var asset in assets)
            {
                //string assetName = Path.GetFileNameWithoutExtension(asset);
                //_asset2Bundles[asset] = RuledAssetBundleName(rule.assetBundleName + "/" + assetName);
                string bundleName;
                if (!rule.isSubDirectory)
                {
                    string assetName = Path.GetFileNameWithoutExtension(asset);
                    bundleName = RuledAssetBundleName(rule.assetBundleName + "/" + assetName);
                }
                else
                {
                    string dirName = asset.Substring(rule.searchPath.Length, asset.Length - rule.searchPath.Length - Path.GetFileName(asset).Length - 1);
                    bundleName = RuledAssetBundleName(rule.assetBundleName + dirName);
                }
                _asset2Bundles[asset] = bundleName;
            }
        }
        else
        {
            foreach (var asset in assets)
            {
               
                _asset2Bundles[asset] = RuledAssetBundleName(rule.assetBundleName);
            }
                
        }

       
        if (File.Exists(rule.searchPath) && BuildRules.IsScene(rule.searchPath) && rule.searchPath.EndsWith("_baked.unity"))
        {
            if (BuildRules.ValidateAsset(rule.searchPath))
            {
              
                //检查导出的资源文件。
                var name = Path.GetFileNameWithoutExtension(rule.searchPath);

                var dir = Path.GetDirectoryName(rule.searchPath);
                var assetbundlefile = $"{dir}/{name}_assetbundle.txt";
                if (File.Exists(assetbundlefile))
                {
                    var reader = new StreamReader(File.OpenRead(assetbundlefile));
                    if (reader != null)
                    {
                        while (!reader.EndOfStream) {
                            string file = reader.ReadLine();
                            _asset2Bundles[file] = rule.sharedAssetBundle;
                            realAssets.Add(file);
                        } 
                        reader.Close();
                    }
                }
            }

        }
        var depends = new List<string>(realAssets);
        depends.AddRange(AssetDatabase.GetDependencies(realAssets.ToArray(), true));

        //foreach (var asset in depends)
        //{
            
        //    if (_asset2DuplicatedBundles.ContainsKey(asset) && _asset2DuplicatedBundles[asset] != rule.sharedAssetBundle)
        //        Debug.LogWarning("重复资源，被放到多个共享AB包里，资源：" + asset + "，将把" + _asset2DuplicatedBundles[asset] + "覆盖为" + rule.sharedAssetBundle);
        //}

        foreach (var asset in depends)
        {
            _asset2DuplicatedBundles[asset] = rule.sharedAssetBundle;
        }
    }


    #endregion
}
