using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.Annotations; using Artemis.Core.Events; using Artemis.Core.Exceptions; using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.Core.Plugins.Exceptions; using Artemis.Core.Services.Interfaces; using Artemis.Storage.Entities.Profile; namespace Artemis.Core.Models.Profile { public abstract class LayerPropertyGroup { private readonly List _layerProperties; private readonly List _layerPropertyGroups; private ReadOnlyCollection _allLayerProperties; private bool _isHidden; protected LayerPropertyGroup() { _layerProperties = new List(); _layerPropertyGroups = new List(); } /// /// The layer this property group applies to /// public Layer Layer { get; internal set; } /// /// The path of this property group /// public string Path { get; internal set; } /// /// The parent group of this layer property group, set after construction /// public LayerPropertyGroup Parent { get; internal set; } /// /// Gets whether this property group's properties are all initialized /// public bool PropertiesInitialized { get; private set; } /// /// Used to declare that this property group doesn't belong to a plugin and should use the core plugin GUID /// public bool IsCorePropertyGroup { get; internal set; } public PropertyGroupDescriptionAttribute GroupDescription { get; internal set; } /// /// Gets or sets whether the property is hidden in the UI /// public bool IsHidden { get => _isHidden; set { _isHidden = value; OnVisibilityChanged(); } } /// /// A list of all layer properties in this group /// public ReadOnlyCollection LayerProperties => _layerProperties.AsReadOnly(); /// /// A list of al child groups in this group /// public ReadOnlyCollection LayerPropertyGroups => _layerPropertyGroups.AsReadOnly(); /// /// Recursively gets all layer properties on this group and any subgroups /// /// public IReadOnlyCollection GetAllLayerProperties() { if (!PropertiesInitialized) return new List(); if (_allLayerProperties != null) return _allLayerProperties; var result = new List(LayerProperties); foreach (var layerPropertyGroup in LayerPropertyGroups) result.AddRange(layerPropertyGroup.GetAllLayerProperties()); _allLayerProperties = result.AsReadOnly(); return _allLayerProperties; } /// /// Called before properties are fully initialized to allow you to populate /// on the properties you want /// protected abstract void PopulateDefaults(); /// /// Called when all layer properties in this property group have been initialized, you may access all properties on the /// group here /// protected abstract void OnPropertiesInitialized(); protected virtual void OnPropertyGroupInitialized() { PropertyGroupInitialized?.Invoke(this, EventArgs.Empty); } internal void InitializeProperties(ILayerService layerService, Layer layer, [NotNull] string path) { if (path == null) throw new ArgumentNullException(nameof(path)); // Doubt this will happen but let's make sure if (PropertiesInitialized) throw new ArtemisCoreException("Layer property group already initialized, wut"); Layer = layer; Path = path.TrimEnd('.'); // Get all properties with a PropertyDescriptionAttribute foreach (var propertyInfo in GetType().GetProperties()) { var propertyDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute)); if (propertyDescription != null) { if (!typeof(BaseLayerProperty).IsAssignableFrom(propertyInfo.PropertyType)) throw new ArtemisPluginException($"Layer property with PropertyDescription attribute must be of type LayerProperty at {path + propertyInfo.Name}"); var instance = (BaseLayerProperty) Activator.CreateInstance(propertyInfo.PropertyType, true); if (instance == null) throw new ArtemisPluginException($"Failed to create instance of layer property at {path + propertyInfo.Name}"); instance.Layer = layer; instance.Parent = this; instance.PropertyDescription = (PropertyDescriptionAttribute) propertyDescription; InitializeProperty(layer, path + propertyInfo.Name, instance); propertyInfo.SetValue(this, instance); _layerProperties.Add(instance); } else { var propertyGroupDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute)); if (propertyGroupDescription != null) { if (!typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType)) throw new ArtemisPluginException("Layer property with PropertyGroupDescription attribute must be of type LayerPropertyGroup"); var instance = (LayerPropertyGroup) Activator.CreateInstance(propertyInfo.PropertyType); if (instance == null) throw new ArtemisPluginException($"Failed to create instance of layer property group at {path + propertyInfo.Name}"); instance.Parent = this; instance.GroupDescription = (PropertyGroupDescriptionAttribute) propertyGroupDescription; instance.InitializeProperties(layerService, layer, $"{path}{propertyInfo.Name}."); propertyInfo.SetValue(this, instance); _layerPropertyGroups.Add(instance); } } } // Request the property group to populate defaults PopulateDefaults(); // Apply the newly populated defaults foreach (var layerProperty in _layerProperties.Where(p => !p.IsLoadedFromStorage)) layerProperty.ApplyDefaultValue(); OnPropertiesInitialized(); PropertiesInitialized = true; OnPropertyGroupInitialized(); } internal void ApplyToEntity() { if (!PropertiesInitialized) return; // Get all properties with a PropertyDescriptionAttribute foreach (var propertyInfo in GetType().GetProperties()) { var propertyDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute)); if (propertyDescription != null) { var layerProperty = (BaseLayerProperty) propertyInfo.GetValue(this); layerProperty.ApplyToEntity(); } else { var propertyGroupDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute)); if (propertyGroupDescription != null) { var layerPropertyGroup = (LayerPropertyGroup) propertyInfo.GetValue(this); layerPropertyGroup.ApplyToEntity(); } } } } internal void Update(double deltaTime) { // Since at this point we don't know what properties the group has without using reflection, // let properties subscribe to the update event and update themselves OnPropertyGroupUpdating(new PropertyGroupUpdatingEventArgs(deltaTime)); } internal void Override(TimeSpan overrideTime) { // Same as above, but now the progress is overridden OnPropertyGroupOverriding(new PropertyGroupUpdatingEventArgs(overrideTime)); } private void InitializeProperty(Layer layer, string path, BaseLayerProperty instance) { var pluginGuid = IsCorePropertyGroup || instance.IsCoreProperty ? Constants.CorePluginInfo.Guid : layer.LayerBrush.PluginInfo.Guid; var entity = layer.LayerEntity.PropertyEntities.FirstOrDefault(p => p.PluginGuid == pluginGuid && p.Path == path); var fromStorage = true; if (entity == null) { fromStorage = false; entity = new PropertyEntity {PluginGuid = pluginGuid, Path = path}; layer.LayerEntity.PropertyEntities.Add(entity); } instance.ApplyToLayerProperty(entity, this, fromStorage); } #region Events internal event EventHandler PropertyGroupUpdating; internal event EventHandler PropertyGroupOverriding; public event EventHandler PropertyGroupInitialized; /// /// Occurs when the value of the layer property was updated /// public event EventHandler VisibilityChanged; internal virtual void OnPropertyGroupUpdating(PropertyGroupUpdatingEventArgs e) { PropertyGroupUpdating?.Invoke(this, e); } protected virtual void OnPropertyGroupOverriding(PropertyGroupUpdatingEventArgs e) { PropertyGroupOverriding?.Invoke(this, e); } protected virtual void OnVisibilityChanged() { VisibilityChanged?.Invoke(this, EventArgs.Empty); } #endregion } }