using System; using System.Collections.Generic; using System.Linq; using Artemis.Core.Modules; using Artemis.Storage.Entities.Profile; using SkiaSharp; namespace Artemis.Core { /// /// Represents a profile containing folders and layers /// public sealed class Profile : ProfileElement { private readonly object _lock = new(); private bool _isActivated; internal Profile(ProfileModule module, string name) : base(null!) { ProfileEntity = new ProfileEntity(); EntityId = Guid.NewGuid(); Profile = this; Module = module; Name = name; UndoStack = new Stack(); RedoStack = new Stack(); Folder _ = new(this, "Root folder"); Save(); } internal Profile(ProfileModule module, ProfileEntity profileEntity) : base(null!) { Profile = this; ProfileEntity = profileEntity; EntityId = profileEntity.Id; Module = module; UndoStack = new Stack(); RedoStack = new Stack(); Load(); } /// /// Gets the module backing this profile /// public ProfileModule Module { get; } /// /// Gets a boolean indicating whether this profile is activated /// public bool IsActivated { get => _isActivated; private set => SetAndNotify(ref _isActivated, value); } /// /// Gets the profile entity this profile uses for persistent storage /// public ProfileEntity ProfileEntity { get; internal set; } internal Stack UndoStack { get; set; } internal Stack RedoStack { get; set; } /// public override void Update(double deltaTime) { lock (_lock) { if (Disposed) throw new ObjectDisposedException("Profile"); if (!IsActivated) throw new ArtemisCoreException($"Cannot update inactive profile: {this}"); foreach (ProfileElement profileElement in Children) profileElement.Update(deltaTime); } } /// public override void Render(SKCanvas canvas, SKPointI basePosition) { lock (_lock) { if (Disposed) throw new ObjectDisposedException("Profile"); if (!IsActivated) throw new ArtemisCoreException($"Cannot render inactive profile: {this}"); foreach (ProfileElement profileElement in Children) profileElement.Render(canvas, basePosition); } } /// public override void Reset() { foreach (ProfileElement child in Children) child.Reset(); } /// /// Retrieves the root folder of this profile /// /// The root folder of the profile /// public Folder GetRootFolder() { if (Disposed) throw new ObjectDisposedException("Profile"); return (Folder) Children.Single(); } /// public override string ToString() { return $"[Profile] {nameof(Name)}: {Name}, {nameof(IsActivated)}: {IsActivated}, {nameof(Module)}: {Module}"; } /// /// Populates all the LEDs on the elements in this profile /// /// The devices to use while populating LEDs public void PopulateLeds(IEnumerable devices) { if (Disposed) throw new ObjectDisposedException("Profile"); foreach (Layer layer in GetAllLayers()) layer.PopulateLeds(devices); } /// protected override void Dispose(bool disposing) { if (!disposing) return; OnDeactivating(); foreach (ProfileElement profileElement in Children) profileElement.Dispose(); ChildrenList.Clear(); IsActivated = false; Disposed = true; } internal override void Load() { if (Disposed) throw new ObjectDisposedException("Profile"); Name = ProfileEntity.Name; lock (ChildrenList) { // Remove the old profile tree foreach (ProfileElement profileElement in Children) profileElement.Dispose(); ChildrenList.Clear(); // Populate the profile starting at the root, the rest is populated recursively FolderEntity? rootFolder = ProfileEntity.Folders.FirstOrDefault(f => f.ParentId == EntityId); if (rootFolder == null) { Folder _ = new(this, "Root folder"); } else { AddChild(new Folder(this, this, rootFolder)); } } } internal override void Save() { if (Disposed) throw new ObjectDisposedException("Profile"); ProfileEntity.Id = EntityId; ProfileEntity.ModuleId = Module.Id; ProfileEntity.Name = Name; ProfileEntity.IsActive = IsActivated; foreach (ProfileElement profileElement in Children) profileElement.Save(); ProfileEntity.Folders.Clear(); ProfileEntity.Folders.AddRange(GetAllFolders().Select(f => f.FolderEntity)); ProfileEntity.Layers.Clear(); ProfileEntity.Layers.AddRange(GetAllLayers().Select(f => f.LayerEntity)); } internal void Activate(IEnumerable devices) { lock (_lock) { if (Disposed) throw new ObjectDisposedException("Profile"); if (IsActivated) return; PopulateLeds(devices); OnActivated(); IsActivated = true; } } #region Events /// /// Occurs when the profile has been activated. /// public event EventHandler? Activated; /// /// Occurs when the profile is being deactivated. /// public event EventHandler? Deactivated; private void OnActivated() { Activated?.Invoke(this, EventArgs.Empty); } private void OnDeactivating() { Deactivated?.Invoke(this, EventArgs.Empty); } #endregion } }