using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;

using UnityEditor.Build.Pipeline;
using UnityEditor.Build.Pipeline.Interfaces;

namespace YooAsset.Editor
{
	public class ManifestContext : IContextObject
	{
		internal PackageManifest Manifest;
	}

	[TaskAttribute("创建清单文件")]
	public class TaskCreateManifest : IBuildTask
	{
		void IBuildTask.Run(BuildContext context)
		{
			CreateManifestFile(context);
		}

		/// <summary>
		/// 创建补丁清单文件到输出目录
		/// </summary>
		private void CreateManifestFile(BuildContext context)
		{
			var buildMapContext = context.GetContextObject<BuildMapContext>();
			var buildParametersContext = context.GetContextObject<BuildParametersContext>();
			var buildParameters = buildParametersContext.Parameters;
			string packageOutputDirectory = buildParametersContext.GetPackageOutputDirectory();

			// 创建新补丁清单
			PackageManifest manifest = new PackageManifest();
			manifest.FileVersion = YooAssetSettings.ManifestFileVersion;
			manifest.EnableAddressable = buildMapContext.Command.EnableAddressable;
			manifest.LocationToLower = buildMapContext.Command.LocationToLower;
			manifest.IncludeAssetGUID = buildMapContext.Command.IncludeAssetGUID;
			manifest.OutputNameStyle = (int)buildParameters.OutputNameStyle;
			manifest.PackageName = buildParameters.PackageName;
			manifest.PackageVersion = buildParameters.PackageVersion;

			// 填充资源包集合
			manifest.BundleList = GetAllPackageBundle(context);
			CacheBundleIDs(manifest);

			// 填充主资源集合
			manifest.AssetList = GetAllPackageAsset(context, manifest);

			// 更新Unity内置资源包的引用关系
			if (buildParameters.BuildPipeline == EBuildPipeline.ScriptableBuildPipeline)
			{
				if (buildParameters.BuildMode == EBuildMode.IncrementalBuild)
				{
					var buildResultContext = context.GetContextObject<TaskBuilding_SBP.BuildResultContext>();
					UpdateBuiltInBundleReference(manifest, buildResultContext, buildMapContext.Command.ShadersBundleName);
				}
			}

			// 更新资源包之间的引用关系
			if (buildParameters.BuildPipeline == EBuildPipeline.ScriptableBuildPipeline)
			{
				if (buildParameters.BuildMode == EBuildMode.IncrementalBuild)
				{
					var buildResultContext = context.GetContextObject<TaskBuilding_SBP.BuildResultContext>();
					UpdateScriptPipelineReference(manifest, buildResultContext);
				}
			}

			// 更新资源包之间的引用关系
			if (buildParameters.BuildPipeline == EBuildPipeline.BuiltinBuildPipeline)
			{
				if (buildParameters.BuildMode != EBuildMode.SimulateBuild)
				{
					var buildResultContext = context.GetContextObject<TaskBuilding.BuildResultContext>();
					UpdateBuiltinPipelineReference(manifest, buildResultContext);
				}
			}

			// 创建补丁清单文本文件
			{
				string fileName = YooAssetSettingsData.GetManifestJsonFileName(buildParameters.PackageName, buildParameters.PackageVersion);
				string filePath = $"{packageOutputDirectory}/{fileName}";
				ManifestTools.SerializeToJson(filePath, manifest);
				BuildLogger.Log($"创建补丁清单文件：{filePath}");
			}

			// 创建补丁清单二进制文件
			string packageHash;
			{
				string fileName = YooAssetSettingsData.GetManifestBinaryFileName(buildParameters.PackageName, buildParameters.PackageVersion);
				string filePath = $"{packageOutputDirectory}/{fileName}";
				ManifestTools.SerializeToBinary(filePath, manifest);
				packageHash = HashUtility.FileMD5(filePath);
				BuildLogger.Log($"创建补丁清单文件：{filePath}");

				ManifestContext manifestContext = new ManifestContext();
				byte[] bytesData = FileUtility.ReadAllBytes(filePath);
				manifestContext.Manifest = ManifestTools.DeserializeFromBinary(bytesData);
				context.SetContextObject(manifestContext);
			}

			// 创建补丁清单哈希文件
			{
				string fileName = YooAssetSettingsData.GetPackageHashFileName(buildParameters.PackageName, buildParameters.PackageVersion);
				string filePath = $"{packageOutputDirectory}/{fileName}";
				FileUtility.WriteAllText(filePath, packageHash);
				BuildLogger.Log($"创建补丁清单哈希文件：{filePath}");
			}

			// 创建补丁清单版本文件
			{
				string fileName = YooAssetSettingsData.GetPackageVersionFileName(buildParameters.PackageName);
				string filePath = $"{packageOutputDirectory}/{fileName}";
				FileUtility.WriteAllText(filePath, buildParameters.PackageVersion);
				BuildLogger.Log($"创建补丁清单版本文件：{filePath}");
			}
		}

