package com.atlassian.bitbucket.mesh.coordination;

import com.atlassian.bitbucket.mesh.network.MeshNode;
import com.atlassian.bitbucket.mesh.repository.Repository;

import javax.annotation.Nonnull;
import java.util.Collection;

/**
 * A registry of repository write tokens used to manage exclusive write access a repository. Conceptually, each Mesh
 * node 'owns' one write token for each repository it hosts. Exclusive access is granted to any process or thread that
 * obtains write tokens from all participating nodes, following
 * <a href="https://en.wikipedia.org/wiki/Maekawa%27s_algorithm">Maekawa's algorithm</a>. The registry only tracks
 * requests for this node's write tokens; tracking the acquisition of locks, comprised of tokens from multiple nodes,
 * is done by {@link WriteLock}.
 *
 * @see WriteLock
 * @see WriteToken
 */
public interface WriteTokenRegistry {

    /**
     * Request by the provided {@code node} to acquire this node's write token for {@code repository}.
     *
     * @param repository       the repository the write token is requested for
     * @param logicalTimestamp the logical timestamp on the requesting node for the request
     * @param transactionId    the ID of the transaction that requests the token
     * @param handler          the handler to call when the the token is granted and the promise is fulfilled
     * @return a promise for the token, which when closed retracts the request and releases the ticket if held
     */
    @Nonnull
    WriteToken.Promise acquireToken(@Nonnull Repository repository, @Nonnull String transactionId,
                                    long logicalTimestamp, @Nonnull WriteTokenHandler handler);

    /**
     * Creates a {@link WriteLock} for the provided {@code repository} and <em>registers</em> for the local write
     * token for this repository. The local write token may or may not be immediately available, but this method does
     * not block waiting for it. Since the local node is the only participant, the lock is acquired when the local
     * write token is granted.
     *
     * @param repository    the repository to start a lock for
     * @param transactionId the transaction ID to start the lock for
     * @return the started write lock
     */
    @Nonnull
    WriteLock startLocalLock(@Nonnull Repository repository, @Nonnull String transactionId);

    /**
     * Creates a {@link WriteLock} for the provided {@code repository} and <em>registers</em> for the local write
     * token for this repository. The local write token may or may not be immediately available, but this method does
     * not block waiting for it.
     * <p>
     * The caller is expected to request tokens from other participating nodes over RPC and update the
     * {@link WriteLock} when they are {@link WriteLock#onTokenGranted(MeshNode) granted} or need to be
     * {@link WriteLock#yieldToken(MeshNode) yielded back}. When requesting the token over RPC, it is
     * <strong>critical</strong> that the same {@link WriteLock#getLogicalTimestamp() timestamp} is used.
     * Failing to do so breaks Maekawa's algorithm and can lead to deadlocks.
     *
     * @param repository    the repository to start a lock for
     * @param transactionId the transaction ID to start the lock for
     * @param nodes         the other nodes participating in the write
     * @return the started write lock
     */
    @Nonnull
    WriteLock startLock(@Nonnull Repository repository, @Nonnull String transactionId,
                        @Nonnull Collection<MeshNode> nodes);
}
