/// <summary>
 /// OnRemove is the counterpart to OnAdd, both should be implemented if one is.
 /// Like OnAdd the OnRemove is provided the <see cref="IndexState"/> used for all registration operations and should be used to unregister any additional
 /// details previously added.
 /// </summary>
 /// <param name="tx">The <see cref="ITransaction"/> this Remove operation is part of.</param>
 /// <param name="state"><see cref="IndexState"/></param>
 /// <param name="cancellationToken"><see cref="CancellationToken"/></param>
 /// <returns><see cref="Task"/></returns>
 /// <remarks>
 /// Expected behavior for this method is the provided transaction <paramref name="tx"/> should not be explicitly committed under normal behavior.
 /// If an error occurs an exception is the preferred method to trigger failure, but the transaction can be aborted with <see cref="ITransaction.Abort"/>
 /// explicitly.
 /// Both methods will result in complete rollback of the transaction.
 /// </remarks>
 protected virtual Task OnRemoveActorAsync(ITransaction tx, IndexState state, CancellationToken cancellationToken)
 {
     return(Task.CompletedTask);
 }
        /// <summary>
        /// Catalogs the provided actor and its identity into this registry.
        /// This method expects an external transaction to be provided for managing atomicity
        /// </summary>
        /// <param name="tx">The <see cref="ITransaction"/> this Add operation is part of.</param>
        /// <param name="reference"><see cref="ActorReference"/> to store into internal state</param>
        /// <param name="identity">The underlying identity of the provided actor. This is not the actor id</param>
        /// <param name="cancellationToken"><see cref="CancellationToken"/></param>
        /// <returns><see cref="Task"/></returns>
        protected async Task AddActorAsync(ITransaction tx, ActorReference reference, Identity identity, CancellationToken cancellationToken)
        {
            var referenceMap = await GetReferenceMapAsync();

            var indexMap = await GetIndexMapAsync();

            var indexableRef = new IndexableActorReference(reference);
            var indexLookup  = await referenceMap.TryGetValueAsync(tx, indexableRef, ReliableCollectionDefaults.ReadTimeout, cancellationToken);

            long       index;
            IndexState state;

            if (indexLookup.HasValue)
            {
                index = indexLookup.Value;
                var stateLookup = await indexMap.TryGetValueAsync(tx, index, ReliableCollectionDefaults.ReadTimeout, cancellationToken);

                if (!stateLookup.HasValue)
                {
                    throw new InvalidOperationException($"{indexableRef.StringRepresentation} is missing its internal IndexState");
                }
                state = stateLookup.Value;
                IndexState newState;
                if (Equals(state.Identity, identity))
                {
                    newState = state;
                }
                else
                {
                    newState = new IndexState
                    {
                        Reference = reference,
                        Identity  = identity,
                        Index     = index
                    };
                    // replace old state
                    await indexMap.SetAsync(tx, index, newState, ReliableCollectionDefaults.WriteTimeout, cancellationToken);
                }

                await OnUpdateActorAsync(tx, state, newState, cancellationToken);
            }
            else
            {
                var identityMap = await GetIdentityMapAsync();

                var existingIndex = await identityMap.TryGetValueAsync(tx, identity, ReliableCollectionDefaults.ReadTimeout, cancellationToken);

                if (existingIndex.HasValue)
                {
                    var existingState = await indexMap.TryGetValueAsync(tx, existingIndex.Value);

                    throw new InvalidOperationException($"{identity} is already registered to Actor {new IndexableActorReference(existingState.Value.Reference).StringRepresentation}");
                }

                var propertiesMap = await GetPropertiesMapAsync();

                index = (long)await propertiesMap.AddOrUpdateAsync(tx, "currentIndex", 1L, (key, value) => (long)value + 1, ReliableCollectionDefaults.WriteTimeout, cancellationToken);

                state = new IndexState
                {
                    Reference = reference,
                    Identity  = identity,
                    Index     = index
                };

                await referenceMap.AddAsync(tx, indexableRef, index, ReliableCollectionDefaults.WriteTimeout, cancellationToken);

                await indexMap.AddAsync(tx, index, state, ReliableCollectionDefaults.WriteTimeout, cancellationToken);

                await identityMap.AddAsync(tx, identity, index, ReliableCollectionDefaults.WriteTimeout, cancellationToken);

                await OnAddActorAsync(tx, state, cancellationToken);
            }
        }
 /// <summary>
 /// Implementations that need to allow alterable state should override this method. Additions that already have an internal state will trigger an update
 /// instead of an add which will pass the old and new state to this method for further re-cataloging activity.
 /// </summary>
 /// <param name="tx">The <see cref="ITransaction"/> this Add operation is part of.</param>
 /// <param name="oldState">The old <see cref="IndexState"/></param>
 /// <param name="newState">The new <see cref="IndexState"/></param>
 /// <param name="cancellationToken">The cancellation token.</param>
 /// <returns>Task.</returns>
 protected virtual Task OnUpdateActorAsync(ITransaction tx, IndexState oldState, IndexState newState, CancellationToken cancellationToken)
 {
     return(Task.CompletedTask);
 }