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

namespace Capnp
{
    /// <summary>
    /// Implements the heart of serialization. Exposes all functionality to encode serialized data.
    /// Although it is public, you should not use it directly. Instead, use the reader, writer, and domain class adapters which are produced
    /// by the code generator. Particularly, those writer classes are actually specializations of SerializerState, adding convenience methods
    /// for accessing the struct's fields.
    /// </summary>
    public class SerializerState : IStructSerializer, IDisposable
    {
        /// <summary>
        /// Constructs a SerializerState instance for use in RPC context.
        /// This particularly means that the capability table will be initialized.
        /// </summary>
        /// <typeparam name="T">Type of state (must inherit from SerializerState)</typeparam>
        /// <returns>root object state</returns>
        public static T CreateForRpc<T>() where T: SerializerState, new()
        {
            var mb = MessageBuilder.Create();
            mb.InitCapTable();
            var s = new T();
            s.Bind(mb);
            return s;
        }

        internal MessageBuilder? MsgBuilder { get; set; }
        internal ISegmentAllocator? Allocator => MsgBuilder?.Allocator;
        internal List<Rpc.ConsumedCapability>? Caps => MsgBuilder?.Caps;
        internal SerializerState? Owner { get; set; }
        internal int OwnerSlot { get; set; }
        internal uint SegmentIndex { get; set; }
        internal int Offset { get; set; }
        internal uint WordsAllocated { get; set; }
        internal int ListElementCount { get; set; }
        internal ushort StructDataCount { get; set; }
        internal ushort StructPtrCount { get; set; }
        internal uint CapabilityIndex { get; set; }

        SerializerState[]? _linkedStates;
        bool _disposed;

        /// <summary>
        /// Constructs an unbound serializer state.
        /// </summary>
        public SerializerState()
        {
            Offset = -1;
            ListElementCount = -1;
        }

        /// <summary>
        /// Constructs a serializer state which is bound to a particular message builder.
        /// </summary>
        /// <param name="messageBuilder">message builder to bind</param>
        public SerializerState(MessageBuilder messageBuilder)
        {
            MsgBuilder = messageBuilder ?? throw new ArgumentNullException(nameof(messageBuilder));
            Offset = -1;
            ListElementCount = -1;
        }

        internal void Bind(MessageBuilder messageBuilder)
        {
            MsgBuilder = messageBuilder ?? throw new ArgumentNullException(nameof(messageBuilder));
        }

        internal void Bind(SerializerState owner, int ownerSlot)
        {
            Owner = owner ?? throw new ArgumentNullException(nameof(owner));
            OwnerSlot = ownerSlot;
            MsgBuilder = owner.MsgBuilder;
        }

        internal void InheritFrom(SerializerState other)
        {
            SegmentIndex = other.SegmentIndex;
            Offset = other.Offset;
            WordsAllocated = other.WordsAllocated;
            ListElementCount = other.ListElementCount;
            StructDataCount = other.StructDataCount;
            StructPtrCount = other.StructPtrCount;
            Kind = other.Kind;
            CapabilityIndex = other.CapabilityIndex;
            _linkedStates = other._linkedStates;
        }

        /// <summary>
        /// Represents this state by a different serializer state specialization. This is similar to a type-cast: The underlying object remains the same,
        /// but the specialization adds a particular "view" on that data.
        /// </summary>
        /// <typeparam name="TS">target serializer state type</typeparam>
        /// <returns>serializer state instance</returns>
        /// <exception cref="InvalidOperationException">The target serializer state is incompatible to this instance, because the instances do not agree on the
        /// kind of underlying object (e.g. struct with particular data/pointer section size, list of something)</exception>
        public TS Rewrap<TS>() where TS: SerializerState, new()
        {
            if (this is TS ts)
                return ts;

            ts = new TS();

            if (Kind != ObjectKind.Nil)
            {
                static InvalidOperationException InvalidWrap() =>
                    new InvalidOperationException("Incompatible cast");

                switch (ts.Kind)
                {
                    case ObjectKind.Struct:
                    case ObjectKind.ListOfStructs:
                        if (ts.Kind != Kind ||
                            ts.StructDataCount != StructDataCount ||
                            ts.StructPtrCount != StructPtrCount)
                            throw InvalidWrap();
                        break;

                    case ObjectKind.Nil:
                        break;

                    default:
                        if (ts.Kind != Kind)
                            throw InvalidWrap();
                        break;
                }

                ts.InheritFrom(this);
            }

            if (Owner != null)
                ts.Bind(Owner, OwnerSlot);
            else
                ts.Bind(MsgBuilder ?? throw Unbound());

            return ts;
        }

        /// <summary>
        /// Whether storage space for the underlying object was already allocated. Note that allocation happens
        /// lazily, i.e. constructing a SerializerState and binding it to a MessageBuilder does NOT yet result in allocation.
        /// </summary>
        public bool IsAllocated => Offset >= 0;

        Span<ulong> SegmentSpan => IsAllocated && Allocator != null ? Allocator.Segments[(int)SegmentIndex].Span : Span<ulong>.Empty;
        Span<ulong> FarSpan(uint index) => Allocator!.Segments[(int)index].Span;

        /// <summary>
        /// Given this state describes a struct and is allocated, returns the struct's data section.
        /// </summary>
        public Span<ulong> StructDataSection => SegmentSpan.Slice(Offset, StructDataCount);

        /// <summary>
        /// Returns the allocated memory slice (given this state already is allocated). Note that this definition is somewhat
        /// non-symmetric to <code>DeserializerState.RawData</code>. Never mind: You should not use it directly, anyway.
        /// </summary>
        public Span<ulong> RawData => IsAllocated ? SegmentSpan.Slice(Offset, (int)WordsAllocated) : Span<ulong>.Empty;

        /// <summary>
        /// The kind of object this state currently represents.
        /// </summary>
        public ObjectKind Kind { get; internal set; }

        void AllocateWords(uint count)
        {
            if (count == 0)
            {
                SegmentIndex = 0;
                Offset = 0;
            }
            else
            {
                if (Allocator == null)
                    throw Unbound();

                SegmentIndex = Owner?.SegmentIndex ?? SegmentIndex;
                Allocator.Allocate(count, SegmentIndex, out var slice, false);
                SegmentIndex = slice.SegmentIndex;
                Offset = slice.Offset;
            }

            WordsAllocated = count;
        }

