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; 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 abstract class ModelFactory { private readonly ConditionalWeakTable _models = new ConditionalWeakTable(); public abstract Delegate Delegate { get; } protected abstract EntityControlViewModel Create(EntityViewModel evm); #if NETFRAMEWORK [ReflectedGetter(Name = "Keys")] private static readonly Func, ICollection> WeakTableKeys = null!; internal IEnumerable Keys => WeakTableKeys(_models); #else internal IEnumerable Keys => _models.Select(b => b.Key); #endif internal EntityControlViewModel GetOrCreate(EntityViewModel evm) { return _models.GetValue(evm, Create); } internal bool TryGet(EntityViewModel evm, out EntityControlViewModel res) { return _models.TryGetValue(evm, out res); } } private class ModelFactory : ModelFactory where T : EntityViewModel { private readonly Func _factory; public override Delegate Delegate => _factory; internal ModelFactory(Func factory) { _factory = factory; } protected override EntityControlViewModel Create(EntityViewModel evm) { if (evm is T m) { var result = _factory(m); _log.Trace($"Model factory {_factory.Method} created {result} for {evm}"); return result; } return null; } } private readonly List _modelFactories = new List(); 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 factory = new ModelFactory(modelFactory); _modelFactories.Add(factory); 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) UpdateBinding(target, components); 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) { for (var i = 0; i < _modelFactories.Count; i++) { if (_modelFactories[i].Delegate == (Delegate)modelFactory) { foreach (var entry in _modelFactories[i].Keys) if (_modelFactories[i].TryGet(entry, out EntityControlViewModel ecvm) && ecvm != null && _boundViewModels.TryGetValue(entry, out var binding)) binding.Remove(ecvm); _modelFactories.RemoveAt(i); break; } } } } /// /// 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.Trace($"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) { _boundEntityViewModels.Add(new WeakReference(key)); } binding.PropertyChanged += (x, args) => { if (nameof(binding.IsObserved).Equals(args.PropertyName)) UpdateBinding(key, binding); }; return binding; } private void UpdateBinding(EntityViewModel key, MtObservableList binding) { if (!binding.IsObserved) return; lock (this) { foreach (ModelFactory factory in _modelFactories) { var result = factory.GetOrCreate(key); if (result != null && !binding.Contains(result)) binding.Add(result); } } } } }