using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Interfaces;

namespace RaceLapTimer.Extensions
{
    public class PluginLocator:IPluginLocator
    {
        private readonly IContainerHelper _helper;
        private readonly IPluginPathProvider _pluginPath;
        private readonly ILoggerService _logger;

        public PluginLocator(IContainerHelper helper, IPluginPathProvider pluginPath, ILoggerService logger)
        {
            _helper = helper;
            _pluginPath = pluginPath;
            _logger = logger;
        }

        public async Task<List<T>> LocateAsync<T>() where T:class
        {
            return await Task.Run(() => Locate<T>());
        }

        public List<T> Locate<T>() where T:class
        {
            try
            {
                if (!Directory.Exists(_pluginPath.GetPluginPath)) return new List<T>();
                var files = Directory.GetFiles(_pluginPath.GetPluginPath);
                var discoveryList = new List<DiscoveredPlugin>();

                foreach (var file in files)
                {
                    var ext = Path.GetExtension(file);
                    if (!IsAssembly(ext)) continue;

                    var assembly = Assembly.ReflectionOnlyLoadFrom(file);
                    var pluginTypes = new List<Type>();
                    try
                    {
                        pluginTypes.AddRange((from t in assembly.GetExportedTypes()
                                                where t.GetInterfaces().Any(x => x.Name == typeof(T).Name)
                                                select t).ToList());
                    }
                    catch (FileLoadException flex)
                    {
                        _logger.Error(flex);
                    }

                    if (pluginTypes.Any())
                    {
                        discoveryList.Add(new DiscoveredPlugin
                        {
                            FilePath = file,
                            TypeToBindTo = typeof(T),
                            UniqueName = Path.GetFileNameWithoutExtension(file)
                        });
                    }
                }

                foreach (var plugin in discoveryList)
                {
                    var assy = AssemblyIsLoaded(plugin.FilePath)
                        ? GetAssemblyFromAppDomain(plugin.FilePath)
                        : Assembly.LoadFrom(plugin.FilePath);
                    
                    //register our type
                    var pluginTypes =
                    (from t in assy.GetExportedTypes()
                     where t.GetInterfaces().Any(x => x.Name == plugin.TypeToBindTo.Name)
                     select t).ToList();

                    if (!pluginTypes.Any()) continue;

                    var pg = pluginTypes.First();
                    _helper.RegisterType(plugin.TypeToBindTo, pg, plugin.UniqueName);
                }

                return _helper.GetAll<T>().ToList();
            }
            catch (Exception ex)
            {
                _logger.Error(ex);
                return new List<T>();
            }
        }

        private Assembly GetAssemblyFromAppDomain(string pluginFilePath)
        {
            var name = Path.GetFileName(pluginFilePath);
            return AppDomain.CurrentDomain.GetAssemblies().First(x => x.ManifestModule.Name == name);
        }

        private bool AssemblyIsLoaded(string pluginFilePath)
        {
            var name = Path.GetFileName(pluginFilePath);
            var d = AppDomain.CurrentDomain.GetAssemblies().Where(x => x.ManifestModule.Name == name);
            return d.Any();
        }

        private bool IsAssembly(string extension)
        {
            var validAssemblyExts = new List<string>
            {
                "dll", "exe"
            };
            var ext = extension.Remove(0, 1);
            return validAssemblyExts.Contains(ext);
        }

        private class DiscoveredPlugin
        {
            public string FilePath { get; set; }
            public Type TypeToBindTo { get; set; }
            public string UniqueName { get; set; }
        }
    }
}
