using MediaBrowser.Common.Logging;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Serialization;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Progress;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace MediaBrowser.Common.Kernel
{
    /// 
    /// Represents a shared base kernel for both the UI and server apps
    /// 
    public abstract class BaseKernel : IDisposable, IKernel
        where TConfigurationType : BaseApplicationConfiguration, new()
        where TApplicationPathsType : BaseApplicationPaths, new()
    {
        /// 
        /// Gets the current configuration
        /// 
        public TConfigurationType Configuration { get; private set; }
        public TApplicationPathsType ApplicationPaths { get; private set; }
        /// 
        /// Gets the list of currently loaded plugins
        /// 
        [ImportMany(typeof(BasePlugin))]
        public IEnumerable Plugins { get; private set; }
        /// 
        /// Both the UI and server will have a built-in HttpServer.
        /// People will inevitably want remote control apps so it's needed in the UI too.
        /// 
        public HttpServer HttpServer { get; private set; }
        protected virtual string HttpServerUrlPrefix
        {
            get
            {
                return "http://+:" + Configuration.HttpServerPortNumber + "/mediabrowser/";
            }
        }
        /// 
        /// Gets the kernel context. Subclasses will have to override.
        /// 
        public abstract KernelContext KernelContext { get; }
        public BaseKernel()
        {
            ApplicationPaths = new TApplicationPathsType();
        }
        public virtual async Task Init(IProgress progress)
        {
            ReloadLogger();
            progress.Report(new TaskProgress() { Description = "Loading configuration", PercentComplete = 0 });
            ReloadConfiguration();
            progress.Report(new TaskProgress() { Description = "Starting Http server", PercentComplete = 5 });
            ReloadHttpServer();
            progress.Report(new TaskProgress() { Description = "Loading Plugins", PercentComplete = 10 });
            await ReloadComposableParts().ConfigureAwait(false);
        }
        /// 
        /// Gets or sets the path to the current log file
        /// 
        public static string LogFilePath { get; set; }
        private void ReloadLogger()
        {
            DisposeLogger();
            DateTime now = DateTime.Now;
            LogFilePath = Path.Combine(ApplicationPaths.LogDirectoryPath, "log-" + now.ToString("dMyyyy") + "-" + now.Ticks + ".log");
            FileStream fs = new FileStream(LogFilePath, FileMode.Append, FileAccess.Write, FileShare.Read);
            Logger.LoggerInstance = new StreamLogger(fs);
        }
        /// 
        /// Uses MEF to locate plugins
        /// Subclasses can use this to locate types within plugins
        /// 
        protected virtual Task ReloadComposableParts()
        {
            return Task.Run(() =>
            {
                DisposeComposableParts();
                var container = GetCompositionContainer(includeCurrentAssembly: true);
                container.ComposeParts(this);
                OnComposablePartsLoaded();
                container.Catalog.Dispose();
                container.Dispose();
            });
        }
        public CompositionContainer GetCompositionContainer(bool includeCurrentAssembly = false)
        {
            // Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that
            // This will prevent the .dll file from getting locked, and allow us to replace it when needed
            IEnumerable pluginAssemblies = Directory.GetFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly).Select(f => Assembly.Load(File.ReadAllBytes((f))));
            var catalog = new AggregateCatalog(pluginAssemblies.Select(a => new AssemblyCatalog(a)));
            // Include composable parts in the Common assembly 
            // Uncomment this if it's ever needed
            //catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
            if (includeCurrentAssembly)
            {
                // Include composable parts in the subclass assembly
                catalog.Catalogs.Add(new AssemblyCatalog(GetType().Assembly));
            }
            return new CompositionContainer(catalog);
        }
        /// 
        /// Fires after MEF finishes finding composable parts within plugin assemblies
        /// 
        protected virtual void OnComposablePartsLoaded()
        {
            // This event handler will allow any plugin to reference another
            AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            StartPlugins();
        }
        /// 
        /// Initializes all plugins
        /// 
        private void StartPlugins()
        {
            foreach (BasePlugin plugin in Plugins)
            {
                plugin.Initialize(this);
            }
        }
        /// 
        /// Reloads application configuration from the config file
        /// 
        protected virtual void ReloadConfiguration()
        {
            //Configuration information for anything other than server-specific configuration will have to come via the API... -ebr
            // Deserialize config
            if (!File.Exists(ApplicationPaths.SystemConfigurationFilePath))
            {
                Configuration = new TConfigurationType();
                XmlSerializer.SerializeToFile(Configuration, ApplicationPaths.SystemConfigurationFilePath);
            }
            else
            {
                Configuration = XmlSerializer.DeserializeFromFile(ApplicationPaths.SystemConfigurationFilePath);
            }
            Logger.LoggerInstance.LogSeverity = Configuration.EnableDebugLevelLogging ? LogSeverity.Debug : LogSeverity.Info;
        }
        /// 
        /// Restarts the Http Server, or starts it if not currently running
        /// 
        private void ReloadHttpServer()
        {
            DisposeHttpServer();
            HttpServer = new HttpServer(HttpServerUrlPrefix);
        }
        /// 
        /// This snippet will allow any plugin to reference another
        /// 
        Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            AssemblyName assemblyName = new AssemblyName(args.Name);
            // Look for the .dll recursively within the plugins directory
            string dll = Directory.GetFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.AllDirectories)
                .FirstOrDefault(f => Path.GetFileNameWithoutExtension(f) == assemblyName.Name);
            // If we found a matching assembly, load it now
            if (!string.IsNullOrEmpty(dll))
            {
                return Assembly.Load(File.ReadAllBytes(dll));
            }
            return null;
        }
        /// 
        /// Disposes all resources currently in use.
        /// 
        public virtual void Dispose()
        {
            DisposeComposableParts();
            DisposeHttpServer();
            DisposeLogger();
        }
        /// 
        /// Disposes all objects gathered through MEF composable parts
        /// 
        protected virtual void DisposeComposableParts()
        {
            DisposePlugins();
        }
        /// 
        /// Disposes all plugins
        /// 
        private void DisposePlugins()
        {
            if (Plugins != null)
            {
                foreach (BasePlugin plugin in Plugins)
                {
                    plugin.Dispose();
                }
            }
        }
        /// 
        /// Disposes the current HttpServer
        /// 
        private void DisposeHttpServer()
        {
            if (HttpServer != null)
            {
                HttpServer.Dispose();
            }
        }
        /// 
        /// Disposes the current Logger instance
        /// 
        private void DisposeLogger()
        {
            if (Logger.LoggerInstance != null)
            {
                Logger.LoggerInstance.Dispose();
            }
        }
        /// 
        /// Gets the current application version
        /// 
        public Version ApplicationVersion
        {
            get
            {
                return GetType().Assembly.GetName().Version;
            }
        }
        BaseApplicationPaths IKernel.ApplicationPaths
        {
            get { return ApplicationPaths; }
        }
    }
    public interface IKernel
    {
        BaseApplicationPaths ApplicationPaths { get; }
        KernelContext KernelContext { get; }
        Task Init(IProgress progress);
        void Dispose();
    }
}