        /// <summary>
        /// Allocates storage for the underlying object. Does nothing if it is already allocated. From the point the object is allocated, its type cannot be changed
        /// anymore (e.g. changing from struct to list, or modifying the struct's section sizes).
        /// </summary>
        public void Allocate()
        {
            if (!IsAllocated)
            {
                switch (Kind)
                {
                    case ObjectKind.ListOfBits:
                        AllocateWords(checked((uint)ListElementCount + 63u) / 64);
                        break;

                    case ObjectKind.ListOfBytes:
                        AllocateWords(checked((uint)ListElementCount + 7u) / 8);
                        break;

                    case ObjectKind.ListOfInts:
                        AllocateWords(checked((uint)ListElementCount + 1u) / 2);
                        break;

                    case ObjectKind.ListOfLongs:
                    case ObjectKind.ListOfPointers:
                        AllocateWords(checked((uint)ListElementCount));
                        break;

                    case ObjectKind.ListOfShorts:
                        AllocateWords(checked((uint)ListElementCount + 3u) / 4);
                        break;

                    case ObjectKind.ListOfStructs:
                        AllocateWords(checked(1u + (uint)ListElementCount * (uint)(StructDataCount + StructPtrCount)));
                        var tag = default(WirePointer);
                        tag.BeginStruct(StructDataCount, StructPtrCount);
                        tag.ListOfStructsElementCount = ListElementCount;
                        SegmentSpan[Offset] = tag;
                        break;

                    case ObjectKind.Struct:
                        AllocateWords((uint)(StructDataCount + StructPtrCount));
                        break;

                    default:
                        AllocateWords(0);
                        break;
                }

                Owner?.Link(OwnerSlot, this);
            }
        }

        internal Rpc.ConsumedCapability DecodeCapPointer(int offset)
        {
            if (Caps == null)
                throw new InvalidOperationException("Capbility table not set");

            if (!IsAllocated)
            {
                return Rpc.NullCapability.Instance;
            }

            WirePointer pointer = RawData[offset];

            if (pointer.IsNull)
            {
                return Rpc.NullCapability.Instance;
            }

            if (pointer.Kind != PointerKind.Other)
            {
                throw new Rpc.RpcException("Expected a capability pointer, but got something different");
            }

            if (pointer.CapabilityIndex >= Caps.Count)
            {
                throw new Rpc.RpcException("Capability index out of range");
            }

            return Caps[(int)pointer.CapabilityIndex];
        }

        void EncodePointer(int offset, SerializerState target, bool allowCopy)
        {
            if (SegmentSpan[offset] != 0)
            {
                throw new InvalidOperationException("Won't replace an already allocated pointer to prevent memory leaks and security flaws");
            }

            if (target.Allocator != null &&
                target.Allocator != Allocator)
            {
                if (allowCopy)
                {
                    Allocate();

                    var targetCopy = new DynamicSerializerState(MsgBuilder!);
                    Reserializing.DeepCopy(target, targetCopy);
                    target = targetCopy;
                }
                else
                {
                    throw new InvalidOperationException("target was allocated on a different segment allocator");
                }
            }
            else
            {
                Allocate();
            }

            WirePointer targetPtr = default;

            switch (target.Kind)
            {
                case ObjectKind.ListOfBits:
                    targetPtr.BeginList(ListKind.ListOfBits, target.ListElementCount);
                    break;

                case ObjectKind.ListOfBytes:
                    targetPtr.BeginList(ListKind.ListOfBytes, target.ListElementCount);
                    break;

                case ObjectKind.ListOfEmpty:
                    targetPtr.BeginList(ListKind.ListOfEmpty, target.ListElementCount);
                    break;

                case ObjectKind.ListOfInts:
                    targetPtr.BeginList(ListKind.ListOfInts, target.ListElementCount);
                    break;

                case ObjectKind.ListOfLongs:
                    targetPtr.BeginList(ListKind.ListOfLongs, target.ListElementCount);
                    break;

                case ObjectKind.ListOfPointers:
                    targetPtr.BeginList(ListKind.ListOfPointers, target.ListElementCount);
                    break;

                case ObjectKind.ListOfShorts:
                    targetPtr.BeginList(ListKind.ListOfShorts, target.ListElementCount);
                    break;

                case ObjectKind.ListOfStructs:
                    int wordCount = target.ListElementCount * (target.StructDataCount + target.StructPtrCount);
                    targetPtr.BeginList(ListKind.ListOfStructs, wordCount);
                    break;

                case ObjectKind.Capability:
                    targetPtr.SetCapability(target.CapabilityIndex);
                    SegmentSpan[offset] = targetPtr;
                    return;

                case ObjectKind.Struct:
                    targetPtr.BeginStruct(target.StructDataCount, target.StructPtrCount);
                    if (target.StructDataCount == 0 && target.StructPtrCount == 0)
                    {
                        targetPtr.Offset = -1;
                        SegmentSpan[offset] = targetPtr;
                        return;
                    }
                    break;

                case ObjectKind.Nil:
                    SegmentSpan[offset] = 0;
                    return;

                default:
                    throw new NotImplementedException();
            }

            if (SegmentIndex != target.SegmentIndex)
            {
                WirePointer farPtr = default;

                if (Allocator!.Allocate(1, target.SegmentIndex, out var landingPadSlice, true))
                {
                    farPtr.SetFarPointer(target.SegmentIndex, landingPadSlice.Offset, false);
                    SegmentSpan[offset] = farPtr;
                    targetPtr.Offset = target.Offset - (landingPadSlice.Offset + 1);
                    FarSpan(target.SegmentIndex)[landingPadSlice.Offset] = targetPtr;
                }
                else
                {
                    Allocator.Allocate(2, 0, out landingPadSlice, false);
                    farPtr.SetFarPointer(landingPadSlice.SegmentIndex, landingPadSlice.Offset, true);
                    SegmentSpan[offset] = farPtr;
                    WirePointer farPtr2 = default;
                    farPtr2.SetFarPointer(target.SegmentIndex, target.Offset, false);
                    var farSpan = FarSpan(landingPadSlice.SegmentIndex);
                    farSpan[landingPadSlice.Offset] = farPtr2;
                    targetPtr.Offset = 0;
                    farSpan[landingPadSlice.Offset + 1] = targetPtr;
                }
            }
            else
            {
                targetPtr.Offset = target.Offset - (offset + 1);
                SegmentSpan[offset] = targetPtr;
            }
        }

