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

namespace Capnp
{
    /// <summary>
    /// SerializerState specialization for List(T) when T is unknown (generic), List(Data), List(Text), and List(List(...)).
    /// </summary>
    /// <typeparam name="TS">SerializerState which represents the element type</typeparam>
    public class ListOfPointersSerializer<TS>:
        SerializerState,
        IReadOnlyList<TS?>
        where TS: SerializerState, new()
    {
        /// <summary>
        /// Gets or sets the element at given index.
        /// </summary>
        /// <param name="index">Element index</param>
        /// <returns>Serializer state representing the desired element</returns>
        /// <exception cref="InvalidOperationException">List was not initialized, or attempting to overwrite a non-null element.</exception>
        /// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of range.</exception>
        public TS this[int index]
        {
            get
            {
                if (!IsAllocated)
                    throw new InvalidOperationException("Not initialized");

                if (index < 0 || index >= Count)
                    throw new IndexOutOfRangeException();

                return BuildPointer<TS>(index);
            }
            set
            {
                if (!IsAllocated)
                    throw new InvalidOperationException("Not initialized");

                if (index < 0 || index >= Count)
                    throw new IndexOutOfRangeException();

                Link(index, value, true);
            }
        }

        /// <summary>
        /// This list's element count.
        /// </summary>
        public int Count => ListElementCount;

        IEnumerable<TS?> Enumerate()
        {
            int count = Count;

            for (int i = 0; i < count; i++)
            {
                yield return TryGetPointer<TS>(i);
            }
        }

        /// <summary>
        /// Implements <see cref="IEnumerable{TS}"/>.
        /// </summary>
        public IEnumerator<TS?> GetEnumerator()
        {
            return Enumerate().GetEnumerator();
        }

        /// <summary>
        /// Initializes this list with a specific size. The list can be initialized only once.
        /// </summary>
        /// <param name="count">List element count</param>
        /// <exception cref="InvalidOperationException">The list was already initialized</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is negative or greater than 2^29-1</exception>
        public void Init(int count)
        {
            if (IsAllocated)
                throw new InvalidOperationException("Already initialized");

            if (count < 0)
                throw new ArgumentOutOfRangeException(nameof(count));

            SetListOfPointers(count);
        }

        /// <summary>
        /// Initializes the list with given content.
        /// </summary>
        /// <typeparam name="T">Item type</typeparam>
        /// <param name="items">List content. Can be null in which case the list is simply not initialized.</param>
        /// <param name="init">Serialization action to transfer a particular item into the serializer state.</param>
        /// <exception cref="InvalidOperationException">The list was already initialized</exception>
        /// <exception cref="ArgumentOutOfRangeException">More than 2^29-1 items.</exception>
        public void Init<T>(IReadOnlyList<T>? items, Action<TS, T> init)
        {
            if (items == null)
            {
                return;
            }

            Init(items.Count);

            for (int i = 0; i < items.Count; i++)
            {
                init(this[i], items[i]);
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}