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

namespace Capnp
{
    /// <summary>
    /// SerializerState specialization for a list of capabilities.
    /// </summary>
    /// <typeparam name="T">Capability interface</typeparam>
    public class ListOfCapsSerializer<T> :
        SerializerState,
        IReadOnlyList<T>
        where T : class
    {
        /// <summary>
        /// Constructs an instance.
        /// </summary>
        /// <exception cref="Rpc.InvalidCapabilityInterfaceException"><typeparamref name="T"/> does not quality as capability interface.
        /// The implementation might attempt to throw this exception earlier in the static constructor (during type load). Currently it doesn't
        /// because there is a significant risk of messing something up and ending with a hard-to-debug <see cref="TypeLoadException"/>.</exception>
        public ListOfCapsSerializer()
        {
            Rpc.CapabilityReflection.ValidateCapabilityInterface(typeof(T));
        }

        /// <summary>
        /// Gets or sets the capability at given element index.
        /// </summary>
        /// <param name="index">Element index</param>
        /// <returns>Proxy object of capability at given element index</returns>
        /// <exception cref="InvalidOperationException">List was not initialized, or attempting to overwrite an already set element.</exception>
        /// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of range.</exception>
        public T this[int index]
        {
            get => Rpc.CapabilityReflection.CreateProxy<T>(DecodeCapPointer(index)) as T;
            set
            {
                if (!IsAllocated)
                    throw new InvalidOperationException("Call Init() first");

                if (index < 0 || index >= RawData.Length)
                    throw new IndexOutOfRangeException("index out of range");

                uint id = ProvideCapability(value);
                WirePointer ptr = default;
                ptr.SetCapability(id);
                RawData[index] = id;
            }
        }

        /// <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)
        {
            SetListOfPointers(count);
        }

        /// <summary>
        /// Initializes the list with given content.
        /// </summary>
        /// <param name="caps">List content. Can be null in which case the list is simply not initialized.</param>
        /// <exception cref="InvalidOperationException">The list was already initialized</exception>
        /// <exception cref="ArgumentOutOfRangeException">More than 2^29-1 items.</exception>
        public void Init(IReadOnlyList<T> caps)
        {
            if (caps == null)
            {
                return;
            }

            Init(caps.Count);

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

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

        IEnumerable<T> Enumerate()
        {
            int count = Count;
            for (int i = 0; i < count; i++)
                yield return this[i];
        }

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

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