using System.Linq;

namespace System.Collections.Generic
{
    /// <summary>
    /// Цикличный массив
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class CyclicArray<T> : IEnumerable<T>
    {
        readonly T[] array;
        int used, current = -1;

        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="capacity"></param>
        public CyclicArray(int capacity)
        {
            array = new T[capacity];
        }

        /// <summary>
        /// Количество элементов в массиве
        /// </summary>
        public int Count => used;

        /// <summary>
        /// Добавить новый элемент<br/>
        /// Если capacity достигнуто, то вытеснит самый первый элемент
        /// </summary>
        /// <param name="item"></param>
        public void Add(T item)
        {
            current = (++current) % array.Length;
            array[current] = item;
            if (used < array.Length)
                used++;
            UpdatedInvoke(current, item);
        }

        /// <summary>
        /// Добавить новые элементы.<br/>
        /// Если capacity достигнуто, то вытеснит самые первые элементы.<br/>
        /// Не вызывает Updated!
        /// </summary>
        /// <param name="items"></param>
        public void AddRange(IEnumerable<T> items)
        {
            var capacity = array.Length;
            var newItems = items.TakeLast(capacity).ToArray();
            if (newItems.Length == capacity)
            {
                Array.Copy(newItems, array, capacity);
                current = capacity - 1;
            }
            else
            {
                current = (++current) % capacity;
                var countToEndOfArray = capacity - current;
                if (newItems.Length <= countToEndOfArray)
                {
                    Array.Copy(newItems, 0, array, current, newItems.Length);
                    current += newItems.Length - 1;
                }
                else
                {
                    var firstStepLength = countToEndOfArray;
                    Array.Copy(newItems, 0, array, current, firstStepLength);
                    var secondStepCount = newItems.Length - firstStepLength;
                    Array.Copy(newItems, firstStepLength, array, 0, secondStepCount);
                    current = secondStepCount - 1;
                }
            }

            if (used < capacity)
            {
                used += newItems.Length;
                used = used > capacity ? capacity : used;
            }
        }

        /// <summary>
        /// Индекс
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public T this[int index]
        {
            get
            {
                if (used == 0)
                    throw new IndexOutOfRangeException();

                var i = (current + 1 + index) % used;
                return array[i];
            }
            set
            {
                var devider = used > 0 ? used : array.Length;
                var i = (current + 1 + index) % devider;
                array[i] = value;
                UpdatedInvoke(current, value);
            }
        }

        /// <summary>
        /// событие на изменение элемента в массиве
        /// </summary>
        public event EventHandler<(int index, T value)>? Updated;
        private void UpdatedInvoke(int index, T value)
        {
            Updated?.Invoke(this, (index, value));
        }

        /// <summary>
        /// Агрегирование значения по всему массиву
        /// </summary>
        /// <typeparam name="Tout"></typeparam>
        /// <param name="func"></param>
        /// <param name="startValue"></param>
        /// <returns></returns>
        public Tout Aggregate<Tout>(Func<T, Tout, Tout> func, Tout startValue)
        {
            Tout result = startValue;
            for (int i = 0; i < used; i++)
                result = func(this[i], result);
            return result;
        }

        /// <inheritdoc/>
        public IEnumerator<T> GetEnumerator()
            => new CyclycListEnumerator<T>(array, current, used);

        /// <inheritdoc/>
        IEnumerator IEnumerable.GetEnumerator()
        => GetEnumerator();

        class CyclycListEnumerator<Te> : IEnumerator<Te>
        {
            private readonly Te[] array;
            private readonly int used;
            private readonly int first;
            private int current = -1;

            public CyclycListEnumerator(Te[] array, int first, int used)
            {
                this.array = new Te[array.Length];
                array.CopyTo(this.array, 0);
                this.used = used;
                this.first = first;
            }

            public Te Current
            {
                get
                {
                    if (IsCurrentOk())
                    {
                        var i = (current + first + 1) % used;
                        return array[i];
                    }
                    else
                        return default!;
                }
            }

            object? IEnumerator.Current => Current;

            public void Dispose() {; }

            private bool IsCurrentOk() => current >= 0 && current < used;

            public bool MoveNext()
            {
                if (current < used)
                    current++;
                return IsCurrentOk();
            }

            public void Reset()
            {
                current = -1;
            }
        }

        /// <summary>
        /// Очистить весь массив
        /// </summary>
        public void Clear()
        {
            used = 0;
            current = -1;
        }
    }
}