/* EINA - EFL data type library
 * Copyright (C) 2011 Vincent Torri
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library;
 * if not, see <http://www.gnu.org/licenses/>.
 */

#ifndef EINA_LOCK_H_
#define EINA_LOCK_H_

#include "eina_config.h"
#include "eina_types.h"
#include "eina_error.h"
#include "eina_main.h"

/**
 * @defgroup Eina_Lock_Group Lock
 * @ingroup Eina_Tools_Group
 * @brief This group provides thread locking and synchronization capabilities.
 *
 * Similar to POSIX threads (pthreads), but it takes care of the platform specific
 * details so you don't have to.
 *
 * If you know how @c pthreads work, this library will look familiar to you.
 * If you are not familiar with @c pthreads, a good overview is available
 * <a href="https://computing.llnl.gov/tutorials/pthreads/">here</a>
 *
 * The Eina lock functions are grouped into several categories to handle different
 * thread locking and synchronization methods:
 * @li eina_lock_* - Functions that implement locking.
 * @li eina_condition_* - Functions that implement condition variables.
 * @li eina_rwlock_* - Functions that implement read/write locks.
 * @li eina_tls_* - Functions that implement thread level storage.
 * @li eina_semaphore_* - Functions that implement semaphores.
 * @li eina_barrier_* - Functions that implement barriers.
 * @li eina_spinlock_* - Functions that implement spinlocks if they are available
 * on the platform.  If they are not available, these functions degrade to plain locks.
 *
 *
 *
 * @{
 */

/**
* @typedef Eina_Barrier
* @brief An opaque type for working with barrier locks.
*/
/**
* @typedef Eina_Lock
* @brief An opaque type for working with locks.
*/
/**
* @typedef Eina_Condition
* @brief An opaque type that represents a condition variable.
*/
/**
* @typedef Eina_RWLock
* @brief An opaque type for working with read/write locks.
*/
/**
* @typedef Eina_TLS
* @brief An opaque type for working with thread level storage.
*/
/**
* @typedef Eina_Semaphore
* @brief An opaque type for working with semaphores.
*/
/**
* @typedef Eina_Spinlock
* @brief An opaque type for working with spinlocks.
*/

/**
 * @typedef Eina_Lock_Result
 * @brief Return codes for lock operations.
 */
typedef enum
{
   EINA_LOCK_FAIL     = EINA_FALSE, /**< Indicates that the lock operation failed. */
   EINA_LOCK_SUCCEED  = EINA_TRUE,  /**< Indicates that the lock operation succeeded. */
   EINA_LOCK_DEADLOCK               /**< Indicates that the lock is deadlocked. */
} Eina_Lock_Result;

/**
 * @brief A callback type for deallocation of thread level storage data.
 */
typedef void (*Eina_TLS_Delete_Cb)(void *ptr);

#include "eina_inline_lock_posix.x"

/**
 * @brief A type definition for warning that a function was called from
 *        somewhere other than the EFL main loop.
 */
EINA_API extern Eina_Error EINA_ERROR_NOT_MAIN_LOOP;

/**
 * @brief Initializes a new #Eina_Lock.
 *
 * @param[in] mutex The #Eina_Lock structure to be initialized
 * @return #EINA_TRUE on success, #EINA_FALSE otherwise
 *
 * @details This function initializes an #Eina_Lock with appropriate values.
 *          These values are platform dependent as is the structure of the
 *          #Eina_Lock itself.
 *
 * @see eina_lock_free()
 */
static inline Eina_Bool eina_lock_new(Eina_Lock *mutex);

/**
 * @brief Initializes a new #Eina_Lock that is recursive.
 *
 * @param[in] mutex The #Eina_Lock structure to be initialized
 * @return #EINA_TRUE on success, #EINA_FALSE otherwise
 *
 * @details This function initializes an #Eina_Lock with appropriate values.
 *          These values are platform dependent as is the structure of the
 *          #Eina_Lock itself.
 *
 * @see eina_lock_new()
 * @see eina_lock_free()
 * @since 1.19
 */
