using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Avalonia.Controls; using Microsoft.Extensions.DependencyInjection; using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Core.Attributes; namespace StabilityMatrix.Avalonia.Services; [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public class ServiceManager { // Holds providers private readonly Dictionary> providers = new(); // Holds singleton instances private readonly Dictionary instances = new(); /// /// Register a new dialog view model (singleton instance) /// public ServiceManager Register(TService instance) where TService : T { if (instance is null) throw new ArgumentNullException(nameof(instance)); lock (instances) { if (instances.ContainsKey(typeof(TService)) || providers.ContainsKey(typeof(TService))) { throw new ArgumentException( $"Service of type {typeof(TService)} is already registered for {typeof(T)}" ); } instances[instance.GetType()] = instance; } return this; } /// /// Register a new dialog view model provider action (called on each dialog creation) /// public ServiceManager Register(Func provider) where TService : T { lock (providers) { if (instances.ContainsKey(typeof(TService)) || providers.ContainsKey(typeof(TService))) { throw new ArgumentException( $"Service of type {typeof(TService)} is already registered for {typeof(T)}" ); } // Return type is wrong during build with method group syntax // ReSharper disable once RedundantCast providers[typeof(TService)] = () => (TService)provider(); } return this; } /// /// Register a new dialog view model instance using a service provider /// Equal to Register[TService](serviceProvider.GetRequiredService[TService]) /// public ServiceManager RegisterProvider(IServiceProvider provider) where TService : notnull, T { lock (providers) { if (instances.ContainsKey(typeof(TService)) || providers.ContainsKey(typeof(TService))) { throw new ArgumentException( $"Service of type {typeof(TService)} is already registered for {typeof(T)}" ); } // Return type is wrong during build with method group syntax // ReSharper disable once RedundantCast providers[typeof(TService)] = () => (TService)provider.GetRequiredService(); } return this; } /// /// Get a view model instance from runtime type /// [SuppressMessage("ReSharper", "InconsistentlySynchronizedField")] public T Get(Type serviceType) { if (!serviceType.IsAssignableTo(typeof(T))) { throw new ArgumentException( $"Service type {serviceType} is not assignable to {typeof(T)}" ); } if (instances.TryGetValue(serviceType, out var instance)) { if (instance is null) { throw new ArgumentException( $"Service of type {serviceType} was registered as null" ); } return (T)instance; } if (providers.TryGetValue(serviceType, out var provider)) { if (provider is null) { throw new ArgumentException( $"Service of type {serviceType} was registered as null" ); } var result = provider(); if (result is null) { throw new ArgumentException( $"Service provider for type {serviceType} returned null" ); } return (T)result; } throw new ArgumentException( $"Service of type {serviceType} is not registered for {typeof(T)}" ); } /// /// Get a view model instance /// [SuppressMessage("ReSharper", "InconsistentlySynchronizedField")] public TService Get() where TService : T { if (instances.TryGetValue(typeof(TService), out var instance)) { if (instance is null) { throw new ArgumentException( $"Service of type {typeof(TService)} was registered as null" ); } return (TService)instance; } if (providers.TryGetValue(typeof(TService), out var provider)) { if (provider is null) { throw new ArgumentException( $"Service of type {typeof(TService)} was registered as null" ); } var result = provider(); if (result is null) { throw new ArgumentException( $"Service provider for type {typeof(TService)} returned null" ); } return (TService)result; } throw new ArgumentException( $"Service of type {typeof(TService)} is not registered for {typeof(T)}" ); } /// /// Get a view model instance with an initializer parameter /// public TService Get(Func initializer) where TService : T { var instance = Get(); return initializer(instance); } /// /// Get a view model instance with an initializer for a mutable instance /// public TService Get(Action initializer) where TService : T { var instance = Get(); initializer(instance); return instance; } /// /// Get a view model instance, set as DataContext of its View, and return /// a BetterContentDialog with that View as its Content /// public BetterContentDialog GetDialog() where TService : T { var instance = Get()!; if ( Attribute.GetCustomAttribute(instance.GetType(), typeof(ViewAttribute)) is not ViewAttribute viewAttr ) { throw new InvalidOperationException( $"View not found for {instance.GetType().FullName}" ); } if (Activator.CreateInstance(viewAttr.ViewType) is not Control view) { throw new NullReferenceException( $"Unable to create instance for {instance.GetType().FullName}" ); } return new BetterContentDialog { Content = view }; } /// /// Get a view model instance with initializer, set as DataContext of its View, and return /// a BetterContentDialog with that View as its Content /// public BetterContentDialog GetDialog(Action initializer) where TService : T { var instance = Get(initializer)!; if ( Attribute.GetCustomAttribute(instance.GetType(), typeof(ViewAttribute)) is not ViewAttribute viewAttr ) { throw new InvalidOperationException( $"View not found for {instance.GetType().FullName}" ); } if (Activator.CreateInstance(viewAttr.ViewType) is not Control view) { throw new NullReferenceException( $"Unable to create instance for {instance.GetType().FullName}" ); } view.DataContext = instance; return new BetterContentDialog { Content = view }; } }