using System.Threading.Tasks;
using MediaBrowser.Common.Implementations.Udp;
using MediaBrowser.Common.Implementations.Updates;
using MediaBrowser.Common.Implementations.WebSocket;
using MediaBrowser.Common.Kernel;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.ScheduledTasks;
using MediaBrowser.Common.Updates;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using SimpleInjector;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
namespace MediaBrowser.Common.Implementations
{
    public abstract class BaseApplicationHost
    {
        /// 
        /// Gets or sets the logger.
        /// 
        /// The logger.
        public ILogger Logger { get; protected set; }
        /// 
        /// Gets or sets the plugins.
        /// 
        /// The plugins.
        public IEnumerable Plugins { get; protected set; }
        /// 
        /// Gets or sets the log manager.
        /// 
        /// The log manager.
        public ILogManager LogManager { get; protected set; }
        /// 
        /// Gets the application paths.
        /// 
        /// The application paths.
        protected IApplicationPaths ApplicationPaths { get; private set; }
        /// 
        /// The container
        /// 
        protected readonly Container Container = new Container();
        /// 
        /// Gets assemblies that failed to load
        /// 
        public List FailedAssemblies { get; protected set; }
        /// 
        /// Gets all types within all running assemblies
        /// 
        /// All types.
        public Type[] AllTypes { get; protected set; }
        /// 
        /// Gets all concrete types.
        /// 
        /// All concrete types.
        public Type[] AllConcreteTypes { get; protected set; }
        /// 
        /// The disposable parts
        /// 
        protected readonly List DisposableParts = new List();
        /// 
        /// Gets a value indicating whether this instance is first run.
        /// 
        /// true if this instance is first run; otherwise, false.
        public bool IsFirstRun { get; private set; }
        /// 
        /// The _protobuf serializer initialized
        /// 
        private bool _protobufSerializerInitialized;
        /// 
        /// The _protobuf serializer sync lock
        /// 
        private object _protobufSerializerSyncLock = new object();
        /// 
        /// Gets a dynamically compiled generated serializer that can serialize protocontracts without reflection
        /// 
        private IProtobufSerializer _protobufSerializer;
        /// 
        /// Gets the protobuf serializer.
        /// 
        /// The protobuf serializer.
        protected IProtobufSerializer ProtobufSerializer
        {
            get
            {
                // Lazy load
                LazyInitializer.EnsureInitialized(ref _protobufSerializer, ref _protobufSerializerInitialized, ref _protobufSerializerSyncLock, () => Serialization.ProtobufSerializer.Create(AllTypes));
                return _protobufSerializer;
            }
            private set
            {
                _protobufSerializer = value;
                _protobufSerializerInitialized = value != null;
            }
        }
        /// 
        /// Initializes a new instance of the  class.
        /// 
        protected BaseApplicationHost()
        {
            FailedAssemblies = new List();
        }
        /// 
        /// Inits this instance.
        /// 
        /// Task.
        public virtual Task Init()
        {
            return Task.Run(() =>
            {
                ApplicationPaths = GetApplicationPaths();
                LogManager = GetLogManager();
                Logger = LogManager.GetLogger("App");
                IsFirstRun = !File.Exists(ApplicationPaths.SystemConfigurationFilePath);
                DiscoverTypes();
            });
        }
        /// 
        /// Gets the composable part assemblies.
        /// 
        /// IEnumerable{Assembly}.
        protected abstract IEnumerable GetComposablePartAssemblies();
        /// 
        /// Gets the log manager.
        /// 
        /// ILogManager.
        protected abstract ILogManager GetLogManager();
        /// 
        /// Gets the application paths.
        /// 
        /// IApplicationPaths.
        protected abstract IApplicationPaths GetApplicationPaths();
        /// 
        /// Finds the parts.
        /// 
        protected virtual void FindParts()
        {
            Resolve().Init(GetExports(false));
            Resolve().AddWebSocketListeners(GetExports(false));
            Resolve().Start();
            Resolve().AddTasks(GetExports(false));
            Plugins = GetExports();
        }
        /// 
        /// Discovers the types.
        /// 
        protected void DiscoverTypes()
        {
            FailedAssemblies.Clear();
            AllTypes = GetComposablePartAssemblies().SelectMany(GetTypes).ToArray();
            AllConcreteTypes = AllTypes.Where(t => t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType).ToArray();
        }
        /// 
        /// Registers resources that classes will depend on
        /// 
        protected virtual void RegisterResources(ITaskManager taskManager, INetworkManager networkManager, IServerManager serverManager)
        {
            RegisterSingleInstance(LogManager);
            RegisterSingleInstance(Logger);
            RegisterSingleInstance(ApplicationPaths);
            RegisterSingleInstance(taskManager);
            RegisterSingleInstance(() => new AlchemyServer(Logger));
            RegisterSingleInstance(ProtobufSerializer);
            RegisterSingleInstance(new UdpServer(Logger), false);
            RegisterSingleInstance(new PackageManager());
            RegisterSingleInstance(new HttpClientManager.HttpClientManager(ApplicationPaths, Logger));
            RegisterSingleInstance(networkManager);
            RegisterSingleInstance(serverManager);
        }
        /// 
        /// Gets a list of types within an assembly
        /// This will handle situations that would normally throw an exception - such as a type within the assembly that depends on some other non-existant reference
        /// 
        /// The assembly.
        /// IEnumerable{Type}.
        /// assembly
        protected IEnumerable GetTypes(Assembly assembly)
        {
            if (assembly == null)
            {
                throw new ArgumentNullException("assembly");
            }
            try
            {
                return assembly.GetTypes();
            }
            catch (ReflectionTypeLoadException ex)
            {
                // If it fails we can still get a list of the Types it was able to resolve
                return ex.Types.Where(t => t != null);
            }
        }
        /// 
        /// Creates an instance of type and resolves all constructor dependancies
        /// 
        /// The type.
        /// System.Object.
        public object CreateInstance(Type type)
        {
            try
            {
                return Container.GetInstance(type);
            }
            catch
            {
                Logger.Error("Error creating {0}", type.Name);
                throw;
            }
        }
        /// 
        /// Registers the specified obj.
        /// 
        /// 
        /// The obj.
        /// if set to true [manage lifetime].
        protected void RegisterSingleInstance(T obj, bool manageLifetime = true)
            where T : class
        {
            Container.RegisterSingle(obj);
            if (manageLifetime)
            {
                var disposable = obj as IDisposable;
                if (disposable != null)
                {
                    Logger.Info("Registering " + disposable.GetType().Name);
                    DisposableParts.Add(disposable);
                }
            }
        }
        /// 
        /// Registers the single instance.
        /// 
        /// 
        /// The func.
        protected void RegisterSingleInstance(Func func)
            where T : class
        {
            Container.RegisterSingle(func);
        }
        /// 
        /// Resolves this instance.
        /// 
        /// 
        /// ``0.
        public T Resolve()
        {
            return (T)Container.GetRegistration(typeof(T), true).GetInstance();
        }
        /// 
        /// Resolves this instance.
        /// 
        /// 
        /// ``0.
        public T TryResolve()
        {
            var result = Container.GetRegistration(typeof(T), false);
            if (result == null)
            {
                return default(T);
            }
            return (T)result.GetInstance();
        }
        /// 
        /// Loads the assembly.
        /// 
        /// The file.
        /// Assembly.
        protected Assembly LoadAssembly(string file)
        {
            try
            {
                return Assembly.Load(File.ReadAllBytes((file)));
            }
            catch (Exception ex)
            {
                FailedAssemblies.Add(file);
                Logger.ErrorException("Error loading assembly {0}", ex, file);
                return null;
            }
        }
        /// 
        /// Gets the exports.
        /// 
        /// 
        /// if set to true [manage liftime].
        /// IEnumerable{``0}.
        public IEnumerable GetExports(bool manageLiftime = true)
        {
            var currentType = typeof(T);
            Logger.Info("Composing instances of " + currentType.Name);
            var parts = AllConcreteTypes.Where(currentType.IsAssignableFrom).Select(CreateInstance).Cast().ToArray();
            if (manageLiftime)
            {
                DisposableParts.AddRange(parts.OfType());
            }
            return parts;
        }
        /// 
        /// Gets the current application version
        /// 
        /// The application version.
        public Version ApplicationVersion
        {
            get
            {
                return GetType().Assembly.GetName().Version;
            }
        }
        /// 
        /// Configures the auto run at startup.
        /// 
        /// if set to true [autorun].
        public void ConfigureAutoRunAtStartup(bool autorun)
        {
        }
        /// 
        /// Removes the plugin.
        /// 
        /// The plugin.
        public void RemovePlugin(IPlugin plugin)
        {
            var list = Plugins.ToList();
            list.Remove(plugin);
            Plugins = list;
        }
        /// 
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// 
        public void Dispose()
        {
            Dispose(true);
        }
        /// 
        /// Releases unmanaged and - optionally - managed resources.
        /// 
        /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
        protected virtual void Dispose(bool dispose)
        {
            if (dispose)
            {
                var type = GetType();
                Logger.Info("Disposing " + type.Name);
                var parts = DisposableParts.Distinct().Where(i => i.GetType() != type).ToList();
                DisposableParts.Clear();
                foreach (var part in parts)
                {
                    Logger.Info("Disposing " + part.GetType().Name);
                    part.Dispose();
                }
            }
        }
    }
}