static inline Eina_Bool eina_lock_recursive_new(Eina_Lock *mutex);

/**
 * @brief Deallocates an #Eina_Lock.
 * @details This function deallocates an #Eina_Lock allocated by eina_lock_new()
 *          and does any platform dependent cleanup that is required.
 *
 * @param[in] mutex The #Eina_Lock structure to be deallocated
 *
 * @see eina_lock_new()
 */
static inline void eina_lock_free(Eina_Lock *mutex);

/**
 * @brief Attempts to take a lock.
 * @details This function attempts to gain a lock on the indicated #Eina_Lock. If the
 *          underlying #Eina_Lock is locked already, this call can be blocked until
 *          the lock is released. This is appropriate in many cases, but consider using
 *          eina_lock_take_try() if you don't need to block.
 *
 * @param[in] mutex The #Eina_Lock to take
 *
 * @return Returns #EINA_LOCK_SUCCEED on success. If the operation fails because
 *         a deadlock condition exists, it returns #EINA_LOCK_DEADLOCK. If some other
 *         condition causes the take to fail, #EINA_LOCK_FAIL is returned.
 *
 * @see eina_lock_take_try()
 * @see eina_lock_release()
 */
static inline Eina_Lock_Result eina_lock_take(Eina_Lock *mutex);

/**
 * @brief Attempts to take a lock if possible.
 * @details This function attempts to gain a lock on the indicated #Eina_Lock.
            Identical eina_lock_take(), but returns immediately if the lock is already taken.
 *
 * @param[in] mutex The #Eina_Lock to take
 *
 * @return Returns #EINA_LOCK_SUCCEED on success. If the operation fails because
 *         a deadlock condition exists, it returns #EINA_LOCK_DEADLOCK. If some other
 *         condition causes the take to fail, #EINA_LOCK_FAIL is returned.
 *
 * @see eina_lock_take()
 * @see eina_lock_release()
 */
static inline Eina_Lock_Result eina_lock_take_try(Eina_Lock *mutex);

/**
 * @brief Releases a lock.
 * @details This function releases the lock on the indicated #Eina_Lock. If successful,
 *          and @c EINA_HAVE_DEBUG_THREADS is defined, @p mutex is updated and information
 *          about the locking process is removed (e.g. thread number and backtrace for POSIX).
 *
 * @param[in] mutex The #Eina_Lock to release
 *
 * @return Returns #EINA_LOCK_SUCCEED on success,  If it fails, #EINA_LOCK_FAIL is
 *         returned.
 *
 * @see eina_lock_take()
 * @see eina_lock_take_try()
 */
static inline Eina_Lock_Result eina_lock_release(Eina_Lock *mutex);

/**
 * @brief Prints debug information about a lock.
 * @details This function prints debug information for @p mutex.  The information is
 *          platform dependent.  On POSIX systems it prints the address of @p mutex,
 *          lock state, thread number and a backtrace.
 *
 * @param[in] mutex The #Eina_Lock to print debug info for.
 *
 * @note If @c EINA_HAVE_DEBUG_THREADS is not defined, this function does nothing.
 *
 * @since 1.19
 */
EINA_API void eina_lock_debug(const Eina_Lock *mutex);

/**
 * @brief Initializes a new condition variable.
 * @details This function initializes an #Eina_Condition structure and associates it with
 *          an existing lock.
 *
 * @param[in] cond The condition variable to create
 * @param[in] mutex The #Eina_Lock structure that controls access to this condition variable
 *
 * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
 *
 * Condition variables are used to coordinate actions between threads.  See
 * <a href="https://computing.llnl.gov/tutorials/pthreads/#ConVarOverview"> Condition Variable Overview </a>
 * for an introduction to condition variables and their use.
 *
 * @see eina_condition_free()
 */