		/// <summary>
		/// 获取资源包列表
		/// </summary>
		private List<PackageBundle> GetAllPackageBundle(BuildContext context)
		{
			var buildMapContext = context.GetContextObject<BuildMapContext>();

			List<PackageBundle> result = new List<PackageBundle>(1000);
			foreach (var bundleInfo in buildMapContext.Collection)
			{
				var packageBundle = bundleInfo.CreatePackageBundle();
				result.Add(packageBundle);
			}
			return result;
		}

		/// <summary>
		/// 获取资源列表
		/// </summary>
		private List<PackageAsset> GetAllPackageAsset(BuildContext context, PackageManifest manifest)
		{
			var buildMapContext = context.GetContextObject<BuildMapContext>();

			List<PackageAsset> result = new List<PackageAsset>(1000);
			foreach (var bundleInfo in buildMapContext.Collection)
			{
				var assetInfos = bundleInfo.GetAllManifestAssetInfos();
				foreach (var assetInfo in assetInfos)
				{
					PackageAsset packageAsset = new PackageAsset();
					packageAsset.Address = buildMapContext.Command.EnableAddressable ? assetInfo.Address : string.Empty;
					packageAsset.AssetPath = assetInfo.AssetPath;
					packageAsset.AssetGUID = buildMapContext.Command.IncludeAssetGUID ? assetInfo.AssetGUID : string.Empty;
					packageAsset.AssetTags = assetInfo.AssetTags.ToArray();
					packageAsset.BundleID = GetCachedBundleID(assetInfo.BundleName);
					packageAsset.DependIDs = GetAssetBundleDependIDs(packageAsset.BundleID, assetInfo, manifest);
					result.Add(packageAsset);
				}
			}
			return result;
		}
		private int[] GetAssetBundleDependIDs(int mainBundleID, BuildAssetInfo assetInfo, PackageManifest manifest)
		{
			HashSet<int> result = new HashSet<int>();
			foreach (var dependAssetInfo in assetInfo.AllDependAssetInfos)
			{
				if (dependAssetInfo.HasBundleName())
				{
					int bundleID = GetCachedBundleID(dependAssetInfo.BundleName);
					if (mainBundleID != bundleID)
					{
						if (result.Contains(bundleID) == false)
							result.Add(bundleID);
					}
				}
			}
			return result.ToArray();
		}

		/// <summary>
		/// 更新Unity内置资源包的引用关系
		/// </summary>
		private void UpdateBuiltInBundleReference(PackageManifest manifest, TaskBuilding_SBP.BuildResultContext buildResultContext, string shadersBunldeName)
		{
			// 获取所有依赖着色器资源包的资源包列表
			List<string> shaderBundleReferenceList = new List<string>();
			foreach (var valuePair in buildResultContext.Results.BundleInfos)
			{
				if (valuePair.Value.Dependencies.Any(t => t == shadersBunldeName))
					shaderBundleReferenceList.Add(valuePair.Key);
			}

			// 注意：没有任何资源依赖着色器
			if (shaderBundleReferenceList.Count == 0)
				return;

			// 获取着色器资源包索引
			Predicate<PackageBundle> predicate = new Predicate<PackageBundle>(s => s.BundleName == shadersBunldeName);
			int shaderBundleId = manifest.BundleList.FindIndex(predicate);
			if (shaderBundleId == -1)
				throw new Exception("没有发现着色器资源包！");

			// 检测依赖交集并更新依赖ID
			HashSet<string> tagTemps = new HashSet<string>();
			foreach (var packageAsset in manifest.AssetList)
			{
				List<string> dependBundles = GetPackageAssetAllDependBundles(manifest, packageAsset);
				List<string> conflictAssetPathList = dependBundles.Intersect(shaderBundleReferenceList).ToList();
				if (conflictAssetPathList.Count > 0)
				{
					HashSet<int> newDependIDs = new HashSet<int>(packageAsset.DependIDs);
					if (newDependIDs.Contains(shaderBundleId) == false)
						newDependIDs.Add(shaderBundleId);
					packageAsset.DependIDs = newDependIDs.ToArray();
					foreach (var tag in packageAsset.AssetTags)
					{
						if (tagTemps.Contains(tag) == false)
							tagTemps.Add(tag);
					}
				}
			}

			// 更新资源包标签
			var packageBundle = manifest.BundleList[shaderBundleId];
			HashSet<string> newTags = new HashSet<string>(packageBundle.Tags);
			foreach (var tag in tagTemps)
			{
				if (newTags.Contains(tag) == false)
					newTags.Add(tag);
			}
			packageBundle.Tags = newTags.ToArray();
		}
		private List<string> GetPackageAssetAllDependBundles(PackageManifest manifest, PackageAsset packageAsset)
		{
			List<string> result = new List<string>();
			string mainBundle = manifest.BundleList[packageAsset.BundleID].BundleName;
			result.Add(mainBundle);
			foreach (var dependID in packageAsset.DependIDs)
			{
				string dependBundle = manifest.BundleList[dependID].BundleName;
				result.Add(dependBundle);
			}
			return result;
		}

