using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace Capnp
{
    /// <summary>
    /// SerializerState specialization for List(Int*), List(UInt*), List(Float*), and List(Enum).
    /// </summary>
    /// <typeparam name="T">List element type, must be primitive. Static constructor will throw if the type does not work.</typeparam>
    public class ListOfPrimitivesSerializer<T> :
        SerializerState,
        IReadOnlyList<T>
        where T : struct
    {
        static readonly int ElementSize;

        static ListOfPrimitivesSerializer()
        {
            if (typeof(T).IsEnum)
            {
                ElementSize = Marshal.SizeOf(Enum.GetUnderlyingType(typeof(T)));
            }
            else
            {
                ElementSize = Marshal.SizeOf<T>();
            }
        }

        Span<T> Data => MemoryMarshal.Cast<ulong, T>(RawData);

        /// <summary>
        /// Gets or sets the value at given index.
        /// </summary>
        /// <param name="index">Element index</param>
        /// <returns>Element value</returns>
        public T this[int index]
        {
            get => Data[index];
            set => Data[index] = value;
        }

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

        /// <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));

            SetListOfValues((byte)(8 * ElementSize), count);
        }

        /// <summary>
        /// Initializes the list with given content.
        /// </summary>
        /// <param name="items">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> items)
        {
            if (items == null)
            {
                return;
            }

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

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

        IEnumerator IEnumerable.GetEnumerator() => Data.ToArray().GetEnumerator();
    }
}