static inline Eina_Bool eina_condition_new(Eina_Condition *cond, Eina_Lock *mutex);

/**
 * @brief Deallocates a condition variable.
 * @details This function deallocates a condition variable and does any platform dependent
 *          cleanup that is required.
 *
 * @param[in] cond The condition variable to be deallocated.
 *
 * @see eina_condition_new()
 */
static inline void eina_condition_free(Eina_Condition *cond);

/**
 * @brief Causes a thread to wait until signaled by the condition.
 * @details This function makes a thread block until a signal is sent to it via @p cond.
 *
 * @param[in] cond The #Eina_Condition upon which the thread waits.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
 *
 * @see eina_condition_timedwait()
 */
static inline Eina_Bool eina_condition_wait(Eina_Condition *cond);

/**
 * @brief Causes a thread to wait until signaled by the condition or a
 *        timeout is reached.
 * @details This function makes a thread block until either a signal is sent to it via
 *          @p cond or @p t seconds have passed.
 *
 * @param[in] cond The #Eina_Condition upon which the thread waits.
 * @param[in] t The maximum amount of time to wait, in seconds.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE otherwise. If the operation
 *         timed out, eina error will be set to @c ETIMEDOUT.
 *
 * @see eina_condition_wait()
 */
static inline Eina_Bool eina_condition_timedwait(Eina_Condition *cond, double t);

/**
 * @brief Signals all threads waiting for a condition.
 * @details This function sends a signal to all the threads waiting on the condition @p cond.
 *          If you know for sure that there is only one thread waiting, use eina_condition_signal()
 *          instead to gain a little optimization.
 *
 * @param[in] cond The #Eina_Condition that signals all its waiting threads.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
 *
 * @see eina_condition_signal()
 */
static inline Eina_Bool eina_condition_broadcast(Eina_Condition *cond);

/**
 * @brief Signals a thread waiting for a condition.
 * @details This function sends a signal to a thread waiting on the condition @p cond.
 *          If you do not know for sure that there is only one thread waiting, use
 *          eina_condition_broadcast() instead.
 *
 * @param[in] cond The #Eina_Condition that signals its waiting thread.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
 *
 * @note If there is more than one thread waiting on this condition, one of them is
 *       signaled, but which one is undefined.
 *
 * @see eina_condition_broadcast()
 */
static inline Eina_Bool eina_condition_signal(Eina_Condition *cond);


/**
 * @brief Initializes a new #Eina_RWLock.
 * @details This function initializes an #Eina_RWLock with appropriate values.
 *          These values are platform dependent as is the structure of the #Eina_RWLock
 *          itself.
 *
 * @param[in] mutex The #Eina_RWLock to be initialized.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
 *
 * @see eina_rwlock_free()
 */
static inline Eina_Bool eina_rwlock_new(Eina_RWLock *mutex);

/**
 * @brief Deallocates an #Eina_RWLock.
 * @details This function deallocates an #Eina_RWLock and does any platform dependent
 *          cleanup that is required.
 *
 * @param[in] mutex The #Eina_RWLock structure to be deallocated.
 *
 * @see eina_rwlock_new()
 */
static inline void eina_rwlock_free(Eina_RWLock *mutex);

/**
 * @brief Attempts to take a read lock.
 * @details This function attempts to gain a read lock on the indicated #Eina_RWLock. If
 *          the #Eina_RWLock is write locked, this call can be blocked until
 *          the lock is released.
 *
 * @param[in] mutex The #Eina_RWLock to take.
 *
 * @return Returns #EINA_LOCK_SUCCEED on success, #EINA_LOCK_FAIL on failure.
 *
 * @note This function never returns #EINA_LOCK_DEADLOCK.
 *
 * @see eina_rwlock_release()
 */
static inline Eina_Lock_Result eina_rwlock_take_read(Eina_RWLock *mutex);

