using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using BepInEx.Logging;

namespace BepInEx.Bootstrap
{
	/// <summary>
	/// Provides methods for loading specified types from an assembly.
	/// </summary>
	public static class TypeLoader
	{
		/// <summary>
		/// Loads a list of types from a directory containing assemblies, that derive from a base type.
		/// </summary>
		/// <typeparam name="T">The specific base type to search for.</typeparam>
		/// <param name="directory">The directory to search for assemblies.</param>
		/// <returns>Returns a list of found derivative types.</returns>
		public static IEnumerable<Type> LoadTypes<T>(string directory)
		{
			List<Type> types = new List<Type>();
			Type pluginType = typeof(T);

			foreach (string dll in Directory.GetFiles(Path.GetFullPath(directory), "*.dll", SearchOption.AllDirectories))
			{
				try
				{
					AssemblyName an = AssemblyName.GetAssemblyName(dll);
					Assembly assembly = Assembly.Load(an);

					foreach (Type type in assembly.GetTypes())
					{
						if (!type.IsInterface && !type.IsAbstract && type.BaseType == pluginType)
							types.Add(type);
					}
				}
				catch (BadImageFormatException) { } //unmanaged DLL
				catch (ReflectionTypeLoadException ex)
				{
					Logger.Log(LogLevel.Error, $"Could not load \"{Path.GetFileName(dll)}\" as a plugin!");
					Logger.Log(LogLevel.Debug, TypeLoadExceptionToString(ex));
				}
			}

			return types;
		}

		private static string TypeLoadExceptionToString(ReflectionTypeLoadException ex)
		{
			StringBuilder sb = new StringBuilder();
			foreach (Exception exSub in ex.LoaderExceptions)
			{
				sb.AppendLine(exSub.Message);
				if (exSub is FileNotFoundException exFileNotFound)
				{
					if (!string.IsNullOrEmpty(exFileNotFound.FusionLog))
					{
						sb.AppendLine("Fusion Log:");
						sb.AppendLine(exFileNotFound.FusionLog);
					}
				}
				else if (exSub is FileLoadException exLoad)
				{
					if (!string.IsNullOrEmpty(exLoad.FusionLog))
					{
						sb.AppendLine("Fusion Log:");
						sb.AppendLine(exLoad.FusionLog);
					}
				}

				sb.AppendLine();
			}

			return sb.ToString();
		}
	}
}