/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.ignite;

import java.io.Closeable;
import java.util.concurrent.TimeUnit;

import org.apache.ignite.lang.IgniteCallable;
import org.apache.ignite.lang.IgniteFuture;

/**
 * This interface provides a rich API for working with distributed semaphore.
 * <p>
 * <h1 class="header">Functionality</h1>
 * Distributed semaphore provides functionality similar to {@code java.util.concurrent.Semaphore}.
 * <h1 class="header">Creating Distributed Semaphore</h1>
 * Instance of cache semaphore can be created by calling the following method:
 * {@link Ignite#semaphore(String, int, boolean, boolean)}.
 */
public interface IgniteSemaphore extends Closeable {
    /**
     * Gets name of the semaphore.
     *
     * @return Name of the semaphore.
     */
    public String name();

    /**
     * Acquires a permit from this semaphore, blocking until one is available, or the thread is {@linkplain
     * Thread#interrupt interrupted}.
     *
     * <p>Acquires a permit, if one is available and returns immediately, reducing the number of available permits by
     * one.
     *
     * <p>If no permit is available then the current thread becomes disabled for thread scheduling purposes and lies
     * dormant until one of two things happens: <ul> <li>Some other thread invokes the {@link #release} method for this
     * semaphore and the current thread is next to be assigned a permit; or <li>Some other thread {@linkplain
     * Thread#interrupt interrupts} the current thread. </ul>
     *
     * <p>If the current thread: <ul> <li>has its interrupted status set on entry to this method; or <li>is {@linkplain
     * Thread#interrupt interrupted} while waiting for a permit, </ul> then {@link IgniteInterruptedException} is thrown
     * and the current thread's interrupted status is cleared.
     *
     * @throws IgniteInterruptedException if the current thread is interrupted
     */
    public void acquire() throws IgniteInterruptedException;

    /**
     * Acquires a permit from this semaphore, blocking until one is available.
     *
     * <p>Acquires a permit, if one is available and returns immediately, reducing the number of available permits by
     * one.
     *
     * <p>If no permit is available then the current thread becomes disabled for thread scheduling purposes and lies
     * dormant until some other thread invokes the {@link #release} method for this semaphore and the current thread is
     * next to be assigned a permit.
     *
     * <p>If the current thread is {@linkplain Thread#interrupt interrupted} while waiting for a permit then it will
     * continue to wait, but the time at which the thread is assigned a permit may change compared to the time it would
     * have received the permit had no interruption occurred.  When the thread does return from this method its
     * interrupt status will be set.
     */
    public void acquireUninterruptibly();

    /**
     * Acquires a permit from this semaphore, only if one is available at the time of invocation.
     *
     * <p>Acquires a permit, if one is available and returns immediately, with the value {@code true}, reducing the
     * number of available permits by one.
     *
     * <p>If no permit is available then this method will return immediately with the value {@code false}.
     *
     * @return {@code true} if a permit was acquired and {@code false} otherwise
     */
    public boolean tryAcquire();

    /**
     * Acquires a permit from this semaphore, if one becomes available within the given waiting time and the current
     * thread has not been {@linkplain Thread#interrupt interrupted}.
     *
     * <p>Acquires a permit, if one is available and returns immediately, with the value {@code true}, reducing the
     * number of available permits by one.
     *
     * <p>If no permit is available then the current thread becomes disabled for thread scheduling purposes and lies
     * dormant until one of three things happens: <ul> <li>Some other thread invokes the {@link #release} method for
     * this semaphore and the current thread is next to be assigned a permit; or <li>Some other thread {@linkplain
     * Thread#interrupt interrupts} the current thread; or <li>The specified waiting time elapses. </ul>
     *
     * <p>If a permit is acquired then the value {@code true} is returned.
     *
     * <p>If the current thread: <ul> <li>has its interrupted status set on entry to this method; or <li>is {@linkplain
     * Thread#interrupt interrupted} while waiting to acquire a permit, </ul> then {@link IgniteInterruptedException} is
     * thrown and the current thread's interrupted status is cleared.
     *
     * <p>If the specified waiting time elapses then the value {@code false} is returned.  If the time is less than or
     * equal to zero, the method will not wait at all.
     *
     * @param timeout the maximum time to wait for a permit
     * @param unit the time unit of the {@code timeout} argument
     * @return {@code true} if a permit was acquired and {@code false} if the waiting time elapsed before a permit was
     * acquired
     * @throws IgniteInterruptedException if the current thread is interrupted
     */
    public boolean tryAcquire(long timeout, TimeUnit unit)
        throws IgniteInterruptedException;

