text: ﻿using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Linq;
  
  namespace Spect.Net.SpectrumEmu.Utility
  {
      /// <summary>
      /// This class implements an LRU list with a fixed capacity that supports only
      /// Add operations.
      /// </summary>
      /// <typeparam name="T">Item type</typeparam>
      /// <remarks>
      /// Whenever the capacity is exceeded, a newly added item erase the oldest one.
      /// </remarks>
      public class LruList<T>: IList<T>
      {
          // --- Index that shows the write position
          private int _writeIndex;
  
          // --- Index that shows the read position
          private int _readIndex;
  
          // --- Number of items
          private int _count;
  
          // --- The list that holds the items
          private readonly List<T> _items;
  
          public int Capacity { get; }
  
  
          /// <summary>Initializes a new instance of the <see cref="T:System.Object" /> class.</summary>
          public LruList(int capacity = 10)
          {
              Capacity = capacity;
              _items = new List<T>(capacity);
              Clear();
          }
  
          /// <summary>
          /// Returns an enumerator that iterates through the collection.
          /// </summary>
          /// <returns>
          /// An enumerator that can be used to iterate through the collection.
          /// </returns>
          public IEnumerator<T> GetEnumerator()
          {
              return Enumerate().GetEnumerator();
          }
  
          /// <summary>
          /// Returns an enumerator that iterates through a collection.
          /// </summary>
          /// <returns>
          /// An <see cref="T:System.Collections.IEnumerator" /> object that can 
          /// be used to iterate through the collection.
          /// </returns>
          IEnumerator IEnumerable.GetEnumerator()
          {
              return GetEnumerator();
          }
  
          /// <summary>
          /// Enumerates the items in the list
          /// </summary>
          private IEnumerable<T> Enumerate()
          {
              var readIndex = _readIndex;
              for (var i = 0; i < _count; i++)
              {
                  yield return _items[readIndex];
                  readIndex = (readIndex + 1) % Capacity;
              }
          }
  
          /// <summary>
          /// Enumerates the items in the list in reverse order
          /// </summary>
          public IEnumerable<T> Reverse()
          {
              var readIndex = (_readIndex + _count - 1) % Capacity;
              for (var i = 0; i < _count; i++)
              {
                  yield return _items[readIndex];
                  readIndex = (readIndex - 1 + Capacity) % Capacity;
              }
          }
          
          /// <summary>
          /// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1" />.
          /// </summary>
          /// <param name="item">
          /// The object to add to the <see cref="T:System.Collections.Generic.ICollection`1" />.
          /// </param>
          /// <exception cref="T:System.NotSupportedException">The 
          /// <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.
          /// </exception>
          public void Add(T item)
          {
              if (_count < Capacity)
              {
                  _count++;
              }
              else
              {
                  _readIndex = (_readIndex + 1) % Capacity;
              }
              _items[_writeIndex] = item;
              _writeIndex = (_writeIndex + 1) % Capacity;
          }
  
          /// <summary>
          /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1" />.
          /// </summary>
          /// <exception cref="T:System.NotSupportedException">The 
          /// <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only. 
          /// </exception>
          public void Clear()
          {
              _readIndex = 0;
              _writeIndex = 0;
              _count = 0;
              _items.Clear();
              for (var i = 0; i < Capacity; i++)
              {
                  _items.Add(default(T));
              }
          }
  
          /// <summary>
          /// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1" /> 
          /// contains a specific value.
          /// </summary>
          /// <param name="item">
          /// The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1" />.
          /// </param>
          /// <returns>
          /// <see langword="true" /> if <paramref name="item" /> is found in the 
          /// <see cref="T:System.Collections.Generic.ICollection`1" />; otherwise, 
          /// <see langword="false" />.
          /// </returns>
          public bool Contains(T item) => Enumerate().Any(t => t.Equals(item));
  
          /// <summary>
          /// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1" /> 
          /// to an <see cref="T:System.Array" />, starting at a particular 
          /// <see cref="T:System.Array" /> index.
          /// </summary>
          /// <param name="array">
          /// The one-dimensional <see cref="T:System.Array" /> that is the destination 
          /// of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1" />. 
          /// The <see cref="T:System.Array" /> must have zero-based indexing.
          /// </param>
          /// <param name="arrayIndex">
          /// The zero-based index in <paramref name="array" /> at which copying begins.
          /// </param>
          /// <exception cref="T:System.ArgumentNullException">
          /// <paramref name="array" /> is <see langword="null" />.
          /// </exception>
          /// <exception cref="T:System.ArgumentOutOfRangeException">
          /// <paramref name="arrayIndex" /> is less than 0.
          /// </exception>
          /// <exception cref="T:System.ArgumentException">
          /// The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1" /> 
          /// is greater than the available space from <paramref name="arrayIndex" /> to the 
          /// end of the destination <paramref name="array" />.
          /// </exception>
          public void CopyTo(T[] array, int arrayIndex)
          {
              var idx = arrayIndex;
              foreach (var item in Enumerate())
              {
                  array[idx++] = item;
              }
          }
  
          /// <summary>
          /// Removes the first occurrence of a specific object from the 
          /// <see cref="T:System.Collections.Generic.ICollection`1" />.
          /// </summary>
          /// <param name="item">
          /// The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1" />.
          /// </param>
          /// <returns>
          /// <see langword="true" /> if <paramref name="item" /> was successfully 
          /// removed from the <see cref="T:System.Collections.Generic.ICollection`1" />; 
          /// otherwise, <see langword="false" />. This method also returns 
          /// <see langword="false" /> if <paramref name="item" /> is not found in the original 
          /// <see cref="T:System.Collections.Generic.ICollection`1" />.
          /// </returns>
          /// <exception cref="T:System.NotSupportedException">
          /// The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.
          /// </exception>
          public bool Remove(T item) => throw new NotSupportedException();
  
          /// <summary>
          /// Gets the number of elements contained in the 
          /// <see cref="T:System.Collections.Generic.ICollection`1" />.
          /// </summary>
          /// <returns>
          /// The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />.
          /// </returns>
          public int Count => _count;
  
          /// <summary>
          /// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1" /> 
          /// is read-only.
          /// </summary>
          /// <returns>
          /// <see langword="true" /> if the <see cref="T:System.Collections.Generic.ICollection`1" /> 
          /// is read-only; otherwise, <see langword="false" />.
          /// </returns>
          public bool IsReadOnly => false;
  
          /// <summary>
          /// Determines the index of a specific item in the 
          /// <see cref="T:System.Collections.Generic.IList`1" />.
          /// </summary>
          /// <param name="item">
          /// The object to locate in the <see cref="T:System.Collections.Generic.IList`1" />.
          /// </param>
          /// <returns>
          /// The index of <paramref name="item" /> if found in the list; otherwise, -1.
          /// </returns>
          public int IndexOf(T item)
          {
              var idx = 0;
              foreach (var t in Enumerate())
              {
                  if (t.Equals(item))
                  {
                      return idx;
                  }
                  idx++;
              }
              return -1;
          }
  
          /// <summary>
          /// Inserts an item to the <see cref="T:System.Collections.Generic.IList`1" /> 
          /// at the specified index.
          /// </summary>
          /// <param name="index">
          /// The zero-based index at which <paramref name="item" /> should be inserted.
          /// </param>
          /// <param name="item">
          /// The object to insert into the <see cref="T:System.Collections.Generic.IList`1" />.
          /// </param>
          /// <exception cref="T:System.ArgumentOutOfRangeException">
          /// <paramref name="index" /> is not a valid index in the 
          /// <see cref="T:System.Collections.Generic.IList`1" />.
          /// </exception>
          /// <exception cref="T:System.NotSupportedException">
          /// The <see cref="T:System.Collections.Generic.IList`1" /> is read-only.
          /// </exception>
          public void Insert(int index, T item) => throw new NotSupportedException();
  
          /// <summary>
          /// Removes the <see cref="T:System.Collections.Generic.IList`1" /> 
          /// item at the specified index.
          /// </summary>
          /// <param name="index">
          /// The zero-based index of the item to remove.
          /// </param>
          /// <exception cref="T:System.ArgumentOutOfRangeException">
          /// <paramref name="index" /> is not a valid index in the 
          /// <see cref="T:System.Collections.Generic.IList`1" />.
          /// </exception>
          /// <exception cref="T:System.NotSupportedException">
          /// The <see cref="T:System.Collections.Generic.IList`1" /> is read-only.
          /// </exception>
          public void RemoveAt(int index) => throw new NotSupportedException();
  
          /// <summary>
          /// Gets or sets the element at the specified index.
          /// </summary>
          /// <param name="index">
          /// The zero-based index of the element to get or set.
          /// </param>
          /// <returns>
          /// The element at the specified index.
          /// </returns>
          /// <exception cref="T:System.ArgumentOutOfRangeException">
          /// <paramref name="index" /> is not a valid index in the 
          /// <see cref="T:System.Collections.Generic.IList`1" />.
          /// </exception>
          /// <exception cref="T:System.NotSupportedException">
          /// The property is set and the <see cref="T:System.Collections.Generic.IList`1" /> 
          /// is read-only.
          /// </exception>
          public T this[int index]
          {
              get => _items[(_readIndex + index) % Capacity];
              set => throw new NotSupportedException();
          }
      }
  }