        /// <summary>
        /// Links a sub-item (struct field or list element) of this state to another state. Usually, this operation is not necessary, since objects are constructed top-down.
        /// However, there might be some advanced scenarios where you want to reference the same object twice (also interesting for designing amplification attacks).
        /// The Cap'n Proto serialization intrinsically supports this, since messages are object graphs, not trees.
        /// </summary>
        /// <param name="slot">If this state describes a struct: Index into this struct's pointer table. 
        /// If this state describes a list of pointers: List element index.</param>
        /// <param name="target">state to be linked</param>
        /// <param name="allowCopy">Whether to deep copy the target state if it belongs to a different message builder than this state.</param>
        /// <exception cref="ArgumentNullException"><paramref name="target"/> is null</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="slot"/> out of range</exception>
        /// <exception cref="InvalidOperationException"><list type="bullet">
        /// <item><description>This state does neither describe a struct, nor a list of pointers</description></item>
        /// <item><description>Another state is already linked to the specified position (sorry, no overwrite allowed)</description></item>
        /// <item><description>This state and <paramref name="target"/> belong to different message builder, and<paramref name="allowCopy"/> is false</description></item>
        /// </list>
        /// </exception>
        protected void Link(int slot, SerializerState target, bool allowCopy = true)
        {
            if (target == null)
                throw new ArgumentNullException(nameof(target));

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

            if (!IsAllocated)
            {
                SegmentIndex = target.SegmentIndex;
                Allocate();
            }

            if (!target.IsAllocated)
            {
                target.Allocate();
            }

            switch (Kind)
            {
                case ObjectKind.Struct:
                    if (slot >= StructPtrCount)
                        throw new ArgumentOutOfRangeException(nameof(slot));

                    EncodePointer(Offset + StructDataCount + slot, target, allowCopy);

                    break;

                case ObjectKind.ListOfPointers:
                    if (slot >= ListElementCount)
                        throw new ArgumentOutOfRangeException(nameof(slot));

                    EncodePointer(Offset + slot, target, allowCopy);

                    break;

                default:
                    throw new InvalidOperationException("This object cannot own pointers to sub-objects");
            }

            _linkedStates![slot] = target;
        }

        /// <summary>
        /// Links a sub-item (struct field or list element) of this state to a capability.
        /// </summary>
        /// <param name="slot">If this state describes a struct: Index into this struct's pointer table. 
        /// If this state describes a list of pointers: List element index.</param>
        /// <param name="capabilityIndex">capability index inside the capability table</param>
        /// <exception cref="InvalidOperationException"><list type="bullet">
        /// <item><description>This state does neither describe a struct, nor a list of pointers</description></item>
        /// <item><description>Another state is already linked to the specified position (sorry, no overwrite allowed)</description></item></list>
        /// </exception>
        protected void LinkToCapability(int slot, uint? capabilityIndex)
        {
            var cstate = new SerializerState();
            cstate.SetCapability(capabilityIndex);
            Link(slot, cstate);
        }

        static InvalidOperationException AlreadySet() => new InvalidOperationException("The object type was already set");
        static InvalidOperationException Unbound() => new InvalidOperationException("This state is not bound to a MessageBuilder");

        void VerifyNotYetAllocated()
        {
            if (IsAllocated)
                throw new InvalidOperationException("Not permitted since the state is already allocated");
        }

        /// <summary>
        /// Determines the underlying object to be a struct.
        /// </summary>
        /// <param name="dataCount">Desired size of the struct's data section, in words</param>
        /// <param name="ptrCount">Desired size of the struct's pointer section, in words</param>
        /// <exception cref="InvalidOperationException">The object type was already set to something different</exception>
        protected void SetStruct(ushort dataCount, ushort ptrCount)
        {
            if (Kind == ObjectKind.Nil)
            {
                VerifyNotYetAllocated();

                Kind = ObjectKind.Struct;
                StructDataCount = dataCount;
                StructPtrCount = ptrCount;

                _linkedStates = new SerializerState[ptrCount];
            }
            else if (Kind != ObjectKind.Struct || StructDataCount != dataCount || StructPtrCount != ptrCount)
            {
                throw AlreadySet();
            }
        }

        /// <summary>
        /// Determines the underyling object to be a capability.
        /// </summary>
        /// <param name="capabilityIndex">Capability table index, or null to encode a null pointer</param>
        /// <exception cref="InvalidOperationException">The object type was already set to something different</exception>
        protected void SetCapability(uint? capabilityIndex)
        {
            if (capabilityIndex.HasValue)
            {
                if (Kind == ObjectKind.Nil)
                {
                    VerifyNotYetAllocated();

                    Kind = ObjectKind.Capability;
                    CapabilityIndex = capabilityIndex.Value;
                    Allocate();
                }
                else if (Kind != ObjectKind.Capability || CapabilityIndex != capabilityIndex)
                {
                    throw AlreadySet();
                }
            }
            else
            {
                if (Kind != ObjectKind.Nil)
                {
                    throw AlreadySet();
                }
            }
        }

        /// <summary>
        /// Determines the underlying object to be a list of (primitive) values.
        /// </summary>
        /// <param name="bitsPerElement">Element size in bits, must be 0 (void), 1 (bool), 8, 16, 32, or 64</param>
        /// <param name="totalCount">Desired element count</param>
        /// <exception cref="InvalidOperationException">The object type was already set to something different</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="bitsPerElement"/> outside allowed range, 
        /// <paramref name="totalCount"/> negative or exceeding 2^29-1</exception>
        protected void SetListOfValues(byte bitsPerElement, int totalCount)
        {
            var kind = bitsPerElement switch
            {
                0 => ObjectKind.ListOfEmpty,
                1 => ObjectKind.ListOfBits,
                8 => ObjectKind.ListOfBytes,
                16 => ObjectKind.ListOfShorts,
                32 => ObjectKind.ListOfInts,
                64 => ObjectKind.ListOfLongs,
                _ => throw new ArgumentOutOfRangeException(nameof(bitsPerElement)),
            };
            if (Kind == ObjectKind.Nil)
            {
                if (totalCount < 0)
                    throw new ArgumentOutOfRangeException(nameof(totalCount));

                VerifyNotYetAllocated();

                Kind = kind;
                ListElementCount = totalCount;

                Allocate();
            }
            else if (Kind != kind || ListElementCount != totalCount)
            {
                throw AlreadySet();
            }
        }

