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;
}
}
}