/**
 * Copyright (c) 2011 - 2021, Nordic Semiconductor ASA
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, must reproduce the above copyright notice, this list of
 *    conditions and the following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * 4. This software, with or without modification, must only be used with a
 *    Nordic Semiconductor ASA integrated circuit.
 *
 * 5. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 *
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
#ifndef NRF_ATFIFO_H__
#define NRF_ATFIFO_H__

#include <stdint.h>
#include <stdbool.h>
#include "sdk_config.h"
#include "nordic_common.h"
#include "nrf_assert.h"
#include "sdk_errors.h"
#include "nrf_log_instance.h"
#ifdef __cplusplus
extern "C" {
#endif

/**
 * @defgroup nrf_atfifo Atomic FIFO
 * @ingroup app_common
 *
 * @brief @tagAPI52 FIFO implementation that allows for making atomic transactions without
 * locking interrupts.
 *
 * @details There are two types of functions to prepare the FIFO writing:
 * - Single function for simple access:
 * @code
 * if (NRF_SUCCESS != nrf_atfifo_simple_put(my_fifo, &data, NULL))
 * {
 *      // Error handling
 * }
 * @endcode
 * - Function pair to limit data copying:
 * @code
 * struct point3d
 * {
 *      int x, y, z;
 * }point3d_t;
 * nrf_atfifo_context_t context;
 * point3d_t * point;
 *
 * if (NULL != (point = nrf_atfifo_item_alloc(my_fifo, &context)))
 * {
 *      point->x = a;
 *      point->y = b;
 *      point->z = c;
 *      if (nrf_atfifo_item_put(my_fifo, &context))
 *      {
 *          // Send information to the rest of the system
 *          // that there is new data in the FIFO available for reading.
 *      }
 * }
 * else
 * {
 *      // Error handling
 * }
 *
 * @endcode
 * @note
 * This atomic FIFO implementation requires that the operation that is
 * opened last is finished (committed/flushed) first.
 * This is typical for operations performed from the interrupt runtime
 * when the other operation is performed from the main thread.
 *
 * This implementation does not support typical multithreading operating system
 * access where operations can be started and finished in totally unrelated order.
 *
 * @{
 */

/**
 * @brief Read and write position structure.
 *
 * A structure that holds the read and write position used by the FIFO head and tail.
 */
typedef struct nrf_atfifo_postag_pos_s
{
    uint16_t wr; //!< First free space to write the data
    uint16_t rd; //!< A place after the last data to read
}nrf_atfifo_postag_pos_t;

/**
 * @brief End data index tag.
 *
 * A tag used to mark the end of data.
 * To properly realize atomic data committing, the whole variable has to be
 * accessed atomically.
 */
typedef union nrf_atfifo_postag_u
{
    uint32_t                tag; //!< Whole tag, used for atomic, 32-bit access
    nrf_atfifo_postag_pos_t pos; //!< Structure that holds reading and writing position separately
}nrf_atfifo_postag_t;

/**
 * @brief The FIFO instance.
 *
 * The instance of atomic FIFO.
 * Used with all FIFO functions.
 */
typedef struct nrf_atfifo_s
{
    void                * p_buf;        //!< Pointer to the data buffer
    nrf_atfifo_postag_t   tail;         //!< Read and write tail position tag
    nrf_atfifo_postag_t   head;         //!< Read and write head position tag
    uint16_t              buf_size;     //!< FIFO size in number of bytes (has to be divisible by @c item_size)
    uint16_t              item_size;    //!< Size of a single FIFO item
    NRF_LOG_INSTANCE_PTR_DECLARE(p_log) //!< Pointer to instance of the logger object (Conditionally compiled).
}nrf_atfifo_t;

/**
 * @brief FIFO write operation item context.
 *
 * Context structure used to mark an allocated space in FIFO that is ready for put.
 * All the data required to properly put allocated and written data.
 */