        /// <summary>
        /// Determines the underlying object to be a list of pointers.
        /// </summary>
        /// <param name="totalCount">Desired element count</param>
        /// <exception cref="InvalidOperationException">The object type was already set to something different</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="totalCount"/> negative or exceeding 2^29-1</exception>
        protected void SetListOfPointers(int totalCount)
        {
            if (Kind == ObjectKind.Nil)
            {
                if (totalCount < 0)
                    throw new ArgumentOutOfRangeException(nameof(totalCount));

                VerifyNotYetAllocated();

                Kind = ObjectKind.ListOfPointers;
                ListElementCount = totalCount;

                _linkedStates = new SerializerState[totalCount];

                Allocate();
            }
            else if (Kind != ObjectKind.ListOfPointers || ListElementCount != totalCount)
            {
                throw AlreadySet();
            }
        }

        /// <summary>
        /// Determines the underlying object to be a list of structs (fixed-width compound list).
        /// </summary>
        /// <param name="totalCount">Desired element count</param>
        /// <param name="dataCount">Desired size of each struct's data section, in words</param>
        /// <param name="ptrCount">Desired size of each struct's pointer section, in words</param>
        /// <exception cref="InvalidOperationException">The object type was already set to something different</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="totalCount"/> negative, or total word count would exceed 2^29-1</exception>
        protected void SetListOfStructs(int totalCount, ushort dataCount, ushort ptrCount)
        {
            if (Kind == ObjectKind.Nil)
            {
                if (totalCount < 0)
                    throw new ArgumentOutOfRangeException(nameof(totalCount));

                VerifyNotYetAllocated();

                Kind = ObjectKind.ListOfStructs;
                ListElementCount = totalCount;
                StructDataCount = dataCount;
                StructPtrCount = ptrCount;

                _linkedStates = new SerializerState[totalCount];

                Allocate();
            }
            else if (Kind != ObjectKind.ListOfStructs || 
                ListElementCount != totalCount ||
                StructDataCount != dataCount ||
                StructPtrCount != ptrCount)
            {
                throw AlreadySet();
            }
        }

        /// <summary>
        /// Determines the underlying object to be a list of bytes and encodes the given text.
        /// </summary>
        /// <param name="text">text to encode</param>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is null</exception>
        /// <exception cref="EncoderFallbackException">Trying to obtain the UTF-8 encoding might throw this exception.</exception>
        /// <exception cref="InvalidOperationException">The object type was already set to something different</exception>
        /// <exception cref="ArgumentOutOfRangeException">UTF-8 encoding exceeds 2^29-2 bytes</exception>
        protected void WriteText(string? text)
        {
            if (text == null)
            {
                VerifyNotYetAllocated();
            }
            else
            {
                byte[] srcBytes = Encoding.UTF8.GetBytes(text);
                SetListOfValues(8, srcBytes.Length + 1);
                var srcSpan = new ReadOnlySpan<byte>(srcBytes);
                var dstSpan = ListGetBytes();
                dstSpan = dstSpan.Slice(0, dstSpan.Length - 1);
                srcSpan.CopyTo(dstSpan);
            }
        }

        /// <summary>
        /// Writes data (up to 64 bits) into the underlying struct's data section. 
        /// The write operation must be aligned to fit within a single word.
        /// </summary>
        /// <param name="bitOffset">Start bit relative to the struct's data section, little endian</param>
        /// <param name="bitCount">Number of bits to write</param>
        /// <param name="value">Data bits to write</param>
        /// <exception cref="InvalidOperationException">The object was not determined to be a struct</exception>
        /// <exception cref="ArgumentOutOfRangeException">The data slice specified by <paramref name="bitOffset"/> and <paramref name="bitCount"/>
        /// is not completely within the struct's data section, misaligned, exceeds one word, or <paramref name="bitCount"/> is negative</exception>
        public void StructWriteData(ulong bitOffset, int bitCount, ulong value)
        {
            if (Kind != ObjectKind.Struct)
                throw new InvalidOperationException("This is not a struct");

            Allocate();

            int index = checked((int)(bitOffset / 64));
            int relBitOffset = (int)(bitOffset % 64);

            var data = StructDataSection;

            if (relBitOffset + bitCount > 64)
                throw new ArgumentOutOfRangeException(nameof(bitCount));

            if (bitCount == 64)
            {
                data[index] = value;
            }
            else
            {
                ulong mask = ~(((1ul << bitCount) - 1) << relBitOffset);
                data[index] = data[index] & mask | (value << relBitOffset);
            }
        }

        /// <summary>
        /// Reads data (up to 64 bits) from the underlying struct's data section. 
        /// The write operation must be aligned to fit within a single word.
        /// </summary>
        /// <param name="bitOffset">Start bit relative to the struct's data section, little endian</param>
        /// <param name="count">Number of bits to read</param>
        /// <returns>Data bits which were read</returns>
        /// <exception cref="InvalidOperationException">The object was not determined to be a struct</exception>
        /// <exception cref="ArgumentOutOfRangeException">The data slice specified by <paramref name="bitOffset"/> and <paramref name="count"/>
        /// is not completely within the struct's data section, misaligned, exceeds one word, or <paramref name="count"/> is negative</exception>
        public ulong StructReadData(ulong bitOffset, int count)
        {
            if (Kind != ObjectKind.Struct)
                throw new InvalidOperationException("This is not a struct");

            if (!IsAllocated)
                return 0;

            int index = checked((int)(bitOffset / 64));
            int relBitOffset = (int)(bitOffset % 64);

            var data = StructDataSection;

            if (index >= data.Length)
                return 0; // Assume backwards-compatible change

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

            if (relBitOffset + count > 64)
                throw new ArgumentOutOfRangeException(nameof(count));

            ulong word = data[index];

            if (count == 64)
            {
                return word;
            }
            else
            {
                ulong mask = (1ul << count) - 1;
                return (word >> relBitOffset) & mask;
            }
        }

