using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.IO; namespace Vanara.Collections { /// Provides an interface for a history of items. public interface IHistory : IEnumerable, INotifyCollectionChanged, INotifyPropertyChanged { /// Indicates the presence of items in the history that can be reached by calling . /// if this instance can seek backward; otherwise, . bool CanSeekBackward { get; } /// Indicates the presence of items in the history that can be reached by calling . /// if this instance can seek forward; otherwise, . bool CanSeekForward { get; } /// Gets the items in the history. /// The number of items. int Count { get; } /// Gets the value at a pointer within the history that represents the current item. /// The current item. /// There are no items in the history. T Current { get; } /// Adds the specified item as the last history entry and sets the property to it's value. /// The item to add to the history. /// indicates to remove all items forward of the current pointer; leaves the history intact. void Add(T item, bool removeForwardItems); /// Clears the history of all items. void Clear(); /// Gets a specified number of items starting at a location within the history. /// The maximum number of items to retrieve. The actual number of items returned may be less if not avaialable. /// The reference point within the history at which to start fetching items. /// A read-only list of items. IReadOnlyList GetItems(int count, SeekOrigin origin); /// /// Seeks through the history a given number of items starting at a known location within the history. This updates the property. /// /// The number of items to move. This value can be negative to search backwards or positive to search forwards. /// The reference point within the history at which to start seeking. /// The value at the new current pointer position. /// Cannot seek on an empty history. /// /// The number of items to move cannot be accomplished given the number of items in the history and the seek origin. /// T Seek(int count, SeekOrigin origin); /// Seeks one position backwards. /// The value at the new current pointer position. T SeekBackward(); /// Seeks one position forwards. /// The value at the new current pointer position. T SeekForward(); } /// Provides a history of items that lives efficiently in memory and whose size can change easily. /// The type of item to hold. /// /// /// public class History : IHistory { private readonly LinkedList activeHistory = new LinkedList(); private int capacity; private LinkedListNode current; /// Initializes a new instance of the class with a capacity of 256 items. public History() : this(256) { } /// Initializes a new instance of the class with a variable capacity. /// The capacity. public History(int capacity) => Capacity = capacity; /// Initializes a new instance of the class with a initial list of items. /// The items with which to initialize the history. public History(IEnumerable items) { foreach (var i in items) activeHistory.AddLast(i); capacity = activeHistory.Count; GetCurrent(); } /// Occurs when an item is added, removed, changed, moved, or the entire list is refreshed. public event NotifyCollectionChangedEventHandler CollectionChanged; /// Occurs when a property value changes. public event PropertyChangedEventHandler PropertyChanged; /// Indicates the presence of items in the history that can be reached by calling . /// if this instance can seek backward; otherwise, . public virtual bool CanSeekBackward => GetCurrent()?.Previous != null; /// Indicates the presence of items in the history that can be reached by calling . /// if this instance can seek forward; otherwise, . public virtual bool CanSeekForward => GetCurrent()?.Next != null; /// Gets or sets the capacity of the history, or the maximum number of items that it will hold. /// The history's capacity. public virtual int Capacity { get => capacity; set { if (capacity == value) return; if (value < activeHistory.Count) { var list = new List(); while (activeHistory.Count > value) { list.Add(activeHistory.First.Value); activeHistory.RemoveFirst(); } OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, list)); } capacity = value; OnPropertyChanged(); } } /// Gets the value at a pointer within the history that represents the current item. /// The current item. /// There are no items in the history. public virtual T Current => !(GetCurrent() is null) ? current.Value : throw new InvalidOperationException("There are no items in the history."); /// Gets the items in the history. /// The number of items. public virtual int Count => activeHistory.Count; /// /// Adds the specified item as the last history entry and sets the property to it's value. /// /// The item to add to the history. /// indicates to remove all items forward of the current pointer; leaves the history intact. public virtual void Add(T item, bool removeForwardItems = false) { if (removeForwardItems && CanSeekForward) { var ptr = activeHistory.Last; var items = new List(); while (ptr != current) { items.Add(ptr.Value); activeHistory.RemoveLast(); ptr = activeHistory.Last; } OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, items)); } var added = activeHistory.AddLast(item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); if (activeHistory.Count > Capacity) { var first = activeHistory.First; activeHistory.RemoveFirst(); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, first.Value)); } OnPropertyChanged(nameof(Count)); current = added; OnPropertyChanged(nameof(Current)); } /// Clears the history of all items. public virtual void Clear() { if (Count == 0) return; activeHistory.Clear(); current = null; OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); OnPropertyChanged(nameof(Count)); OnPropertyChanged(nameof(Current)); } /// Returns an enumerator that iterates through the collection. /// A that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() => activeHistory.GetEnumerator(); /// Gets a specified number of items starting at a location within the history. /// The maximum number of items to retrieve. The actual number of items returned may be less if not avaialable. /// The reference point within the history at which to start fetching items. /// A read-only list of items. public virtual IReadOnlyList GetItems(int count, SeekOrigin origin) { if (count == 0 || Count == 0) return (IReadOnlyList)new List(0); var ptr = origin switch { SeekOrigin.Begin => activeHistory.First, SeekOrigin.Current => GetCurrent(), SeekOrigin.End => activeHistory.Last, _ => throw new ArgumentOutOfRangeException(nameof(origin)), }; var items = new List(); for (int i = 0; i < Math.Abs(count) && ptr != null; i++) { items.Add(ptr.Value); ptr = count > 0 ? ptr.Next : ptr.Previous; } return (IReadOnlyList)items; } /// /// Seeks through the history a given number of items starting at a known location within the history. This updates the property. /// /// The number of items to move. This value can be negative to search backwards or positive to search forwards. /// The reference point within the history at which to start seeking. /// The value at the new current pointer position. /// Cannot seek on an empty history. /// /// The number of items to move cannot be accomplished given the number of items in the history and the seek origin. /// public virtual T Seek(int count, SeekOrigin origin) { if (activeHistory.Count == 0) throw new InvalidOperationException("Cannot seek on an empty history."); var ptr = origin switch { SeekOrigin.Begin => activeHistory.First, SeekOrigin.Current => GetCurrent(), SeekOrigin.End => activeHistory.Last, _ => throw new ArgumentOutOfRangeException(nameof(origin)), }; for (int i = 0; i < Math.Abs(count); i++) { if (ptr is null) throw new ArgumentOutOfRangeException(nameof(count)); ptr = count > 0 ? ptr.Next : ptr.Previous; } current = ptr; OnPropertyChanged(nameof(Current)); return Current; } /// Seeks one position backwards. /// The value at the new current pointer position. public virtual T SeekBackward() => CanSeekBackward ? Seek(-1, SeekOrigin.Current) : default; /// Seeks one position forwards. /// The value at the new current pointer position. public virtual T SeekForward() => CanSeekForward ? Seek(1, SeekOrigin.Current) : default; /// Returns an enumerator that iterates through a collection. /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// Raises the event. /// The instance containing the event data. protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) => CollectionChanged?.Invoke(this, e); /// Raises the event. /// Name of the property that has changed. protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); private LinkedListNode GetCurrent() { if (current is null) { current = activeHistory.Last; if (current != null) OnPropertyChanged(nameof(Current)); } return current; } } }