/**
 * @brief Attempts to take a write lock.
 * @details This function attempts to gain a write lock on the indicated #Eina_RWLock. If
 *          the #Eina_RWLock is locked for reading or writing, this call can be
 *          blocked until the lock is released.
 *
 * @param[in] mutex The #Eina_RWLock to take.
 *
 * @return Returns #EINA_LOCK_SUCCEED on success, #EINA_LOCK_FAIL on failure.
 *
 * @note This function never returns #EINA_LOCK_DEADLOCK.
 *
 * @see eina_rwlock_release()
 */
static inline Eina_Lock_Result eina_rwlock_take_write(Eina_RWLock *mutex);

/**
 * @brief Releases a lock.
 * @details This function releases the lock on the indicated #Eina_RWLock.
 *
 * @param[in] mutex The #Eina_RWLock to release.
 *
 * @return Returns #EINA_LOCK_SUCCEED on success.  If it fails, #EINA_LOCK_FAIL is
 *         returned.
 *
 * @see eina_rwlock_take_read()
 * @see eina_rwlock_take_write()
 */
static inline Eina_Lock_Result eina_rwlock_release(Eina_RWLock *mutex);

/**
 * @brief Initializes a new #Eina_TLS, or thread level storage, to store thread
 *        specific data.
 * @details This function initializes an #Eina_TLS with @p key but does not set a
 *          callback to deallocate @p key when the thread exits. The implementation
 *          is platform dependent as is the structure of the #Eina_TLS itself.
 *
 * @param[in] key The #Eina_TLS to be initialized.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
 *
 * @note use eina_tls_cb_new() instead to set a callback for deallocating @p key.
 *
 * @see eina_tls_cb_new()
 * @see eina_tls_free()
 */
static inline Eina_Bool eina_tls_new(Eina_TLS *key);

/**
 * @brief Initializes a new #Eina_TLS, or thread level storage, to store thread
 *        specific data.
 * @details This function initializes an #Eina_TLS with @p key and sets a
 *          callback to deallocate @p key when the thread exits. The implementation
 *          is platform dependent as is the structure of the #Eina_TLS itself.
 *
 * @param[in] key The #Eina_TLS to be initialized.
 * @param[in] delete_cb A pointer to a function that deallocates @p key.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
 *
 * @see eina_tls_new()
 * @see eina_tls_free()
 */
static inline Eina_Bool eina_tls_cb_new(Eina_TLS *key, Eina_TLS_Delete_Cb delete_cb);

/**
 * @brief Frees an allocated #Eina_TLS.
 * @details This function frees the #Eina_TLS @p key. The implementation
 *          is platform dependent.
 *
 * @param[in] key The #Eina_TLS to be freed.
 *
 * @see eina_tls_new()
 * @see eina_tls_cb_new()
 */
static inline void eina_tls_free(Eina_TLS key);

/**
 * @brief Gets the value in #Eina_TLS for this thread.
 * @details This function gets a pointer to the data associated with #Eina_TLS @p key for
 *          this thread. The implementation is platform dependent.
 *
 * @param[in] key The #Eina_TLS to be retrieved.
 *
 * @return A pointer to the data associated with @p key.
 *
 * @see  eina_tls_set()
 */
static inline void *eina_tls_get(Eina_TLS key);

/**
 * @brief Sets the value in Eina_TLS for this thread.
 * @details This function sets the value associated with @p key to the pointer to the data
 *          @p data. The implementation is platform dependent.
 *
 * @param[in] key The #Eina_TLS to be set.
 * @param[in] data A pointer to the data to be stored.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
 *
 * @see eina_tls_get()
 */
static inline Eina_Bool eina_tls_set(Eina_TLS key, const void *data);

/**
 * @brief Initializes a new #Eina_Semaphore.
 * @details This function initializes an unnamed #Eina_Semaphore with appropriate values.
 *          These values are platform dependent.
 *
 * @param[in] sem The #Eina_Semaphore to be initialized.
 * @param[in] count_init Indicates the initial count of threads waiting on this semaphore.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
 *
 * @see eina_semaphore_free()
 */