        /// <summary>
        /// Constructs a new object at a struct field or list element, or returns the serializer state for an existing object.
        /// </summary>
        /// <typeparam name="TS">Target state type</typeparam>
        /// <param name="index">If the underlying object is a struct: index into the struct's pointer section.
        /// If the underlying object is a list of pointers: Element index</param>
        /// <returns>Bound serializer state instance</returns>
        /// <exception cref="InvalidOperationException"><list type="bullet">
        /// <item><description>The underlying object was not determined to be a struct or list of pointers.</description></item>
        /// <item><description>Object at given position was already built and is not compatible with the desired target serializer type.</description></item>
        /// </list></exception>
        /// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
        public TS BuildPointer<TS>(int index) where TS: SerializerState, new()
        {
            if (Kind != ObjectKind.Struct && Kind != ObjectKind.ListOfPointers)
                throw new InvalidOperationException("This is not a struct or list of pointers");

            ref var state = ref _linkedStates![index];

            if (state == null)
            {
                state = new TS();
                state.Bind(this, index);
            }

            return state.Rewrap<TS>();
        }

        /// <summary>
        /// Returns an existing serializer state for a struct field or list element, or null if no such object exists.
        /// </summary>
        /// <typeparam name="TS">Target state type</typeparam>
        /// <param name="index">If the underlying object is a struct: index into the struct's pointer section.
        /// If the underlying object is a list of pointers: Element index</param>
        /// <returns>Bound serializer state instance</returns>
        /// <exception cref="InvalidOperationException"><list type="bullet">
        /// <item><description>The underlying object was not determined to be a struct or list of pointers.</description></item>
        /// <item><description>Object at given position is not compatible with the desired target serializer type.</description></item>
        /// </list></exception>
        /// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
        public TS? TryGetPointer<TS>(int index) where TS : SerializerState, new()
        {
            if (Kind != ObjectKind.Struct && Kind != ObjectKind.ListOfPointers)
                throw new InvalidOperationException("This is not a struct or list of pointers");

            if (index < 0 || index >= _linkedStates!.Length)
                throw new ArgumentOutOfRangeException(nameof(index));

            var state = _linkedStates![index];

            if (state == null) return null;

            return state.Rewrap<TS>();
        }

        /// <summary>
        /// Convenience method for <code><![CDATA[BuildPointer<DynamicSerializerState>]]></code>
        /// </summary>
        /// <param name="index">If the underlying object is a struct: index into the struct's pointer section.
        /// If the underlying object is a list of pointers: Element index</param>
        /// <returns>Bound serializer state instance</returns>
        /// <exception cref="InvalidOperationException"><list type="bullet">
        /// <item><description>The underlying object was not determined to be a struct or list of pointers.</description></item>
        /// <item><description>Object at given position was already built and is not compatible with the desired target serializer type.</description></item>
        /// </list></exception>
        /// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
        public DynamicSerializerState BuildPointer(int index) => BuildPointer<DynamicSerializerState>(index);

        /// <summary>
        /// Convenience method for <code><![CDATA[TryGetPointer<SerializerState>]]></code>
        /// </summary>
        /// <param name="index">If the underlying object is a struct: index into the struct's pointer section.
        /// If the underlying object is a list of pointers: Element index</param>
        /// <returns>Bound serializer state instance</returns>
        /// <exception cref="InvalidOperationException"><list type="bullet">
        /// <item><description>The underlying object was not determined to be a struct or list of pointers.</description></item>
        /// <item><description>Object at given position is not compatible with the desired target serializer type.</description></item>
        /// </list></exception>
        /// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
        public SerializerState? TryGetPointer(int index) => TryGetPointer<SerializerState>(index);

        /// <summary>
        /// Reads text from a struct field or list element.
        /// </summary>
        /// <param name="index">If the underlying object is a struct: index into the struct's pointer section.
        /// If the underlying object is a list of pointers: Element index</param>
        /// <param name="defaultText">String to return in case of null</param>
        /// <returns>The decoded text</returns>
        public string? ReadText(int index, string? defaultText = null)
        {
            var b = BuildPointer(index);

            if (b.IsAllocated)
                return b.ListReadAsText();
            else
                return defaultText;
        }

        /// <summary>
        /// Encodes text into a struct field or list element.
        /// </summary>
        /// <param name="index">If the underlying object is a struct: index into the struct's pointer section.
        /// If the underlying object is a list of pointers: Element index</param>
        /// <param name="text">Text to encode</param>
        /// <exception cref="ArgumentNullException"><paramref name="text"/>is null</exception>
        /// <exception cref="InvalidOperationException"><list type="bullet">
        /// <item><description>The underlying object was not determined to be a struct or list of pointers.</description></item>
        /// <item><description>Object at given position was already set.</description></item>
        /// </list></exception>
        /// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
        public void WriteText(int index, string? text)
        {
            BuildPointer(index).WriteText(text);
        }

        /// <summary>
        /// Encodes text into a struct field or list element, with fallback to a default text.
        /// </summary>
        /// <param name="index">If the underlying object is a struct: index into the struct's pointer section.
        /// If the underlying object is a list of pointers: Element index</param>
        /// <param name="text">Text to encode</param>
        /// <param name="defaultText">Default text if <paramref name="text"/>> is null</param>
        /// <exception cref="ArgumentNullException">Both <paramref name="text"/> and <paramref name="defaultText"/> are null</exception>
        /// <exception cref="InvalidOperationException"><list type="bullet">
        /// <item><description>The underlying object was not determined to be a struct or list of pointers.</description></item>
        /// <item><description>Object at given position was already set.</description></item>
        /// </list></exception>
        /// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
        public void WriteText(int index, string? text, string? defaultText)
        {
            BuildPointer(index).WriteText(text ?? defaultText);
        }

        /// <summary>
        /// Returns a state which represents a fixed-width composite list element.
        /// </summary>
        /// <param name="index">Element index</param>
        /// <returns>Bound serializer state</returns>
        /// <exception cref="InvalidOperationException">Underlying object was not determined to be a fixed-width composite list.</exception>
        /// <exception cref="IndexOutOfRangeException"><paramref name="index"/>is out of bounds.</exception>
        public SerializerState ListBuildStruct(int index)
        {
            if (Kind != ObjectKind.ListOfStructs)
                throw new InvalidOperationException("This is not a list of structs");

            if (index < 0 || index >= _linkedStates!.Length)
                throw new ArgumentOutOfRangeException(nameof(index));

            ref var state = ref _linkedStates![index];

            if (state == null)
            {
                state = new SerializerState(MsgBuilder!);
                state.SetStruct(StructDataCount, StructPtrCount);
                state.SegmentIndex = SegmentIndex;
                state.Offset = Offset + 1 + index * (StructDataCount + StructPtrCount);
            }

            return _linkedStates[index];
        }