		#region 资源包引用关系相关
		private readonly Dictionary<string, int> _cachedBundleID = new Dictionary<string, int>(10000);
		private readonly Dictionary<string, string[]> _cachedBundleDepends = new Dictionary<string, string[]>(10000);
		
		private void CacheBundleIDs(PackageManifest manifest)
		{
			UnityEngine.Debug.Assert(manifest.BundleList.Count != 0, "Manifest bundle list is empty !");
			for (int index = 0; index < manifest.BundleList.Count; index++)
			{
				string bundleName = manifest.BundleList[index].BundleName;
				_cachedBundleID.Add(bundleName, index);
			}
		}

		private void UpdateScriptPipelineReference(PackageManifest manifest, TaskBuilding_SBP.BuildResultContext buildResultContext)
		{
			int progressValue;
			int totalCount = manifest.BundleList.Count;

			// 缓存资源包依赖
			_cachedBundleDepends.Clear();
			progressValue = 0;
			foreach (var packageBundle in manifest.BundleList)
			{
				if (packageBundle.IsRawFile)
				{
					_cachedBundleDepends.Add(packageBundle.BundleName, new string[] { });
					continue;
				}

				if (buildResultContext.Results.BundleInfos.ContainsKey(packageBundle.BundleName) == false)
					throw new Exception($"Not found bundle in SBP build results : {packageBundle.BundleName}");

				var depends = buildResultContext.Results.BundleInfos[packageBundle.BundleName].Dependencies;
				_cachedBundleDepends.Add(packageBundle.BundleName, depends);
				int pro = ++progressValue;
				if (pro % 100 == 0)
				{
					EditorTools.DisplayProgressBar("缓存资源包依赖列表", pro, totalCount);
				}
			}
			EditorTools.ClearProgressBar();

			// 计算资源包引用列表
			foreach (var packageBundle in manifest.BundleList)
			{
				packageBundle.ReferenceIDs = GetBundleRefrenceIDs(manifest, packageBundle);
				int pro = ++progressValue;
				if (pro % 100 == 0)
				{
					EditorTools.DisplayProgressBar("计算资源包引用关系", pro, totalCount);
				}
			}
			EditorTools.ClearProgressBar();
		}
		private void UpdateBuiltinPipelineReference(PackageManifest manifest, TaskBuilding.BuildResultContext buildResultContext)
		{
			int progressValue;
			int totalCount = manifest.BundleList.Count;

			// 缓存资源包依赖
			_cachedBundleDepends.Clear();
			progressValue = 0;
			foreach (var packageBundle in manifest.BundleList)
			{
				if (packageBundle.IsRawFile)
				{
					_cachedBundleDepends.Add(packageBundle.BundleName, new string[] { });
					continue;
				}

				var depends = buildResultContext.UnityManifest.GetDirectDependencies(packageBundle.BundleName);
				_cachedBundleDepends.Add(packageBundle.BundleName, depends);
				int pro = ++progressValue;
				if (pro % 100 == 0)
				{
					EditorTools.DisplayProgressBar("缓存资源包依赖列表", pro, totalCount);
				}
			}
			EditorTools.ClearProgressBar();

			// 计算资源包引用列表
			progressValue = 0;
			foreach (var packageBundle in manifest.BundleList)
			{
				packageBundle.ReferenceIDs = GetBundleRefrenceIDs(manifest, packageBundle);
				int pro = ++progressValue;
				if (pro % 100 == 0)
				{
					EditorTools.DisplayProgressBar("计算资源包引用关系", ++progressValue, totalCount);
				}
			}
			EditorTools.ClearProgressBar();
		}

		private int[] GetBundleRefrenceIDs(PackageManifest manifest, PackageBundle targetBundle)
		{
			List<string> referenceList = new List<string>();
			foreach (var packageBundle in manifest.BundleList)
			{
				string bundleName = packageBundle.BundleName;
				if (bundleName == targetBundle.BundleName)
					continue;

				string[] dependencies = GetCachedBundleDepends(bundleName);
				if (dependencies.Contains(targetBundle.BundleName))
				{
					referenceList.Add(bundleName);
				}
			}

			HashSet<int> result = new HashSet<int>();
			foreach (var bundleName in referenceList)
			{
				int bundleID = GetCachedBundleID(bundleName);
				if (result.Contains(bundleID) == false)
					result.Add(bundleID);
			}
			return result.ToArray();
		}
		private int GetCachedBundleID(string bundleName)
		{
			if (_cachedBundleID.TryGetValue(bundleName, out int value) == false)
			{
				throw new Exception($"Not found cached bundle ID : {bundleName}");
			}
			return value;
		}
		private string[] GetCachedBundleDepends(string bundleName)
		{
			if (_cachedBundleDepends.TryGetValue(bundleName, out string[] value) == false)
			{
				throw new Exception($"Not found cached bundle depends : {bundleName}");
			}
			return value;
		}
		#endregion
	}
}