using System.Collections;
using System.Collections.Generic;

namespace pdinklag
{
    /// <summary>
    /// A small array of up to 8 items without any allocations.
    /// If more items are needed, an additional array is allocated to store the remaining items.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public struct InlineArray<T> : IReadOnlyList<T>
    {
        public static InlineArray<T> MaxNonAlloc() => new(8);

        private class Enumerator : IEnumerator<T>
        {
            private readonly InlineArray<T> _copy;
            private int _current;

            public T Current => _copy[_current];
            object IEnumerator.Current => Current;

            public Enumerator(ref InlineArray<T> _array)
            {
                _copy = new InlineArray<T>(ref _array);
                _current = -1;
            }

            public bool MoveNext()
            {
                if (_current + 1 < _copy.Length)
                {
                    ++_current;
                    return true;
                }
                else
                {
                    return false;
                }
            }

            public void Reset()
            {
                _current = 0;
            }

            public void Dispose()
            {
            }
        }

        private T _x0, _x1, _x2, _x3, _x4, _x5, _x6, _x7;
        private readonly T[] _more;

        public readonly int Length;
        public int Count => Length;

        public T this[int i]
        {
            get
            {
                switch (i)
                {
                    case 0: return _x0;
                    case 1: return _x1;
                    case 2: return _x2;
                    case 3: return _x3;
                    case 4: return _x4;
                    case 5: return _x5;
                    case 6: return _x6;
                    case 7: return _x7;
                    default: return _more[i - 8];
                }
            }

            set
            {
                switch (i)
                {
                    case 0: _x0 = value; break;
                    case 1: _x1 = value; break;
                    case 2: _x2 = value; break;
                    case 3: _x3 = value; break;
                    case 4: _x4 = value; break;
                    case 5: _x5 = value; break;
                    case 6: _x6 = value; break;
                    case 7: _x7 = value; break;
                    default: _more[i - 8] = value; break;
                }
            }
        }

        /// <summary>
        /// Shallow (!) copy.
        /// </summary>
        /// <param name="other">the array to copy</param>
        private InlineArray(ref InlineArray<T> other)
        {
            _x0 = other._x0;
            _x1 = other._x1;
            _x2 = other._x2;
            _x3 = other._x3;
            _x4 = other._x4;
            _x5 = other._x5;
            _x6 = other._x6;
            _x7 = other._x7;
            _more = other._more;
            Length = other.Length;
        }

        public InlineArray(int length) : this()
        {
            _more = length > 8 ? new T[length - 8] : null;
            Length = length;
        }

        public InlineArray(T x0) : this()
        {
            Length = 1;
            _x0 = x0;
        }

        public InlineArray(T x0, T x1) : this()
        {
            Length = 2;
            _x0 = x0;
            _x1 = x1;
        }

        public InlineArray(T x0, T x1, T x2) : this()
        {
            Length = 3;
            _x0 = x0;
            _x1 = x1;
            _x2 = x2;
        }

        public InlineArray(T x0, T x1, T x2, T x3) : this()
        {
            Length = 4;
            _x0 = x0;
            _x1 = x1;
            _x2 = x2;
            _x3 = x3;
        }

        public InlineArray(T x0, T x1, T x2, T x3, T x4) : this()
        {
            Length = 5;
            _x0 = x0;
            _x1 = x1;
            _x2 = x2;
            _x3 = x3;
            _x4 = x4;
        }

        public InlineArray(T x0, T x1, T x2, T x3, T x4, T x5) : this()
        {
            Length = 6;
            _x0 = x0;
            _x1 = x1;
            _x2 = x2;
            _x3 = x3;
            _x4 = x4;
            _x5 = x5;
        }

        public InlineArray(T x0, T x1, T x2, T x3, T x4, T x5, T x6) : this()
        {
            Length = 7;
            _x0 = x0;
            _x1 = x1;
            _x2 = x2;
            _x3 = x3;
            _x4 = x4;
            _x5 = x5;
            _x6 = x6;
        }

        public InlineArray(T x0, T x1, T x2, T x3, T x4, T x5, T x6, T x7)
        {
            Length = 8;
            _x0 = x0;
            _x1 = x1;
            _x2 = x2;
            _x3 = x3;
            _x4 = x4;
            _x5 = x5;
            _x6 = x6;
            _x7 = x7;
            _more = null;
        }

        public int IndexOf<Q>(Q item) where Q : class, T
        {
            if (Length > 0 && (_x0 as Q) == item) return 0;
            if (Length > 1 && (_x1 as Q) == item) return 1;
            if (Length > 2 && (_x2 as Q) == item) return 2;
            if (Length > 3 && (_x3 as Q) == item) return 3;
            if (Length > 4 && (_x4 as Q) == item) return 4;
            if (Length > 5 && (_x5 as Q) == item) return 5;
            if (Length > 6 && (_x6 as Q) == item) return 6;
            if (Length > 7 && (_x7 as Q) == item) return 7;
            for (var i = 0; i < Length - 8; i++)
            {
                if ((_more[i] as Q) == item) return i + 8;
            }
            return -1;
        }

        public bool Contains<Q>(Q what) where Q : class, T => IndexOf(what) >= 0;

        public InlineArray<T> Prefix(int num)
        {
            var copy = new InlineArray<T>(num);
            if (num > 0) copy._x0 = _x0;
            if (num > 1) copy._x1 = _x1;
            if (num > 2) copy._x2 = _x2;
            if (num > 3) copy._x3 = _x3;
            if (num > 4) copy._x4 = _x4;
            if (num > 5) copy._x5 = _x5;
            if (num > 6) copy._x6 = _x6;
            if (num > 7) copy._x7 = _x7;
            for (var i = 0; i < num - 8; i++) copy._more[i] = _more[i];
            return copy;
        }

        public IEnumerator<T> GetEnumerator() => new Enumerator(ref this);
        IEnumerator IEnumerable.GetEnumerator() => new Enumerator(ref this);
    }
}