        /// <summary>
        /// Returns a state which represents a fixed-width composite list element.
        /// </summary>
        /// <typeparam name="TS">Target serializer state type</typeparam>
        /// <param name="index"></param>
        /// <returns>Bound serializer state</returns>
        /// <exception cref="InvalidOperationException">Underlying object was not determined to be a fixed-width composite list.</exception>
        /// <exception cref="IndexOutOfRangeException"><paramref name="index"/>is out of bounds.</exception>
        public TS ListBuildStruct<TS>(int index)
            where TS: SerializerState, new()
        {
            if (Kind != ObjectKind.ListOfStructs)
                throw new InvalidOperationException("This is not a list of structs");

            if (index < 0 || index >= _linkedStates!.Length)
                throw new ArgumentOutOfRangeException(nameof(index));

            ref var state = ref _linkedStates![index];

            if (state == null)
            {
                state = new TS();
                state.Bind(MsgBuilder!);
                state.SetStruct(StructDataCount, StructPtrCount);
                state.SegmentIndex = SegmentIndex;
                int stride = StructDataCount + StructPtrCount;
                state.Offset = Offset + stride * index + 1;
            }

            return (TS)state;
        }

        internal IReadOnlyList<TS> ListEnumerateStructs<TS>()
            where TS: SerializerState, new()
        {
            if (Kind != ObjectKind.ListOfStructs)
                throw new InvalidOperationException("This is not a list of structs");

            if (ListElementCount < 0)
                throw new InvalidOperationException("Define element count first");

            int minOffset = Offset + 1;
            int maxOffset = minOffset + ListElementCount;

            for (int offset = minOffset; offset < maxOffset; offset++)
            {
                ref var state = ref _linkedStates![offset - minOffset];

                if (state == null)
                {
                    state = new TS();
                    state.Bind(MsgBuilder!);
                    state.SetStruct(StructDataCount, StructPtrCount);
                    state.SegmentIndex = SegmentIndex;
                    state.Offset = offset;
                }
            }

            return _linkedStates!.LazyListSelect(ts => (TS)ts);
        }

        /// <summary>
        /// Sets an element of a list of bits.
        /// </summary>
        /// <param name="index">Element index</param>
        /// <param name="value">Element value</param>
        /// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
        /// <exception cref="InvalidOperationException">The underlying object was not set to a list of bits.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
        public void ListWriteValue(int index, bool value, bool defaultValue = false)
        {
            if (Kind != ObjectKind.ListOfBits)
                throw new InvalidOperationException("This is not a list of bits");

            if (index < 0 || index >= ListElementCount)
                throw new ArgumentOutOfRangeException(nameof(index));

            bool bit = value != defaultValue;
            int wordIndex = index / 64;
            int relBitOffset = index % 64;

            if (bit)
                SegmentSpan[Offset + wordIndex] |= (1ul << relBitOffset);
            else
                SegmentSpan[Offset + wordIndex] &= ~(1ul << relBitOffset);
        }

        /// <summary>
        /// Sets the list-of-bits' content.
        /// </summary>
        /// <param name="values">Content to set</param>
        /// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
        /// <exception cref="InvalidOperationException">The underlying object was not set to a list of bits.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="values"/> is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The given element count does not match the underlying list's element count.</exception>
        public void ListWriteValues(IReadOnlyList<bool> values, bool defaultValue = false)
        {
            if (Kind != ObjectKind.ListOfBits)
                throw new InvalidOperationException("This is not a list of bits");

            if (values == null)
                throw new ArgumentNullException(nameof(values));

            if (values.Count != ListElementCount)
                throw new ArgumentOutOfRangeException(nameof(values));

            int i, w = Offset;
            ulong v;

            for (i = 0; i < ListElementCount - 63; i += 64)
            {
                v = 0;

                for (int j = 63; j >= 0; j--)
                {
                    v <<= 1;
                    if (values[i + j]) v |= 1;
                }

                if (defaultValue) v = ~v;

                SegmentSpan[w++] = v;
            }

            if (i < ListElementCount)
            {
                v = 0;

                for (int k = ListElementCount - 1; k >= i; k--)
                {
                    v <<= 1;
                    if (values[k]) v |= 1;
                }

                if (defaultValue) v = ~v;

                SegmentSpan[w] = v;
            }
        }

        /// <summary>
        /// Sets an element of a list of bytes.
        /// </summary>
        /// <param name="index">Element index</param>
        /// <param name="value">Element value</param>
        /// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
        /// <exception cref="InvalidOperationException">The underlying object was not set to a list of bytes.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
        public void ListWriteValue(int index, byte value, byte defaultValue = 0)
        {
            if (Kind != ObjectKind.ListOfBytes)
                throw new InvalidOperationException("This is not a list of bytes");

            if (index < 0 || index >= ListElementCount)
                throw new ArgumentOutOfRangeException(nameof(index));

            byte x = (byte)(value ^ defaultValue);
            MemoryMarshal.Cast<ulong, byte>(SegmentSpan.Slice(Offset))[index] = x;
        }

        /// <summary>
        /// Returns the content of a list of bytes.
        /// </summary>
        /// <returns>The list bytes</returns>
        /// <exception cref="InvalidOperationException">The underlying object was not set to a list of bytes.</exception>
        public Span<byte> ListGetBytes()
        {
            if (Kind != ObjectKind.ListOfBytes)
                throw new InvalidOperationException("This is not a list of bytes");

            return MemoryMarshal.Cast<ulong, byte>(SegmentSpan.Slice(Offset)).Slice(0, ListElementCount);
        }

        /// <summary>
        /// Decodes a list of bytes as text.
        /// </summary>
        /// <returns>The decoded text.</returns>
        /// <exception cref="InvalidOperationException">The underlying object was not set to a list of bytes.</exception>
        /// <exception cref="DecoderFallbackException">Might theoretically be thrown during decoding.</exception>
        public string ListReadAsText()
        {
            var bytes = ListGetBytes();
            if (bytes.Length == 0) return string.Empty;
#if NETSTANDARD2_0
            return Encoding.UTF8.GetString(bytes.Slice(0, bytes.Length - 1).ToArray());
#else
            return Encoding.UTF8.GetString(bytes.Slice(0, bytes.Length - 1));
#endif
        }

