using System;
using System.IO;
using System.Security.Cryptography;
using AssemblyUnhollower;
using BepInEx.Logging;
using Il2CppDumper;

namespace BepInEx.IL2CPP
{
	internal static class ProxyAssemblyGenerator
	{
		public static string GameAssemblyPath => Path.Combine(Paths.GameRootPath, "GameAssembly.dll");

		private static string HashPath => Path.Combine(Preloader.IL2CPPUnhollowedPath, "assembly-hash.txt");

		private static string tempDumperDirectory => Path.Combine(Preloader.IL2CPPUnhollowedPath, "temp");

		private static ManualLogSource Il2cppDumperLogger = Logger.CreateLogSource("Il2CppDumper");


		private static string ComputeGameAssemblyHash()
		{
			using var md5 = MD5.Create();
			using var assemblyStream = File.OpenRead(GameAssemblyPath);

			var hash = md5.ComputeHash(assemblyStream);

			return Utility.ByteArrayToString(hash);
		}

		public static bool CheckIfGenerationRequired()
		{
			if (!Directory.Exists(Preloader.IL2CPPUnhollowedPath))
				return true;

			if (!File.Exists(HashPath))
				return true;

			if (ComputeGameAssemblyHash() != File.ReadAllText(HashPath))
			{
				Preloader.Log.LogInfo("Detected a game update, will regenerate proxy assemblies");
				return true;
			}

			return false;
		}

		public static void GenerateAssemblies()
		{
			var domain = AppDomain.CreateDomain("GeneratorDomain", null, new AppDomainSetup
			{
				ApplicationBase = Paths.BepInExAssemblyDirectory
			});

			var runner = (AppDomainRunner)domain.CreateInstanceAndUnwrap(typeof(AppDomainRunner).Assembly.FullName, typeof(AppDomainRunner).FullName);
			
			runner.Setup(Paths.ExecutablePath, Preloader.IL2CPPUnhollowedPath);
			runner.GenerateAssembliesInternal(new AppDomainListener());

			AppDomain.Unload(domain);

			Directory.Delete(tempDumperDirectory, true);

			File.WriteAllText(HashPath, ComputeGameAssemblyHash());
		}

		[Serializable]
		private class AppDomainListener : MarshalByRefObject
		{
			public void DoPreloaderLog(object data, LogLevel level)
			{
				Preloader.Log.Log(level, data);
			}

			public void DoDumperLog(object data, LogLevel level)
			{
				Il2cppDumperLogger.Log(level, data);
			}

			public void DoUnhollowerLog(object data, LogLevel level)
			{
				Preloader.UnhollowerLog.Log(level, data);
			}
		}

		[Serializable]
		private class AppDomainRunner : MarshalByRefObject
		{
			public void Setup(string executablePath, string unhollowedPath)
			{
				Paths.SetExecutablePath(executablePath);
				Preloader.IL2CPPUnhollowedPath = unhollowedPath;
			}

			public void GenerateAssembliesInternal(AppDomainListener listener)
			{
				listener.DoPreloaderLog("Generating Il2CppUnhollower assemblies", LogLevel.Message);

				if (Directory.Exists(Preloader.IL2CPPUnhollowedPath))
					Directory.Delete(Preloader.IL2CPPUnhollowedPath, true);

				Directory.CreateDirectory(Preloader.IL2CPPUnhollowedPath);

				string tempDumperDirectory = Path.Combine(Preloader.IL2CPPUnhollowedPath, "temp");
				Directory.CreateDirectory(tempDumperDirectory);




				var dumperConfig = new Config
				{
					GenerateScript = false,
					GenerateDummyDll = true
				};

				listener.DoPreloaderLog("Generating Il2CppDumper intermediate assemblies", LogLevel.Info);

				Il2CppDumper.Il2CppDumper.PerformDump(GameAssemblyPath,
					Path.Combine(Paths.GameRootPath, $"{Paths.ProcessName}_Data", "il2cpp_data", "Metadata", "global-metadata.dat"),
					tempDumperDirectory,
					dumperConfig,
					s => listener.DoDumperLog(s, LogLevel.Debug));



				listener.DoPreloaderLog("Executing Il2CppUnhollower generator", LogLevel.Info);

				UnhollowerBaseLib.LogSupport.InfoHandler += s => listener.DoUnhollowerLog(s, LogLevel.Info);
				UnhollowerBaseLib.LogSupport.WarningHandler += s => listener.DoUnhollowerLog(s, LogLevel.Warning);
				UnhollowerBaseLib.LogSupport.TraceHandler += s => listener.DoUnhollowerLog(s, LogLevel.Debug);
				UnhollowerBaseLib.LogSupport.ErrorHandler += s => listener.DoUnhollowerLog(s, LogLevel.Error);

				var unhollowerOptions = new UnhollowerOptions
				{
					GameAssemblyPath = GameAssemblyPath,
					MscorlibPath = Path.Combine(Paths.GameRootPath, "mono", "Managed", "mscorlib.dll"),
					SourceDir = Path.Combine(tempDumperDirectory, "DummyDll"),
					OutputDir = Preloader.IL2CPPUnhollowedPath
				};

				AssemblyUnhollower.Program.Main(unhollowerOptions);
			}
		}
	}
}