typedef struct nrf_atfifo_item_put_s
{
    nrf_atfifo_postag_t last_tail; //!< Tail tag value that was here when opening the FIFO to write
}nrf_atfifo_item_put_t;


/**
 * @brief FIFO read operation item context.
 *
 * Context structure used to mark an opened get operation to properly free an item after reading.
 */
typedef struct nrf_atfifo_rcontext_s
{
    nrf_atfifo_postag_t last_head; //!< Head tag value that was here when opening the FIFO to read
}nrf_atfifo_item_get_t;


/** @brief Name of the module used for logger messaging.
 */
#define NRF_ATFIFO_LOG_NAME atfifo

/**
 * @defgroup nrf_atfifo_instmacros FIFO instance macros
 *
 * A group of macros helpful for FIFO instance creation and initialization.
 * They may be used to create and initialize instances for most use cases.
 *
 * FIFO may also be created and initialized directly using
 * @ref nrf_atfifo_init function.
 * @{
 */
    /**
     * @brief Macro for generating the name for a data buffer.
     *
     * The name of the data buffer that would be created by
     * @ref NRF_ATFIFO_DEF macro.
     *
     * @param[in] fifo_id Identifier of the FIFO object.
     *
     * @return Name of the buffer variable.
     *
     * @note This is auxiliary internal macro and in normal usage
     *       it should not be called.
     */
    #define NRF_ATFIFO_BUF_NAME(fifo_id) CONCAT_2(fifo_id, _data)

    /**
     * @brief Macro for generating the name for a FIFO instance.
     *
     * The name of the instance variable that will be created by the
     * @ref NRF_ATFIFO_DEF macro.
     *
     * @param[in] fifo_id Identifier of the FIFO object.
     *
     * @return Name of the instance variable.
     *
     * @note This is auxiliary internal macro and in normal usage
     *       it should not be called.
     */
    #define NRF_ATFIFO_INST_NAME(fifo_id) CONCAT_2(fifo_id, _inst)

    /**
     * @brief Macro for creating an instance.
     *
     * Creates the FIFO object variable itself.
     *
     * Usage example:
     * @code
     * NRF_ATFIFO_DEF(my_fifo, uint16_t, 12);
     * NRF_ATFIFO_INIT(my_fifo);
     *
     * uint16_t some_val = 45;
     * nrf_atfifo_item_put(my_fifo, &some_val, sizeof(some_val), NULL);
     * nrf_atfifo_item_get(my_fifo, &some_val, sizeof(some_val), NULL);
     * @endcode
     *
     * @param[in] fifo_id      Identifier of a FIFO object.
     *                         This identifier will be a pointer to the instance.
     *                         It makes it possible to use this directly for the functions
     *                         that operate on the FIFO.
     *                         Because it is a static const object, it should be optimized by the compiler.
     * @param[in] storage_type Type of data that will be stored in the FIFO.
     * @param[in] item_cnt     Capacity of the created FIFO in maximum number of items that may be stored.
     *                         The phisical size of the buffer will be 1 element bigger.
     */
    #define NRF_ATFIFO_DEF(fifo_id, storage_type, item_cnt)                                     \
        static storage_type NRF_ATFIFO_BUF_NAME(fifo_id)[(item_cnt)+1];                         \
        NRF_LOG_INSTANCE_REGISTER(NRF_ATFIFO_LOG_NAME, fifo_id,                                 \
                                  NRF_ATFIFO_CONFIG_INFO_COLOR,                                 \
                                  NRF_ATFIFO_CONFIG_DEBUG_COLOR,                                \
                                  NRF_ATFIFO_CONFIG_LOG_INIT_FILTER_LEVEL,                      \
                                  NRF_ATFIFO_CONFIG_LOG_ENABLED ?                               \
                                          NRF_ATFIFO_CONFIG_LOG_LEVEL : NRF_LOG_SEVERITY_NONE); \
        static nrf_atfifo_t NRF_ATFIFO_INST_NAME(fifo_id) = {                                   \
                .p_buf = NULL,                                                                  \
                NRF_LOG_INSTANCE_PTR_INIT(p_log, NRF_ATFIFO_LOG_NAME, fifo_id)                  \
        };                                                                                      \
        static nrf_atfifo_t * const fifo_id = &NRF_ATFIFO_INST_NAME(fifo_id)

    /**
     * @brief Macro for initializing the FIFO that was previously declared by the macro.
     *
     * Use this macro to simplify FIFO initialization.
     *
     * @note
     * This macro can be only used on a FIFO object defined by @ref NRF_ATFIFO_DEF macro.
     *
     * @param[in] fifo_id Identifier of the FIFO object.
     *
     * @return Value from the @ref nrf_atfifo_init function.
     */
    #define NRF_ATFIFO_INIT(fifo_id)                \
        nrf_atfifo_init(                            \
            fifo_id,                                \
            NRF_ATFIFO_BUF_NAME(fifo_id),           \
            sizeof(NRF_ATFIFO_BUF_NAME(fifo_id)),   \
            sizeof(NRF_ATFIFO_BUF_NAME(fifo_id)[0]) \
        )