        /// <summary>
        /// Sets an element of a list of (signed) bytes.
        /// </summary>
        /// <param name="index">Element index</param>
        /// <param name="value">Element value</param>
        /// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
        /// <exception cref="InvalidOperationException">The underlying object was not set to a list of bytes.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
        public void ListWriteValue(int index, sbyte value, sbyte defaultValue = 0)
        {
            ListWriteValue(index, unchecked((byte)value), unchecked((byte)defaultValue));
        }

        /// <summary>
        /// Sets an element of a list of 16 bit words.
        /// </summary>
        /// <param name="index">Element index</param>
        /// <param name="value">Element value</param>
        /// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
        /// <exception cref="InvalidOperationException">The underlying object was not set to a list of 16 bit words.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
        public void ListWriteValue(int index, ushort value, ushort defaultValue = 0)
        {
            if (Kind != ObjectKind.ListOfShorts)
                throw new InvalidOperationException("This is not a list of 16-bit values");

            if (index < 0 || index >= ListElementCount)
                throw new ArgumentOutOfRangeException(nameof(index));

            Allocate();

            ushort x = (ushort)(value ^ defaultValue);
            MemoryMarshal.Cast<ulong, ushort>(SegmentSpan.Slice(Offset))[index] = x;
        }

        /// <summary>
        /// Sets an element of a list of 16 bit words.
        /// </summary>
        /// <param name="index">Element index</param>
        /// <param name="value">Element value</param>
        /// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
        /// <exception cref="InvalidOperationException">The underlying object was not set to a list of 16 bit words.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
        public void ListWriteValue(int index, short value, short defaultValue = 0)
        {
            ListWriteValue(index, unchecked((ushort)value), unchecked((ushort)defaultValue));
        }

        /// <summary>
        /// Sets an element of a list of 32 bit words.
        /// </summary>
        /// <param name="index">Element index</param>
        /// <param name="value">Element value</param>
        /// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
        /// <exception cref="InvalidOperationException">The underlying object was not set to a list of 32 bit words.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
        public void ListWriteValue(int index, uint value, uint defaultValue = 0)
        {
            if (Kind != ObjectKind.ListOfInts)
                throw new InvalidOperationException("This is not a list of 32-bit values");

            if (index < 0 || index >= ListElementCount)
                throw new ArgumentOutOfRangeException(nameof(index));

            Allocate();

            uint x = value ^ defaultValue;
            MemoryMarshal.Cast<ulong, uint>(SegmentSpan.Slice(Offset))[index] = x;
        }

        /// <summary>
        /// Sets an element of a list of 32 bit words.
        /// </summary>
        /// <param name="index">Element index</param>
        /// <param name="value">Element value</param>
        /// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
        /// <exception cref="InvalidOperationException">The underlying object was not set to a list of 32 bit words.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
        public void ListWriteValue(int index, int value, int defaultValue = 0)
        {
            ListWriteValue(index, unchecked((uint)value), unchecked((uint)defaultValue));
        }

        /// <summary>
        /// Sets an element of a list of 32 bit words.
        /// </summary>
        /// <param name="index">Element index</param>
        /// <param name="value">Element value</param>
        /// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
        /// <exception cref="InvalidOperationException">The underlying object was not set to a list of 32 bit words.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
        public void ListWriteValue(int index, float value, float defaultValue = 0)
        {
            int rcastValue = value.ReplacementSingleToInt32Bits();
            int rcastDefaultValue = defaultValue.ReplacementSingleToInt32Bits();
            ListWriteValue(index, rcastValue, rcastDefaultValue);
        }

        /// <summary>
        /// Sets an element of a list of 64 bit words.
        /// </summary>
        /// <param name="index">Element index</param>
        /// <param name="value">Element value</param>
        /// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
        /// <exception cref="InvalidOperationException">The underlying object was not set to a list of 64 bit words.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
        public void ListWriteValue(int index, ulong value, ulong defaultValue = 0)
        {
            if (Kind != ObjectKind.ListOfLongs)
                throw new InvalidOperationException("This is not a list of 64-bit values");

            if (index < 0 || index >= ListElementCount)
                throw new ArgumentOutOfRangeException(nameof(index));

            Allocate();

            ulong x = value ^ defaultValue;
            SegmentSpan.Slice(Offset)[index] = x;
        }

        /// <summary>
        /// Sets an element of a list of 64 bit words.
        /// </summary>
        /// <param name="index">Element index</param>
        /// <param name="value">Element value</param>
        /// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
        /// <exception cref="InvalidOperationException">The underlying object was not set to a list of 64 bit words.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
        public void ListWriteValue(int index, long value, long defaultValue = 0)
        {
            ListWriteValue(index, unchecked((ulong)value), unchecked((ulong)defaultValue));
        }

        /// <summary>
        /// Sets an element of a list of 64 bit words.
        /// </summary>
        /// <param name="index">Element index</param>
        /// <param name="value">Element value</param>
        /// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
        /// <exception cref="InvalidOperationException">The underlying object was not set to a list of 64 bit words.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
        public void ListWriteValue(int index, double value, double defaultValue = 0)
        {
            long rcastValue = BitConverter.DoubleToInt64Bits(value);
            long rcastDefaultValue = BitConverter.DoubleToInt64Bits(defaultValue);
            ListWriteValue(index, rcastValue, rcastDefaultValue);
        }

        /// <summary>
        /// Adds an entry to the capability table if the provided capability does not yet exist.
        /// </summary>
        /// <param name="capability">The low-level capability object to provide.</param>
        /// <returns>Index of the given capability in the capability table, null if capability is null</returns>
        /// <exception cref="InvalidOperationException">The underlying message builder was not configured for capability table support.</exception>
        public uint? ProvideCapability(Rpc.ConsumedCapability capability)
        {
            if (capability == null || capability == Rpc.NullCapability.Instance)
                return null;

            if (Caps == null)
                throw new InvalidOperationException("Underlying MessageBuilder was not enabled to support capabilities");

            int index = Caps.IndexOf(capability);

            if (index < 0)
            {
                index = Caps.Count;
                Caps.Add(capability);
                capability.AddRef();
            }

            return (uint)index;
        }

