using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Controls; using NLog; using NLog.Fluent; using Torch.API; using Torch.Collections; using Torch.Managers; using Torch.Server.ViewModels.Entities; using Torch.Utils; using WeakEntityControlFactoryResult = System.Collections.Generic.KeyValuePair, System.WeakReference>; namespace Torch.Server.Managers { /// /// Manager that lets users bind random view models to entities in Torch's Entity Manager /// public class EntityControlManager : Manager { private static readonly Logger _log = LogManager.GetCurrentClassLogger(); /// /// Creates an entity control manager for the given instance of torch /// /// Torch instance internal EntityControlManager(ITorchBase torchInstance) : base(torchInstance) { } private readonly Dictionary> _modelFactories = new Dictionary>(); private readonly List _controlFactories = new List(); private readonly List> _boundEntityViewModels = new List>(); private readonly ConditionalWeakTable> _boundViewModels = new ConditionalWeakTable>(); /// /// This factory will be used to create component models for matching entity models. /// /// entity model type to match /// Method to create component model from entity model. public void RegisterModelFactory(Func modelFactory) where TEntityBaseModel : EntityViewModel { if (!typeof(TEntityBaseModel).IsAssignableFrom(modelFactory.Method.GetParameters()[0].ParameterType)) throw new ArgumentException("Generic type must match lamda type", nameof(modelFactory)); lock (this) { var results = new List(); _modelFactories.Add(modelFactory, results); var i = 0; while (i < _boundEntityViewModels.Count) { if (_boundEntityViewModels[i].TryGetTarget(out EntityViewModel target) && _boundViewModels.TryGetValue(target, out MtObservableList components)) { if (target is TEntityBaseModel tent) { EntityControlViewModel result = modelFactory.Invoke(tent); if (result != null) { _log.Debug($"Model factory {modelFactory.Method} created {result} for {tent}"); components.Add(result); results.Add(new WeakEntityControlFactoryResult(new WeakReference(target), new WeakReference(result))); } } i++; } else _boundEntityViewModels.RemoveAtFast(i); } } } /// /// Unregisters a factory registered with /// /// entity model type to match /// Method to create component model from entity model. public void UnregisterModelFactory(Func modelFactory) where TEntityBaseModel : EntityViewModel { if (!typeof(TEntityBaseModel).IsAssignableFrom(modelFactory.Method.GetParameters()[0].ParameterType)) throw new ArgumentException("Generic type must match lamda type", nameof(modelFactory)); lock (this) { if (!_modelFactories.TryGetValue(modelFactory, out var results)) return; _modelFactories.Remove(modelFactory); foreach (WeakEntityControlFactoryResult result in results) { if (result.Key.TryGetTarget(out EntityViewModel target) && result.Value.TryGetTarget(out EntityControlViewModel created) && _boundViewModels.TryGetValue(target, out MtObservableList registered)) { registered.Remove(created); } } } } /// /// This factory will be used to create controls for matching view models. /// /// component model to match /// Method to create control from component model public void RegisterControlFactory( Func controlFactory) where TEntityComponentModel : EntityControlViewModel { if (!typeof(TEntityComponentModel).IsAssignableFrom(controlFactory.Method.GetParameters()[0].ParameterType)) throw new ArgumentException("Generic type must match lamda type", nameof(controlFactory)); lock (this) { _controlFactories.Add(controlFactory); RefreshControls(); } } /// /// Unregisters a factory registered with /// /// component model to match /// Method to create control from component model public void UnregisterControlFactory( Func controlFactory) where TEntityComponentModel : EntityControlViewModel { if (!typeof(TEntityComponentModel).IsAssignableFrom(controlFactory.Method.GetParameters()[0].ParameterType)) throw new ArgumentException("Generic type must match lamda type", nameof(controlFactory)); lock (this) { _controlFactories.Remove(controlFactory); RefreshControls(); } } private void RefreshControls() where TEntityComponentModel : EntityControlViewModel { var i = 0; while (i < _boundEntityViewModels.Count) { if (_boundEntityViewModels[i].TryGetTarget(out EntityViewModel target) && _boundViewModels.TryGetValue(target, out MtObservableList components)) { foreach (EntityControlViewModel component in components) if (component is TEntityComponentModel) component.InvalidateControl(); i++; } else _boundEntityViewModels.RemoveAtFast(i); } } /// /// Gets the models bound to the given entity view model. /// /// view model to query /// public MtObservableList BoundModels(EntityViewModel entity) { return _boundViewModels.GetValue(entity, CreateFreshBinding); } /// /// Gets a control for the given view model type. /// /// model to create a control for /// control, or null if none public Control CreateControl(EntityControlViewModel model) { lock (this) foreach (Delegate factory in _controlFactories) if (factory.Method.GetParameters()[0].ParameterType.IsInstanceOfType(model) && factory.DynamicInvoke(model) is Control result) { _log.Debug($"Control factory {factory.Method} created {result}"); return result; } _log.Warn($"No control created for {model}"); return null; } private MtObservableList CreateFreshBinding(EntityViewModel key) { var binding = new MtObservableList(); lock (this) { foreach (KeyValuePair> factory in _modelFactories) { Type ptype = factory.Key.Method.GetParameters()[0].ParameterType; if (ptype.IsInstanceOfType(key) && factory.Key.DynamicInvoke(key) is EntityControlViewModel result) { _log.Debug($"Model factory {factory.Key.Method} created {result} for {key}"); binding.Add(result); result.InvalidateControl(); factory.Value.Add(new WeakEntityControlFactoryResult(new WeakReference(key), new WeakReference(result))); } } _boundEntityViewModels.Add(new WeakReference(key)); } return binding; } } }