/** @} */

/**
 * @brief Function for initializing the FIFO.
 *
 * Preparing the FIFO instance to work.
 *
 * @param[out]    p_fifo    FIFO object to initialize.
 * @param[in,out] p_buf     FIFO buffer for storing data.
 * @param[in]     buf_size  Total buffer size (has to be divisible by @c item_size).
 * @param[in]     item_size Size of a single item held inside the FIFO.
 *
 * @retval     NRF_SUCCESS              If initialization was successful.
 * @retval     NRF_ERROR_NULL           If a NULL pointer is provided as the buffer.
 * @retval     NRF_ERROR_INVALID_LENGTH If size of the buffer provided is not divisible by @c item_size.
 *
 * @note
 * Buffer size must be able to hold one element more than the designed FIFO capacity.
 * This one, empty element is used for overflow checking.
 */
ret_code_t nrf_atfifo_init(nrf_atfifo_t * const p_fifo, void * p_buf, uint16_t buf_size, uint16_t item_size);

/**
 * @brief Function for clearing the FIFO.
 *
 * Function for clearing the FIFO.
 *
 * If this function is called during an opened and uncommitted write operation,
 * the FIFO is cleared up to the currently ongoing commit.
 * There is no possibility to cancel an ongoing commit.
 *
 * If this function is called during an opened and unflushed read operation,
 * the read position in the head is set, but copying it into the write head position
 * is left to read closing operation.
 *
 * This way, there is no more data to read, but the memory is released
 * in the moment when it is safe.
 *
 * @param[in,out] p_fifo FIFO object.
 *
 * @retval NRF_SUCCESS    FIFO totally cleared.
 * @retval NRF_ERROR_BUSY Function called in the middle of writing or reading operation.
 *                        If it is called in the middle of writing operation,
 *                        FIFO was cleared up to the already started and uncommitted write.
 *                        If it is called in the middle of reading operation,
 *                        write head was only moved. It will be copied into read tail when the reading operation
 *                        is flushed.
 */
ret_code_t nrf_atfifo_clear(nrf_atfifo_t * const p_fifo);

/**
 * @brief Function for atomically putting data into the FIFO.
 *
 * It uses memcpy function inside and in most situations, it is more suitable to
 * use @ref nrf_atfifo_item_alloc, write the data, and @ref nrf_atfifo_item_put to store a new value
 * in a FIFO.
 *
 * @param[in,out] p_fifo    FIFO object.
 * @param[in]     p_var     Variable to copy.
 * @param[in]     size      Size of the variable to copy.
 *                          Can be smaller or equal to the FIFO item size.
 * @param[out]    p_visible See value returned by @ref nrf_atfifo_item_put.
 *                          It may be NULL if the caller does not require the current operation status.
 *
 * @retval NRF_SUCCESS      If an element has been successfully added to the FIFO.
 * @retval NRF_ERROR_NO_MEM If the FIFO is full.
 *
 * @note
 * To avoid data copying, you can use the @ref nrf_atfifo_item_alloc and @ref nrf_atfifo_item_put
 * functions pair.
 */