        /// <summary>
        /// Adds an entry to the capability table if the provided capability does not yet exist.
        /// </summary>
        /// <param name="capability">The capability to provide, in terms of its skeleton.</param>
        /// <returns>Index of the given capability in the capability table</returns>
        /// <exception cref="InvalidOperationException">The underlying message builder was not configured for capability table support.</exception>
        public uint? ProvideCapability(Rpc.Skeleton capability)
        {
            return ProvideCapability(capability.AsCapability());
        }

        /// <summary>
        /// Adds an entry to the capability table if the provided capability does not yet exist.
        /// </summary>
        /// <param name="obj">The capability, in one of the following forms:<list type="bullet">
        /// <item><description>Low-level capability (<see cref="Rpc.ConsumedCapability"/>)</description></item>
        /// <item><description>Proxy object (<see cref="Rpc.Proxy"/>). Note that the provision has "move semantics": SerializerState
        /// takes ownership, so the Proxy object will be disposed.</description></item>
        /// <item><description><see cref="Rpc.Skeleton"/> instance</description></item>
        /// <item><description>Capability interface implementation</description></item>
        /// </list></param>
        /// <returns>Index of the given capability in the capability table</returns>
        /// <exception cref="InvalidOperationException">The underlying message builder was not configured for capability table support.</exception>
        public uint? ProvideCapability(object? obj)
        {
            switch (obj)
            {
                case null:
                    return null;
                case Rpc.Proxy proxy: using (proxy)
                    return ProvideCapability(proxy.ConsumedCap);
                case Rpc.ConsumedCapability consumedCapability:
                    return ProvideCapability(consumedCapability);
                case Rpc.Skeleton providedCapability:
                    return ProvideCapability(providedCapability);
                default:
                    return ProvideCapability(Rpc.CapabilityReflection.CreateSkeletonInternal(obj));
            }
        }

        /// <summary>
        /// Links a sub-item (struct field or list element) of this state to another object. 
        /// In contrast to <see cref="Link(int, SerializerState, bool)"/>, this method also accepts deserializer states, domain objects, capabilites, and lists thereof.
        /// If necessary, it will perform a deep copy.
        /// </summary>
        /// <param name="slot">If this state describes a struct: Index into this struct's pointer table. 
        /// If this state describes a list of pointers: List element index.</param>
        /// <param name="obj">Object to be linked. Must be one of the following:<list type="bullet">
        /// <item><description>Another <see cref="SerializerState"/></description></item>
        /// <item><description>A <see cref="DeserializerState"/> (will always deep copy)</description></item>
        /// <item><description>An object implementing <see cref="ICapnpSerializable"/></description></item>
        /// <item><description>A low-level capability object (<see cref="Rpc.ConsumedCapability"/>)</description></item>
        /// <item><description>A proxy object (<see cref="Rpc.Proxy"/>)</description></item>
        /// <item><description>A skeleton object (<see cref="Rpc.Skeleton"/>)</description></item>
        /// <item><description>A capability interface implementation</description></item>
        /// <item><description>A <see cref="IReadOnlyList{T}"/> of one of the things listed here.</description></item>
        /// </list></param>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="slot"/> is out of range.</exception>
        /// <exception cref="InvalidOperationException"><list type="bullet">
        /// <item><description>Object neither describes a struct, nor a list of pointers, nor a capability</description></item>
        /// <item><description>Another state is already linked to the specified position (sorry, no overwrite allowed)</description></item></list>
        /// </exception>
        public void LinkObject<T>(int slot, T obj)
        {
            switch (obj)
            {
                case SerializerState s:
                    Link(slot, s);
                    break;

                case DeserializerState d:
                    Reserializing.DeepCopy(d, BuildPointer(slot));
                    break;

                case ICapnpSerializable serializable:
                    serializable.Serialize(BuildPointer(slot));
                    break;

                case IReadOnlyList<object> list:
                    {
                        var builder = BuildPointer(slot);
                        builder.SetListOfPointers(list.Count);
                        int i = 0;
                        foreach (var item in list)
                        {
                            builder.LinkObject(i++, item);
                        }
                    }
                    break;

                default:
                    try
                    {
                        LinkToCapability(slot, ProvideCapability(obj));
                    }
                    catch (Exception exception) when (
                        exception is Rpc.InvalidCapabilityInterfaceException ||
                        exception is InvalidOperationException)
                    {
                        throw new InvalidOperationException("Object neither describes a struct, nor a list of pointers, nor a capability", exception);
                    }
                    break;
            }
        }

        internal Rpc.ConsumedCapability StructReadRawCap(int index)
        {
            if (Kind != ObjectKind.Struct && Kind != ObjectKind.Nil)
                throw new InvalidOperationException("Allowed on structs only");

            if (index < 0 || index >= StructPtrCount)
                throw new ArgumentOutOfRangeException(nameof(index));

            return DecodeCapPointer(index + StructDataCount);
        }

        /// <summary>
        /// Reads a struct field as capability and returns a proxy to that capability. 
        /// </summary>
        /// <typeparam name="T">Desired capability interface</typeparam>
        /// <param name="slot">Index into this struct's pointer table.</param>
        /// <returns>The proxy instance</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="slot"/> is out of range.</exception>
        /// <exception cref="ArgumentException">The desired interface does not qualify as capability interface (<see cref="Rpc.ProxyAttribute"/>)</exception>
        /// <exception cref="InvalidOperationException">This state does not represent a struct.</exception>
        public T ReadCap<T>(int slot) where T : class
        {
            var cap = StructReadRawCap(slot);
            return (Rpc.CapabilityReflection.CreateProxy<T>(cap) as T)!;
        }

        /// <summary>
        /// Reads a struct field as capability and returns a bare (generic) proxy to that capability. 
        /// </summary>
        /// <param name="slot">Index into this struct's pointer table.</param>
        /// <returns>The proxy instance</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="slot"/> is out of range.</exception>
        /// <exception cref="InvalidOperationException">This state does not represent a struct.</exception>
        public Rpc.BareProxy ReadCap(int slot)
        {
            var cap = StructReadRawCap(slot);
            return new Rpc.BareProxy(cap);
        }

        /// <summary>
        /// Releases the capability table
        /// </summary>
        public void Dispose()
        {
            if (Caps != null && !_disposed)
            {
                foreach (var cap in Caps)
                {
                    cap.Release();
                }

                Caps.Clear();
                _disposed = true;
            }
        }
    }
}