package com.atlassian.bitbucket.mesh.throttle;

import com.atlassian.bitbucket.mesh.execution.ExecutionContext;

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

/**
 * Manages buckets of {@link Ticket}s which may be used to throttle access to named resources. The exact semantics by
 * which tickets are managed is not guaranteed by this interface, but it does have certain contracts:
 * <ul>
 *     <li>Attempting to acquire a ticket <i>shall not</i> block indefinitely, and acquire timeouts may be adjusted
 *     with settings in {@code mesh.properties} (or {@code bitbucket.properties} when running in sidecar mode)</li>
 *     <li>If acquiring a ticket for a named resource times out, a {@code ResourceBusyException} <i>shall</i> be thrown
 *     indicating the resource is busy</li>
 *     <li>If resources have been timing out, a banner <i>shall</i> be displayed in the user interface indicating the
 *     server is under heavy load</li>
 *     <li>Tickets can only be acquired if an {@link ExecutionContext} is active on the current thread, but they can
 *     be released from any thread</li>
 *     <li>When an {@link ExecutionContext} ends, all tickets which were held during that request <i>shall</i> be
 *     released, whether they were explicitly released before the {@link ExecutionContext} ended or not</li>
 * </ul>
 * In general, calling code should follow an approach similar to:
 * <pre><code>
 * try (Ticket ticket = throttleService.acquireTicket("resourceName") {
 *     //Do protected, resource-intensive operation
 * }
 * </code></pre>
 * This ensures the ticket is released immediately after the resource-intensive operation being protected is completed,
 * which, in turn, ensures the resource remains available and isn't starved out.
 */
public interface ThrottleService {

    /**
     * Acquires a ticket for accessing the specified resource.
     * <p>
     * Successful acquisition of a ticket indicates that the named resource should have sufficient capacity to service
     * a <i>single, full request</i>. This means a ticket may be acquired once and held while multiple operations are
     * performed on the gated resource. Callers should use {@code finally} blocks or {@code try}-with-resources to
     * ensure the ticket is released when they are finished using the gated resource.
     * <p>
     * The returned {@link Ticket} will never be {@code null}. If no tickets are available for the requested resource,
     * a {@code ResourceBusyException} will be thrown.
     *
     * @param resource the resource for which the ticket should be acquired
     * @return the acquired ticket
     * @throws IllegalStateException if no {@link ExecutionContext} is currently active
     */
    @Nonnull
    Ticket acquireTicket(@Nonnull ThrottledResource resource);

    /**
     * Retrieves the longest time in the queue for any request currently awaiting a ticket, if any.
     *
     * @param resource the resource
     * @return the longest queue time, or {@code 0L} if no requests are queued
     * @throws NoSuchResourceException if the specified resource has not been configured
     */
    long getLongestQueueingTimeForCurrentTicketRequests(@Nonnull ThrottledResource resource);

    /**
     * Retrieves the time in milliseconds since the last rejected request to {@link #acquireTicket acquire a ticket}.
     *
     * @param resource the resource
     * @return the last rejection time, in milliseconds, or {@code 0L} if no tickets have been rejected
     * @throws NoSuchResourceException if the specified resource has not been configured
     */
    long getTimeSinceLastRejectedTicketRequest(@Nonnull ThrottledResource resource);

    /**
     * @return {@link TicketSummary summaries} for each configured bucket of tickets
     */
    @Nonnull
    TicketSummary[] getSummaries();

    /**
     * @return {@link TicketSummary summary} corresponding to the supplied resource
     * @param resource the resource for which the ticket should be returned
     */
    @Nonnull
    TicketSummary getSummary(@Nonnull ThrottledResource resource);

    /**
     * @return the current ticket, if one is held
     */
    @Nonnull
    Optional<Ticket> getTicket();
}