    /**
     * Acquires the given number of permits from this semaphore, blocking until all are available.
     *
     * <p>Acquires the given number of permits, if they are available, and returns immediately, reducing the number of
     * available permits by the given amount.
     *
     * <p>If insufficient permits are available then the current thread becomes disabled for thread scheduling purposes
     * and lies dormant until some other thread invokes one of the {@link #release() release} methods for this
     * semaphore, the current thread is next to be assigned permits and the number of available permits satisfies this
     * request.
     *
     * <p>If the current thread is {@linkplain Thread#interrupt interrupted} while waiting for permits then it will
     * continue to wait and its position in the queue is not affected.  When the thread does return from this method its
     * interrupt status will be set.
     *
     * @param permits the number of permits to acquire
     * @throws IllegalArgumentException if {@code permits} is negative
     */
    public void acquireUninterruptibly(int permits);

    /**
     * Returns the current number of permits available in this semaphore.
     *
     * <p>This method is typically used for debugging and testing purposes.
     *
     * @return the number of permits available in this semaphore
     */
    public int availablePermits();

    /**
     * Acquires and returns all permits that are immediately available.
     *
     * @return the number of permits acquired
     */
    public int drainPermits();

    /**
     * Releases a permit, returning it to the semaphore.
     *
     * <p>Releases a permit, increasing the number of available permits by one.  If any threads are trying to acquire a
     * permit, then one is selected and given the permit that was just released.  That thread is (re)enabled for thread
     * scheduling purposes.
     *
     * <p>There is no requirement that a thread that releases a permit must have acquired that permit by calling {@link
     * #acquire}. Correct usage of a semaphore is established by programming convention in the application.
     */
    public void release();

    /**
     * Acquires the given number of permits from this semaphore, if all become available within the given waiting time
     * and the current thread has not been {@linkplain Thread#interrupt interrupted}.
     *
     * <p>Acquires the given number of permits, if they are available and returns immediately, with the value {@code
     * true}, reducing the number of available permits by the given amount.
     *
     * <p>If insufficient permits are available then the current thread becomes disabled for thread scheduling purposes
     * and lies dormant until one of three things happens: <ul> <li>Some other thread invokes one of the {@link
     * #release() release} methods for this semaphore, the current thread is next to be assigned permits and the number
     * of available permits satisfies this request; or <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread; or <li>The specified waiting time elapses. </ul>
     *
     * <p>If the permits are acquired then the value {@code true} is returned.
     *
     * <p>If the current thread: <ul> <li>has its interrupted status set on entry to this method; or <li>is {@linkplain
     * Thread#interrupt interrupted} while waiting to acquire the permits, </ul> then {@link IgniteInterruptedException}
     * is thrown and the current thread's interrupted status is cleared. Any permits that were to be assigned to this
     * thread, are instead assigned to other threads trying to acquire permits, as if the permits had been made
     * available by a call to {@link #release()}.
     *
     * <p>If the specified waiting time elapses then the value {@code false} is returned.  If the time is less than or
     * equal to zero, the method will not wait at all.  Any permits that were to be assigned to this thread, are instead
     * assigned to other threads trying to acquire permits, as if the permits had been made available by a call to
     * {@link #release()}.
     *
     * @param permits the number of permits to acquire
     * @param timeout the maximum time to wait for the permits
     * @param unit the time unit of the {@code timeout} argument
     * @return {@code true} if all permits were acquired and {@code false} if the waiting time elapsed before all
     * permits were acquired
     * @throws IgniteInterruptedException if the current thread is interrupted
     * @throws IllegalArgumentException if {@code permits} is negative
     */
    public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
        throws IgniteInterruptedException;

    /**
     * Acquires the given number of permits from this semaphore, only if all are available at the time of invocation.
     *
     * <p>Acquires the given number of permits, if they are available, and returns immediately, with the value {@code
     * true}, reducing the number of available permits by the given amount.
     *
     * <p>If insufficient permits are available then this method will return immediately with the value {@code false}
     * and the number of available permits is unchanged.
     *
     * <p>If you want to honor the failoverSafe setting, then use {@link #tryAcquire(int, long, TimeUnit)
     * tryAcquire(permits, 0, TimeUnit.SECONDS) } which is almost equivalent (it also detects interruption).
     *
     * @param permits the number of permits to acquire
     * @return {@code true} if the permits were acquired and {@code false} otherwise
     * @throws IllegalArgumentException if {@code permits} is negative
     */
    public boolean tryAcquire(int permits);