static inline Eina_Bool eina_semaphore_new(Eina_Semaphore *sem, int count_init);

/**
 * @brief Frees an allocated #Eina_Semaphore.
 * @details This function frees the #Eina_Semaphore @p sem. The implementation
 *          is platform dependent.
 *
 * @param[in] sem The #Eina_Semaphore to be freed.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
 *
 * @see eina_semaphore_new()
 */
static inline Eina_Bool eina_semaphore_free(Eina_Semaphore *sem);

/**
 * @brief Gets a lock on an #Eina_Semaphore.
 * @details This function locks the #Eina_Semaphore @p sem. The implementation
 *          is platform dependent.
 *
 * @param[in] sem The #Eina_Semaphore to lock.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
 *
 * @see eina_semaphore_release()
 */
static inline Eina_Bool eina_semaphore_lock(Eina_Semaphore *sem);

/**
 * @brief Releases a lock on an #Eina_Semaphore.
 * @details This function releases a lock on the #Eina_Semaphore @p sem. The implementation
 *          is platform dependent.
 *
 * @param[in] sem The #Eina_Semaphore to release.
 * @param[in] count_release Not used.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
 *
 * @see eina_semaphore_lock()
 */
static inline Eina_Bool eina_semaphore_release(Eina_Semaphore *sem, int count_release);

/**
 * @brief Initializes a new #Eina_Barrier.
 * @details This function initializes a new #Eina_Barrier.  It sets the @c needed flag
 *          to the value of @p needed, sets the barrier's @c count member to 0 and
 *          creates new #Eina_Lock and #Eina_Condition objects for the barrier.
 *
 * @param[in] barrier The #Eina_Barrier to be initialized.
 * @param[in] needed The number of thread waits that causes this barrier to be reset.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
 *
 * @see eina_barrier_free()
 */
static inline Eina_Bool eina_barrier_new(Eina_Barrier *barrier, int needed);

/**
 * @brief Frees an allocated #Eina_Barrier.
 * @details This function frees the #Eina_Barrier @p barrier.
 *
 * @param[in] barrier The #Eina_Barrier to be freed.
 *
 * @see eina_barrier_new()
 */
static inline void eina_barrier_free(Eina_Barrier *barrier);

/**
 * @brief Increments the count of threads that are waiting on @p barrier.
 * @details When the count of threads reaches the @c needed value for the barrier, all
 *          waiting threads are notified via eina_condition_broadcast().
 * @param[in] barrier The #Eina_Barrier to be incremented.
 *
 * @return #EINA_TRUE on success, else #EINA_FALSE otherwise.
 *
 */
static inline Eina_Bool eina_barrier_wait(Eina_Barrier *barrier);


/**
 * @brief Initializes a new #Eina_Spinlock.
 * @details This function initializes a new #Eina_Spinlock, if spinlocks are available. If
 *          spinlocks are not available, it creates a new #Eina_Lock.
 *
 * @param[in] spinlock The #Eina_Spinlock to be initialized.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
 *
 * @note Spinlocks are only implemented on the POSIX platform and are only available
 *      if @c EINA_HAVE_POSIX_SPINLOCK is defined. You get a new #Eina_Lock on All other platforms.
 *
 * @see eina_spinlock_free()
 */
static inline Eina_Bool eina_spinlock_new(Eina_Spinlock *spinlock);

/**
 * @brief Attempts to take a spinlock.
 * @details This function attempts to gain a lock on the indicated #Eina_Spinlock. If the
 *          underlying #Eina_Spinlock is locked already, this call can be blocked until
 *          the lock is released. This is appropriate in many cases, but consider using
 *          eina_spinlock_take_try() if you don't need to block.
 *
 * @param[in] spinlock The #Eina_Spinlock to take.
 *
 * @return Returns #EINA_LOCK_SUCCEED on success.  If the operation fails because
 *         a deadlock condition exists, it returns #EINA_LOCK_DEADLOCK. If some other
 *         condition causes the take to fail, #EINA_LOCK_FAIL is returned.
 *
 * @see eina_spinlock_take_try()
 * @see eina_spinlock_release()
 */