ret_code_t nrf_atfifo_alloc_put(nrf_atfifo_t * const p_fifo, void const * const p_var, size_t size, bool * const p_visible);

/**
 * @brief Function for opening the FIFO for writing.
 *
 * Function called to start the FIFO write operation and access the given FIFO buffer directly.
 *
 * @param[in,out] p_fifo    FIFO object.
 * @param[out]    p_context Operation context, required by @ref nrf_atfifo_item_put.
 *
 * @return Pointer to the space where variable data can be stored.
 *         NULL if there is no space in the buffer.
 */
void * nrf_atfifo_item_alloc(nrf_atfifo_t * const p_fifo, nrf_atfifo_item_put_t * p_context);

/**
 * @brief Function for closing the writing operation.
 *
 * Puts a previously allocated context into FIFO.
 * This function must be called to commit an opened write operation.
 * It sets all the buffers and marks the data, so that it is visible to read.
 *
 * @param[in,out] p_fifo    FIFO object.
 * @param[in]     p_context Operation context, filled by the @ref nrf_atfifo_item_alloc function.
 *
 * @retval true  Data is currently ready and will be visible to read.
 * @retval false The internal commit was marked, but the writing operation interrupted another writing operation.
 *               The data will be available to read when the interrupted operation is committed.
 */
bool nrf_atfifo_item_put(nrf_atfifo_t * const p_fifo, nrf_atfifo_item_put_t * p_context);

/**
 * @brief Function for getting a single value from the FIFO.
 *
 * This function gets the value from the top of the FIFO.
 * The value is removed from the FIFO memory.
 *
 * @param[in,out] p_fifo     FIFO object.
 * @param[out]    p_var      Pointer to the variable to store the data.
 * @param[in]     size       Size of the data to be loaded.
 * @param[out]    p_released See the values returned by @ref nrf_atfifo_item_free.
 *
 * @retval NRF_SUCCESS         Element was successfully copied from the FIFO memory.
 * @retval NRF_ERROR_NOT_FOUND No data in the FIFO.
 */
ret_code_t nrf_atfifo_get_free(nrf_atfifo_t * const p_fifo, void * const p_var, size_t size, bool * p_released);

/**
 * @brief Function for opening the FIFO for reading.
 *
 * Function called to start the FIFO read operation and access the given FIFO buffer directly.
 *
 * @param[in,out] p_fifo    FIFO object.
 * @param[out]    p_context The operation context, required by @ref nrf_atfifo_item_free
 *
 * @return Pointer to data buffer or NULL if there is no data in the FIFO.
 */
void * nrf_atfifo_item_get(nrf_atfifo_t * const p_fifo, nrf_atfifo_item_get_t * p_context);

/**
 * @brief Function for closing the reading operation.
 *
 * Function used to finish the reading operation.
 * If this reading operation does not interrupt another reading operation, the head write buffer is moved.
 * If this reading operation is placed in the middle of another reading, only the new read pointer is written.
 *
 * @param[in,out] p_fifo    FIFO object.
 * @param[in]     p_context Context of the reading operation to be closed.
 *
 * @retval true  This operation is not generated in the middle of another read operation and the write head will be updated to the read head (space is released).
 * @retval false This operation was performed in the middle of another read operation and the write buffer head was not moved (no space is released).
 */
bool nrf_atfifo_item_free(nrf_atfifo_t * const p_fifo, nrf_atfifo_item_get_t * p_context);


/** @} */

#ifdef __cplusplus
}
#endif

#endif /* NRF_ATFIFO_H__ */