    /**
     * Acquires the given number of permits from this semaphore, blocking until all are available, or the thread is
     * {@linkplain Thread#interrupt interrupted}.
     *
     * <p>Acquires the given number of permits, if they are available, and returns immediately, reducing the number of
     * available permits by the given amount.
     *
     * <p>If insufficient permits are available then the current thread becomes disabled for thread scheduling purposes
     * and lies dormant until one of two things happens: <ul> <li>Some other thread invokes one of the {@link #release()
     * release} methods for this semaphore, the current thread is next to be assigned permits and the number of
     * available permits satisfies this request; or <li>Some other thread {@linkplain Thread#interrupt interrupts} the
     * current thread. </ul>
     *
     * <p>If the current thread: <ul> <li>has its interrupted status set on entry to this method; or <li>is {@linkplain
     * Thread#interrupt interrupted} while waiting for a permit, </ul> then {@link IgniteInterruptedException} is thrown
     * and the current thread's interrupted status is cleared. Any permits that were to be assigned to this thread are
     * instead assigned to other threads trying to acquire permits, as if permits had been made available by a call to
     * {@link #release()}.
     *
     * @param permits the number of permits to acquire
     * @throws IgniteInterruptedException if the current thread is interrupted
     * @throws IllegalArgumentException if {@code permits} is negative
     */
    public void acquire(int permits) throws IgniteInterruptedException;

    /**
     * Acquires the given semaphore, executes the given callable and schedules the release of permits asynchronously
     *
     * @param callable the callable to execute
     * @param numPermits the number of permits to acquire
     * @param <T> Type of the callable execution result.
     * @return Callable execution future.
     * @throws Exception if the callable throws an exception
     */
    public <T> IgniteFuture<T> acquireAndExecute(IgniteCallable<T> callable,
                                                 int numPermits) throws Exception;

    /**
     * Releases the given number of permits, returning them to the semaphore.
     *
     * <p>Releases the given number of permits, increasing the number of available permits by that amount. If any
     * threads are trying to acquire permits, then one is selected and given the permits that were just released. If the
     * number of available permits satisfies that thread's request then that thread is (re)enabled for thread scheduling
     * purposes; otherwise the thread will wait until sufficient permits are available. If there are still permits
     * available after this thread's request has been satisfied, then those permits are assigned in turn to other
     * threads trying to acquire permits.
     *
     * <p>There is no requirement that a thread that releases a permit must have acquired that permit by calling {@link
     * IgniteSemaphore#acquire acquire}. Correct usage of a semaphore is established by programming convention in the
     * application.
     *
     * @param permits the number of permits to release
     * @throws IllegalArgumentException if {@code permits} is negative
     */
    public void release(int permits);

    /**
     * Returns {@code true} if this semaphore is safe to use after node failure.
     * If not, IgniteInterruptedException is thrown on every other node after node failure.
     *
     * @return {@code true} if this semaphore has failoverSafe set true
     */
    public boolean isFailoverSafe();

    /**
     * Queries whether any threads are waiting to acquire. Note that because cancellations may occur at any time, a
     * {@code true} return does not guarantee that any other thread will ever acquire.  This method is designed
     * primarily for use in monitoring of the system state.
     *
     * @return {@code true} if there may be other threads waiting to acquire the lock
     */
    public boolean hasQueuedThreads();

    /**
     * Returns an estimate of the number of nodes waiting to acquire. The value is only an estimate because the number
     * of nodes that are waiting may change dynamically while this method traverses internal data structures.  This method is designed
     * for use in monitoring of the system state, not for synchronization control.
     *
     * @return the estimated number of nodes waiting for this lock
     */
    public int getQueueLength();

    /**
     * Gets {@code broken} status of the semaphore.
     *
     * @return {@code True} if a node failed on this semaphore and failoverSafe flag was set to false, {@code false} otherwise.
     */
    public boolean isBroken();

    /**
     * Gets {@code removed} status of the semaphore.
     *
     * @return {@code True} if semaphore was removed from cache, {@code false} otherwise.
     */
    public boolean removed();

    /**
     * Removes this semaphore.
     *
     * @throws IgniteException If operation failed.
     */
    @Override public void close();
}