static inline Eina_Lock_Result eina_spinlock_take(Eina_Spinlock *spinlock);

/**
 * @brief Attempts to take a spinlock if possible.
 * @details This function attempts to gain a lock on the indicated #Eina_Spinlock. Identical
 *          to eina_lock_take(), but returns immediately if the lock is already taken.
 *
 * @param[in] spinlock The #Eina_Spinlock to take.
 *
 * @return Returns #EINA_LOCK_SUCCEED on success. If the operation fails because
 *         a deadlock condition exists, it returns #EINA_LOCK_DEADLOCK. If some other
 *         condition causes the take to fail, #EINA_LOCK_FAIL is returned.
 *
 * @see eina_spinlock_take_try()
 * @see eina_spinlock_release()
 */
static inline Eina_Lock_Result eina_spinlock_take_try(Eina_Spinlock *spinlock);

/**
 * @brief Releases a spinlock.
 * @details This function will release the lock on the indicated #Eina_Spinlock. If successful,
 *          and @c EINA_HAVE_DEBUG_THREADS is defined, @p mutex is updated and information
 *          about the locking process is removed (e.g. thread number and backtrace for POSIX).
 *
 * @param[in] spinlock The #Eina_Spinlock to release.
 *
 * @return Returns #EINA_LOCK_SUCCEED on success, #EINA_LOCK_FAIL otherwise.
 *
 * @see eina_spinlock_take()
 * @see eina_spinlock_take_try()

 */
static inline Eina_Lock_Result eina_spinlock_release(Eina_Spinlock *spinlock);

/**
 * @brief Deallocates an #Eina_Spinlock.
 * @details This function deallocates an #Eina_Spinlock and does any platform dependent
 *          cleanup that is required.
 *
 * @param[in] spinlock The #Eina_Spinlock to be deallocated.
 *
 */
static inline void eina_spinlock_free(Eina_Spinlock *spinlock);

#ifdef EINA_HAVE_DEBUG_THREADS
# define EINA_MAIN_LOOP_CHECK_RETURN_VAL(val)				\
  do {									\
    if (EINA_UNLIKELY(!eina_main_loop_is()))				\
      {									\
	EINA_LOG_ERR("You are calling %s from outside"			\
		     "of the main loop threads in %s at line %i",	\
		     __func__,					\
		     __FILE__,						\
		     __LINE__);						\
	return val;							\
      }									\
  } while (0)
# define EINA_MAIN_LOOP_CHECK_RETURN					\
  do {									\
    if (EINA_UNLIKELY(!eina_main_loop_is()))				\
      {									\
	EINA_LOG_ERR("You are calling %s from outside"			\
		     "of the main loop threads in %s at line %i",	\
		     __func__,					\
		     __FILE__,						\
		     __LINE__);						\
	return ;							\
      }									\
  } while (0)
#else
/**
 * @def EINA_MAIN_LOOP_CHECK_RETURN_VAL
 * @brief Definition for the macro that doesn't do anything unless @c EINA_HAVE_DEBUG_THREADS is defined.
 * @param[in] val The value to be returned
 */
# define EINA_MAIN_LOOP_CHECK_RETURN_VAL(val) do {} while(0)
/**
 * @def EINA_MAIN_LOOP_CHECK_RETURN
 * @brief Definition for the macro that doesn't do anything unless @c EINA_HAVE_DEBUG_THREADS is defined.
 */
# define EINA_MAIN_LOOP_CHECK_RETURN do {} while(0)
#endif

/**
 * @}
 */

#endif
