/**
 * Copyright (c) 2017 - 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 APP_USBD_CLASS_BASE_H__
#define APP_USBD_CLASS_BASE_H__

#include <stdint.h>
#include <stddef.h>

#include "app_usbd_types.h"
#include "nrf_drv_usbd.h"
#include "nrf_assert.h"
#include "app_util.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @defgroup app_usbd_class_base USBD Class Base module
 * @ingroup app_usbd
 *
 * @brief @tagAPI52840 The base for any class instance is defined in this module.
 *
 * @details Any class instance must start from base class instance structure.
 * This makes them compatible with USBD library independently of the
 * implementation details.
 * @{
 */

/**
 * @brief Endpoint configuration.
 */
typedef struct
{
    nrf_drv_usbd_ep_t address; //!< Endpoint address
} app_usbd_class_ep_conf_t;

/**
 * @brief Interface configuration.
 */
typedef struct
{
    uint8_t number;    //!< Interface number
    uint8_t ep_cnt;    //!< Endpoint number
    uint8_t ep_offset; //!< Offset of the first endpoint
                       /**< Offset in bytes of the first endpoint.
                        *   The offset is calculated from the address of this interface structure
                        */
} app_usbd_class_iface_conf_t;

/**
 * @brief Instance variable data.
 */
typedef struct
{
    app_usbd_class_inst_t const       * p_next;           //!< Pointer to the next instance
    app_usbd_class_inst_t const       * p_sof_next;       //!< Pointer to the next SOF event requiring instance
    app_usbd_sof_interrupt_handler_t    sof_handler;      //!< Instance specific SOF interrupt handler
} app_usbd_class_data_t;

/**
 * @brief Class descriptor context.
 */
typedef struct
{
    uint32_t   line;        //!< Number of line to resume writing descriptors from
    uint8_t    data_buffer; //!< Data from last call of feeder
} app_usbd_class_descriptor_ctx_t;

/**
 * @brief Class descriptor state.
 */
typedef struct
{
    uint8_t  *                          p_buffer;     //!< Pointer to buffer
    uint32_t                            current_size; //!< Current size of descriptor
    uint32_t                            maximum_size; //!< Maximum size of descriptor
    app_usbd_class_descriptor_ctx_t *   p_context;    //!< Pointer to context
} app_usbd_class_descriptor_state_t;

/**
 * @brief   Class interface function set.
 * */
typedef struct {
    /**
     * @brief Instance callback function.
     *
     * The function used by every class instance.
     * @param[in,out] p_inst  Instance of the class.
     * @param[in]     p_event Event to process.
     *
     * @return Standard error code.
     *
     * @note If given event is not supported by class, return @ref NRF_ERROR_NOT_SUPPORTED
     */
    ret_code_t (* event_handler)(app_usbd_class_inst_t const * const p_inst,
                                 app_usbd_complex_evt_t const * const p_event);

    /**
     * @brief Instance feed descriptors.
     *
     * Feeds whole descriptor of the instance.
     * @param[in]     p_ctx     Class descriptor context.
     * @param[in,out] p_inst    Instance of the class.
     * @param[out]    p_buff    Buffer for descriptor.
     * @param[in]     max_size  Requested size of the descriptor.
     *
     * @return True if not finished feeding the descriptor, false if done.
     */
    bool (* feed_descriptors)(app_usbd_class_descriptor_ctx_t  * p_ctx,
                              app_usbd_class_inst_t const      * p_inst,
                              uint8_t                          * p_buff,
                              size_t                             max_size);


    /**
     * @brief Select interface
     *
     * Function called when class interface has to be selected.
     *
     * This function would be called for every interface when:
     * - SET_INTERFACE command is processed by the default handler
     * - SET_CONFIG(1) command is processed by the default handler
     *
     * @note Remember to disable all the endpoints that are not used
     *       in the selected configuration.
     * @note If this function pointer is NULL default procedure would
     *       just enable all the interface endpoints and selecting
     *       alternate configurations other than 0 would generate error.
     * @note Calling the function with alternate setting 0 has to always succeed.
     *
     * @param[in,out] p_inst    Instance of the class
     * @param[in]     iface_idx Index of the interface inside class structure
     * @param[in]     alternate Alternate setting that should be selected
     *
     * @return Function has to return @ref NRF_SUCCESS when it has successfully proceed
     *         interface selection.
     *         If it returns @ref NRF_ERROR_NOT_SUPPORTED, default function would be used
     *         to proceed the request - just like there would be NULL pointer in this field.
     *         Any other kind of error would make library to STALL the request.
     */
    ret_code_t (* iface_select)(app_usbd_class_inst_t const * const p_inst,
                                uint8_t iface_idx,
                                uint8_t alternate);

    /**
     * @brief Deselect interface.
     *
     * Function called when the class interface has to be deselected.
     *
     * This function would be called for every interface when:
     * - Library start internal event is processed by the default handler
     * - RESET event is processed by the default handler
     * - SET_ADDRESS is processed by the default handler
     * - SET_CONFIG(0) is processed by the default handler
     *
     * @note Just after this function is called all the interface
     *       endpoints would be disabled.
     *       This function does not has to take care about it.
     * @note If this function pointer is NULL default procedure would
     *       just disable all the interface endpoints.
     *
     * @param[in,out] p_inst    Instance of the class.
     * @param[in]     iface_idx Index of the interface inside class structure.
     */
    void (* iface_deselect)(app_usbd_class_inst_t const * const p_inst, uint8_t iface_idx);

    /**
     * @brief Get current interface.
     *
     * Function called when class interface has to return its alternate settings
     * in reaction on GET_INTERFACE command.
     * It should be defined in a pair with @ref app_usbd_class_methods_t::iface_select.
     *
     * @param[in]  p_inst     Instance of the class.
     * @param[in]  iface_idx  Index of the interface inside class structure.
     *
     * @return Current alternate setting of the selected interface.
     *
     * @note For the classes that support this function, when an interface that has not alternate
     *       configurations has been selected this function has to return 0 - default alternate setting.
     *
     * @note If this function pointer it NULL default procedure would return alternate interface
     *       value 0.
     */
    uint8_t (* iface_selection_get)(app_usbd_class_inst_t const * const p_inst, uint8_t iface_idx);

} app_usbd_class_methods_t;

/**
 * @brief The instance structure itself.
 *
 * The structure of base class instance.
 */
struct app_usbd_class_inst_s
{
    app_usbd_class_data_t          * p_data;          //!< Pointer to non-constant data
    app_usbd_class_methods_t const * p_class_methods; //!< Class interface methods
    struct
    {
        uint8_t cnt;      //!< Number of defined interfaces
        uint8_t config[]; //!< Interface configuration data followed by endpoint data
    } iface; //!< Interface structure
};


/**
 * @brief Get total number of interfaces.
 *
 *
 */
static inline uint8_t app_usbd_class_iface_count_get(app_usbd_class_inst_t const * const p_inst)
{
    return p_inst->iface.cnt;
}

/**
 * @brief Interface accessing function.
 *
 * Get interface pointer.
 * Interfaces create continuous array in the memory so it is possible to get
 * interface with index 0 and then just iterate to the next one.
 *
 * @param p_inst    Pointer to the class instance
 * @param iface_idx Index of the instance to get.
 *                  This is not the interface identifier.
 *                  Technically it is the index of the interface in the class description array.
 * @return Pointer to the interface configuration parameters or NULL if given index is out of interface scope for given class.
 */
static inline app_usbd_class_iface_conf_t const * app_usbd_class_iface_get(
        app_usbd_class_inst_t const * const p_inst,
        uint8_t iface_idx)
{
    ASSERT(NULL != p_inst);
    if (iface_idx >= (app_usbd_class_iface_count_get(p_inst)))
    {
        return NULL;
    }

    app_usbd_class_iface_conf_t const * p_interface =
            (app_usbd_class_iface_conf_t const * )(p_inst->iface.config);
    return &(p_interface[iface_idx]);
}

/**
 * @brief Get interface number.
 *
 * @param p_iface Pointer to interface structure.
 *
 * @return Interface number from interface configuration structure.
 */
static inline uint8_t app_usbd_class_iface_number_get(
        app_usbd_class_iface_conf_t const * const p_iface)
{
    return p_iface->number;
}

/**
 * @brief Get number of endpoints in interface.
 *
 * @param p_iface Pointer to interface structure.
 *
 * @return Number of endpoints used by given interface.
 */
static inline uint8_t app_usbd_class_iface_ep_count_get(
        app_usbd_class_iface_conf_t const * const p_iface)
{
    return p_iface->ep_cnt;
}

/**
 * @brief Interface Endpoint accessing function.
 *
 * @param p_iface Interface configuration pointer.
 * @param ep_idx  Endpoint index.
 *
 * @return Endpoint information structure pointer or NULL if given index is outside of endpoints for selected interface.
 *
 * @sa app_usbd_class_iface_get
 */
static inline app_usbd_class_ep_conf_t const * app_usbd_class_iface_ep_get(
        app_usbd_class_iface_conf_t const * const p_iface,
        uint8_t ep_idx)
{
    ASSERT(NULL != p_iface);
    if (ep_idx >= p_iface->ep_cnt)
    {
        return NULL;
    }

    app_usbd_class_ep_conf_t const * p_ep =
            (app_usbd_class_ep_conf_t const * )(((uint8_t const *)p_iface) + p_iface->ep_offset);
    return &(p_ep[ep_idx]);
}

/**
 * @brief Translate endpoint address to class index.
 *
 * @param p_iface       Interface configuration pointer.
 * @param ep_address    Endpoint address.
 *
 * @return Endpoint index or number of endpoints if not found.
 *
 */
static inline  uint8_t app_usbd_class_iface_ep_idx_get(
        app_usbd_class_iface_conf_t const * const p_iface,
        nrf_drv_usbd_ep_t ep_address)
{
    ASSERT(NULL != p_iface);
    app_usbd_class_ep_conf_t const * p_ep =
            (app_usbd_class_ep_conf_t const * )(((uint8_t const *)p_iface) + p_iface->ep_offset);

    uint8_t i;
    for (i = 0; i < p_iface->ep_cnt; ++i)
    {
        if (ep_address == p_ep[i].address)
        {
            break;
        }
    }

    return i;
}

/**
 * @brief Get the selected endpoint address.
 *
 * @param p_ep Endpoint configuration structure.
 *
 * @return Endpoint address
 */
static inline nrf_drv_usbd_ep_t app_usbd_class_ep_address_get(app_usbd_class_ep_conf_t const * p_ep)
{
    return (nrf_drv_usbd_ep_t)p_ep->address;
}

/**
 * @brief Get the pointer to the writable instance data.
 *
 * @param p_inst Instance pointer.
 * @return Pointer to writable instance data.
 */
static inline app_usbd_class_data_t * app_usbd_class_data_access(
        app_usbd_class_inst_t const * const p_inst)
{
    return p_inst->p_data;
}

/**
 * @name Internal macros for argument mapping
 *
 * Functions to be used as a mapping macro for @ref MACRO_MAP, @ref MACRO_MAP_FOR or @ref MACRO_MAP_FOR_PARAM
 * @{
 */
    /**
     * @brief Count the number of endpoints in given configuration.
     *
     * Config should be given as a interface configuration in a brackets:
     * @code
     * (interface_nr, ep1, ep2, ep3)
     * @endcode
     * Number of endpoints may vary from 0 to a few (technically up to 16, but it seems not to make sense to use more than 4).
     * Interface number is always present.
     *
     * @param iface_config Single interface configuration (in brackets).
     *
     * @return Number of endpoints in interface. This is computed value - can be used by compiler but not by preprocessor.
     */
    #define APP_USBD_CLASS_CONF_IFACE_EP_COUNT_(iface_config)   \
                        (NUM_VA_ARGS(BRACKET_EXTRACT(iface_config)) - 1)

    /**
     * @brief Adds the number of endpoints in given config to the current value.
     *
     * This is basically @ref APP_USBD_CLASS_CONF_IFACE_EP_COUNT_ with plus sign added.
     *
     * @param iface_config See parameters documentation in @ref APP_USBD_CLASS_CONF_IFACE_EP_COUNT_
     *
     * @return Plus sign followed by number of endpoints in interface.
     *
     * @sa APP_USBD_CLASS_CONF_IFACE_EP_COUNT_
     */
    #define APP_USBD_CLASS_CONF_IFACE_EP_PLUS_COUNT_(iface_config)  \
                        + APP_USBD_CLASS_CONF_IFACE_EP_COUNT_(iface_config)

    /**
     * @brief Create variable for endpoint.
     */

    /**
     * @brief Extract endpoints given interface configuration.
     *
     * This macro gets single endpoint configuration and extracts all the endpoints.
     * It also adds comma on the end of extracted endpoints.
     * This way when this macro is called few times it generates nice list of all endpoints
     * that may be used to array initialization.
     *
     * @param iface_config Single interface configuration in brackets.
     *                     The format should be similar like described in @ref APP_USBD_CLASS_CONF_IFACE_EP_COUNT_.
     */
    #define APP_USBD_CLASS_IFACE_EP_EXTRACT_(iface_config)                  \
        CONCAT_2(APP_USBD_CLASS_IFACE_EP_EXTRACT_,                          \
                NUM_VA_ARGS_IS_MORE_THAN_1(BRACKET_EXTRACT(iface_config)))  \
                (BRACKET_EXTRACT(iface_config))

    /**
     * @brief Auxiliary macro for @ref APP_USBD_CLASS_IFACE_EP_EXTRACT_
     *
     * This macro is called when interface has no endpoints.
     */
    #define APP_USBD_CLASS_IFACE_EP_EXTRACT_0(iface_nr)

    /**
     * @brief Auxiliary macro for @ref APP_USBD_CLASS_IFACE_EP_EXTRACT_
     *
     * This macro is called when interface has at least one endpoint.
     */
    #define APP_USBD_CLASS_IFACE_EP_EXTRACT_1(...)              \
                APP_USBD_CLASS_IFACE_EP_EXTRACT_1_(__VA_ARGS__)

    #define APP_USBD_CLASS_IFACE_EP_EXTRACT_1_(iface_nr, ...)   \
                MACRO_MAP_REC(APP_USBD_CLASS_IFACE_EP_EXTRACT_1__, __VA_ARGS__)
    
    #define APP_USBD_CLASS_IFACE_EP_EXTRACT_1__(ep) \
                {(nrf_drv_usbd_ep_t) (ep)}, 

    /**
     * @brief Generate configuration for single interface.
     *
     * This macro extract configuration for single interface.
     * The configuration is inside curly brackets and comma is added on the end.
     * This mean it can be directly used to init array of interface configurations.
     *
     * @param iface_config  Single interface configuration.
     * @param N             Currently processed configuration.
     * @param iface_configs All interfaces configuration in brackets.
     */
    #define APP_USBD_CLASS_IFACE_CONFIG_EXTRACT_(iface_config, N, iface_configs)  \
        CONCAT_2(APP_USBD_CLASS_IFACE_CONFIG_EXTRACT_,                            \
                NUM_VA_ARGS_IS_MORE_THAN_1(BRACKET_EXTRACT(iface_config)))        \
                (N, iface_configs, BRACKET_EXTRACT(iface_config))

    /**
     * @brief Macro used when there was an error extracting number of configs.
     *
     * Throws a syntax error.
     */
    #define APP_USBD_CLASS_IFACE_CONFIG_EXTRACT_x(iface_config, N, iface_configs) \
        [N] = !!!iface_config!!!
    /**
     * @brief Auxiliary macro for @ref APP_USBD_CLASS_IFACE_CONFIG_EXTRACT_
     *
     * This macro is called when interface has no endpoints.
     */
    #define APP_USBD_CLASS_IFACE_CONFIG_EXTRACT_0(N, iface_configs, iface_nr)   \
        APP_USBD_CLASS_IFACE_CONFIG_EXTRACT_0_(N, iface_configs, iface_nr)
    #define APP_USBD_CLASS_IFACE_CONFIG_EXTRACT_0_(N, iface_configs, iface_nr)  \
        { .number = iface_nr, .ep_cnt = 0, .ep_offset = 0 },

    /**
     * @brief Auxiliary macro for @ref APP_USBD_CLASS_IFACE_CONFIG_EXTRACT_
     *
     * This macro is called when interface has at last one endpoint.
     */
    #define APP_USBD_CLASS_IFACE_CONFIG_EXTRACT_1(N, iface_configs, ...)    \
        APP_USBD_CLASS_IFACE_CONFIG_EXTRACT_1_(N, iface_configs, __VA_ARGS__)
    #define APP_USBD_CLASS_IFACE_CONFIG_EXTRACT_1_(N, iface_configs, iface_nr, ...)     \
        { .number = iface_nr, .ep_cnt = NUM_VA_ARGS(__VA_ARGS__),                       \
          .ep_offset = APP_USBD_CLASS_CONF_TOTAL_EP_COUNT_N(N, iface_configs) *         \
                       sizeof(app_usbd_class_ep_conf_t)                                 \
                       + ((NUM_VA_ARGS(BRACKET_EXTRACT(iface_configs)) - N) *           \
                               sizeof(app_usbd_class_iface_conf_t))                     \
        },

/** @} */


/**
 * @name Macros that uses mapping macros internally
 *
 * Auxiliary macros that uses mapping macros to make some calculations or realize other functionality.
 * Mapped here for easier unit testing and to hide complex mapping functions calling.
 * @{
 */

/**
 * @brief Count total number of endpoints.
 *
 * @param iface_configs List of interface configurations like explained
 *                      in documentation for @ref APP_USBD_CLASS_INSTANCE_TYPEDEF
 *
 * @return The equation to calculate the number of endpoints by compiler.
 */
#define APP_USBD_CLASS_CONF_TOTAL_EP_COUNT(iface_configs) \
    (0 MACRO_MAP(APP_USBD_CLASS_CONF_IFACE_EP_PLUS_COUNT_, BRACKET_EXTRACT(iface_configs)))

/**
 * @brief Count total number of endpoint up-to interface index.
 *
 * The version of @ref APP_USBD_CLASS_CONF_TOTAL_EP_COUNT macro which takes the
 * number of interfaces to analyze.
 *
 * @param N             Number of interfaces to analyze.
 * @param iface_configs List of interface configurations like explained
 *                      in documentation for @ref APP_USBD_CLASS_INSTANCE_TYPEDEF
 *
 * @return The equation to calculate the number of endpoints by compiler.
 */
#define APP_USBD_CLASS_CONF_TOTAL_EP_COUNT_N(N, iface_configs) \
    (0 MACRO_MAP_N(N, APP_USBD_CLASS_CONF_IFACE_EP_PLUS_COUNT_, BRACKET_EXTRACT(iface_configs)))

/**
 * @brief Extract configurations for interfaces.
 *
 * This macro extracts the configurations for every interface.
 * Basically uses the @ref APP_USBD_CLASS_IFACE_CONFIG_EXTRACT_ macro on every
 * configuration found.
 *
 * This should generate interface configuration initialization data
 * in comma separated initializers in curly braces.
 *
 * @param iface_configs List of interface configurations like explained
 *                      in documentation for @ref APP_USBD_CLASS_INSTANCE_TYPEDEF
 *
 * @return Comma separated initialization data for all interfaces.
 */
/*lint -emacro( (40), APP_USBD_CLASS_IFACES_CONFIG_EXTRACT) */
#define APP_USBD_CLASS_IFACES_CONFIG_EXTRACT(iface_configs)     \
    MACRO_MAP_FOR_PARAM(iface_configs,                          \
                        APP_USBD_CLASS_IFACE_CONFIG_EXTRACT_,   \
                        BRACKET_EXTRACT(iface_configs))

/**
 * @brief Extract all endpoints.
 *
 * Macro that extracts all endpoints from every interface.
 *
 * @param iface_configs List of interface configurations like explained
 *                      in documentation for @ref APP_USBD_CLASS_INSTANCE_TYPEDEF
 *
 * @return Comma separated list of endpoints.
 */
/*lint -emacro( (40), APP_USBD_CLASS_IFACES_EP_EXTRACT) */
#define APP_USBD_CLASS_IFACES_EP_EXTRACT(iface_configs) \
    MACRO_MAP(APP_USBD_CLASS_IFACE_EP_EXTRACT_, BRACKET_EXTRACT(iface_configs))


/** @} */


/**
 * @brief USBD instance of class mnemonic.
 *
 * Macro that generates mnemonic for the name of the structure that describes instance for selected class.
 *
 * @param type_name The name of the instance without _t postfix.
 *
 * @return The name with the right postfix to create the name for the type for the class.
 */
#define APP_USBD_CLASS_INSTANCE_TYPE(type_name) CONCAT_2(type_name, _t)

/**
 * @brief USBD data for instance class mnemonic.
 *
 * The mnemonic of the variable type that holds writable part of the class instance.
 *
 * @param type_name The name of the instance without _t postfix.
 *
 * @return The name with the right postfix to create the name for the data type for the class.
 */
#define APP_USBD_CLASS_DATA_TYPE(type_name) CONCAT_2(type_name, _data_t)

/**
 * @brief Declare class specific member of class instance.
 *
 * @param type Type of the attached class configuration.
 *
 * @sa APP_USBD_CLASS_INSTANCE_TYPEDEF
 */
#define APP_USBD_CLASS_INSTANCE_SPECIFIC_DEC(type) type class_part;

/**
 * @brief Used if there is no class specific configuration.
 *
 * This constant can be used if there is no specific configuration inside created instance.
 *
 * @sa APP_USBD_CLASS_INSTANCE_TYPEDEF
 */
#define APP_USBD_CLASS_INSTANCE_SPECIFIC_DEC_NONE

/**
 * @brief Declare class specific member of class data.
 *
 * @param type Type of the attached class data.
 *
 * @sa APP_USBD_CLASS_DATA_TYPEDEF
 */
#define APP_USBD_CLASS_DATA_SPECIFIC_DEC(type) APP_USBD_CLASS_INSTANCE_SPECIFIC_DEC(type)

/**
 * @brief Used if there is no class specific data.
 *
 * This constant can be used if there is no specific writable data inside created instance.
 *
 * @sa APP_USBD_CLASS_DATA_TYPEDEF
 */
#define APP_USBD_CLASS_DATA_SPECIFIC_DEC_NONE APP_USBD_CLASS_INSTANCE_SPECIFIC_DEC_NONE




/**
 * @brief Instance structure declaration.
 *
 * The macro that declares a variable type that would be used to store given class instance.
 * Class instance stores all the data from @ref app_usbd_class_inst_t and overlaid data for specified class.
 *
 * The structure of interface configuration data:
 * @code
 * (
 *    (iface1_nr, (ep1, ep2, ep3)),
      (iface2_nr),
      (iface3_nr, (ep4))
 * )
 * @endcode
 *
 * @param type_name          The name of the instance without _t postfix.
 * @param interfaces_configs List of interface configurations like explained above.
 * @param class_config_dec   Result of the macro
 *                           @ref APP_USBD_CLASS_INSTANCE_SPECIFIC_DEC or
 *                           @ref APP_USBD_CLASS_INSTANCE_SPECIFIC_DEC_NONE
 *
 * @return The definition of the structure type that holds all the required data.
 *
 * @note It should not be used directly in the final application. See @ref APP_USBD_CLASS_DATA_TYPEDEF instead.
 *
 * @note APP_USBD_CLASS_DATA_TYPEDEF has to be called first for the compilation to success.
 *
 * @sa APP_USBD_CLASS_TYPEDEF
 */
#define APP_USBD_CLASS_INSTANCE_TYPEDEF(type_name, interfaces_configs, class_config_dec)     \
    typedef union CONCAT_2(type_name, _u)                                                    \
    {                                                                                        \
        app_usbd_class_inst_t base;                                                          \
        struct                                                                               \
        {                                                                                    \
            APP_USBD_CLASS_DATA_TYPE(type_name) * p_data;                                    \
            app_usbd_class_methods_t const * p_class_methods;                                \
            struct                                                                           \
            {                                                                                \
                uint8_t cnt;                                                                 \
                app_usbd_class_iface_conf_t                                                  \
                                config[NUM_VA_ARGS(BRACKET_EXTRACT(interfaces_configs))];    \
                app_usbd_class_ep_conf_t                                                     \
                                ep[APP_USBD_CLASS_CONF_TOTAL_EP_COUNT(interfaces_configs)];  \
            } iface;                                                                         \
            class_config_dec                                                                 \
        } specific;                                                                          \
    } APP_USBD_CLASS_INSTANCE_TYPE(type_name)

/**
 * @brief Same as @ref APP_USBD_CLASS_INSTANCE_TYPEDEF but for class with EP0 only.
 */
#define APP_USBD_CLASS_INSTANCE_NO_EP_TYPEDEF(type_name, interfaces_configs, class_config_dec)  \
    typedef union CONCAT_2(type_name, _u)                                                       \
    {                                                                                           \
        app_usbd_class_inst_t base;                                                             \
        struct                                                                                  \
        {                                                                                       \
            APP_USBD_CLASS_DATA_TYPE(type_name) * p_data;                                       \
            app_usbd_class_methods_t const * p_class_methods;                                   \
            struct                                                                              \
            {                                                                                   \
                uint8_t cnt;                                                                    \
                app_usbd_class_iface_conf_t                                                     \
                                config[NUM_VA_ARGS(BRACKET_EXTRACT(interfaces_configs))];       \
            } iface;                                                                            \
            class_config_dec                                                                    \
        } specific;                                                                             \
    } APP_USBD_CLASS_INSTANCE_TYPE(type_name)

/**
 * @brief Writable data structure declaration.
 *
 * The macro that declares a variable type that would be used to store given class writable data.
 * Writable data contains base part of the type @ref app_usbd_class_data_t followed by
 * class specific data.
 *
 * @param type_name      The name of the type without _t postfix.
 * @param class_data_dec Result of the macro
 *                       @ref APP_USBD_CLASS_DATA_SPECIFIC_DEC or
 *                       @ref APP_USBD_CLASS_DATA_SPECIFIC_DEC_NONE
 *
 * @return The definition of the structure type that holds all the required writable data.
 *
 * @note It should not be used directly in the final application. See @ref APP_USBD_CLASS_DATA_TYPEDEF instead.
 *
 * @sa APP_USBD_CLASS_TYPEDEF
 */
#define APP_USBD_CLASS_DATA_TYPEDEF(type_name, class_data_dec) \
    typedef struct                                             \
    {                                                          \
        app_usbd_class_data_t base;                            \
        class_data_dec                                         \
    }APP_USBD_CLASS_DATA_TYPE(type_name)


/**
 * @brief Declare all data types required by the class instance.
 *
 * Macro that declares data type first and then instance type.
 *
 * @param type_name          The name of the type without _t postfix.
 * @param interface_configs  List of interface configurations like in @ref APP_USBD_CLASS_INSTANCE_TYPEDEF.
 * @param class_config_dec   Result of the macro
 *                           @ref APP_USBD_CLASS_INSTANCE_SPECIFIC_DEC or
 *                           @ref APP_USBD_CLASS_INSTANCE_SPECIFIC_DEC_NONE
 * @param class_data_dec     Result of the macro
 *                           @ref APP_USBD_CLASS_DATA_SPECIFIC_DEC or
 *                           @ref APP_USBD_CLASS_DATA_SPECIFIC_DEC_NONE
 *
 * @return Declaration of the data type for the instance and instance itself.
 *
 * @sa APP_USBD_CLASS_DATA_TYPEDEF
 * @sa APP_USBD_CLASS_INSTANCE_TYPEDEF
 */
#define APP_USBD_CLASS_TYPEDEF(type_name, interface_configs, class_config_dec, class_data_dec) \
    APP_USBD_CLASS_DATA_TYPEDEF(type_name, class_data_dec);                                    \
    APP_USBD_CLASS_INSTANCE_TYPEDEF(type_name, interface_configs, class_config_dec)

/**
 * @brief Same as @ref APP_USBD_CLASS_TYPEDEF but for class with EP0 only.
 */
#define APP_USBD_CLASS_NO_EP_TYPEDEF(type_name,                                                \
                                     interface_configs,                                        \
                                     class_config_dec,                                         \
                                     class_data_dec)                                           \
    APP_USBD_CLASS_DATA_TYPEDEF(type_name, class_data_dec);                                    \
    APP_USBD_CLASS_INSTANCE_NO_EP_TYPEDEF(type_name, interface_configs, class_config_dec)

/**
 * @brief Forward declaration of type defined by @ref APP_USBD_CLASS_TYPEDEF
 *
 * @param type_name          The name of the type without _t postfix.
 * */
#define APP_USBD_CLASS_FORWARD(type_name) union CONCAT_2(type_name, _u)

/**
 * @brief Generate the initialization data for.
 *
 * Macro that generates the initialization data for instance.
 *
 * @param p_ram_data         Pointer to writable instance data structure.
 * @param class_methods      Class methods.
 * @param interfaces_configs Exactly the same interface config data that in @ref APP_USBD_CLASS_INSTANCE_TYPEDEF
 * @param class_config_part  Configuration part. The data should be inside brackets.
 *                           Any data here would be removed from brackets and then put as an initialization
 *                           data for class_part member of instance structure.
 *
 * @note It should not be used directly in the final application. See @ref APP_USBD_CLASS_INST_DEF instead.
 */
#define APP_USBD_CLASS_INSTANCE_INITVAL(p_ram_data,                                     \
                                        class_methods,                                  \
                                        interfaces_configs,                             \
                                        class_config_part)                              \
    {                                                                                   \
        .specific = {                                                                   \
            .p_data = p_ram_data,                                                       \
            .p_class_methods = class_methods,                                           \
            .iface = {                                                                  \
                .cnt    = NUM_VA_ARGS(BRACKET_EXTRACT(interfaces_configs)),             \
                .config = { APP_USBD_CLASS_IFACES_CONFIG_EXTRACT(interfaces_configs) }, \
                .ep     = { APP_USBD_CLASS_IFACES_EP_EXTRACT(interfaces_configs) }      \
            },                                                                          \
            BRACKET_EXTRACT(class_config_part)                                          \
        }                                                                               \
    }

/**
 * @brief Same as @ref APP_USBD_CLASS_INSTANCE_INITVAL but for class with EP0 only.
 */
#define APP_USBD_CLASS_INSTANCE_NO_EP_INITVAL(p_ram_data,                               \
                                              class_methods,                            \
                                              interfaces_configs,                       \
                                              class_config_part)                        \
    {                                                                                   \
        .specific = {                                                                   \
            .p_data = p_ram_data,                                                       \
            .p_class_methods = class_methods,                                           \
            .iface = {                                                                  \
                .cnt    = NUM_VA_ARGS(BRACKET_EXTRACT(interfaces_configs)),             \
                .config = { APP_USBD_CLASS_IFACES_CONFIG_EXTRACT(interfaces_configs) }  \
            },                                                                          \
            BRACKET_EXTRACT(class_config_part)                                          \
        }                                                                               \
    }

/**
 * @brief Define the base class instance.
 *
 * Macro that defines whole instance variable and fill it with initialization data.
 *
 * The tricky part is @c class_config_part.
 * The configuration data here has to be placed inside brackets.
 * Then any type of values can be used depending on the type used in @ref APP_USBD_CLASS_TYPEDEF.
 * If instance does not has any specyfic data, use just empty bracket here.
 * @code
 * APP_USBD_CLASS_TYPEDEF(
 *      some_base_class,
 *      CLASS_BASE_CONFIGURATION,
 *      APP_USBD_CLASS_INSTANCE_SPECIFIC_DEC_NONE,
 *      APP_USBD_CLASS_DATA_SPECIFIC_DEC_NONE
 * );
 * APP_USBD_CLASS_INST_DEF(
 *     some_base_class_inst,
 *     some_base_class,
 *     base_class_event_handler,
 *     CLASS_BASE_CONFIGURATION,
 *     () // Empty configuration
 * );
 * @endcode
 *
 * If the type of instance configuration is simple type, just provide initialization value:
 * @code
 * APP_USBD_CLASS_TYPEDEF(
 *      some_base_class,
 *      CLASS_BASE_CONFIGURATION,
 *      APP_USBD_CLASS_INSTANCE_SPECIFIC_DEC_NONE,
 *      APP_USBD_CLASS_DATA_SPECIFIC_DEC(uint8_t)
 * );
 * APP_USBD_CLASS_INST_DEF(
 *     some_base_class_inst,
 *     some_base_class,
 *     base_class_event_handler,
 *     CLASS_BASE_CONFIGURATION,
 *     (12) // Example values
 * );
 * @endcode
 *
 * If the type of instance configuration is structure, provide initialization value for the whole structure:
 * @code
 * typedef structure
 * {
 *     uint32_t p1;
 *     uint8_t p2;
 * }my_config_t;
 *
 * APP_USBD_CLASS_TYPEDEF(
 *      some_base_class,
 *      CLASS_BASE_CONFIGURATION,
 *      APP_USBD_CLASS_INSTANCE_SPECIFIC_DEC_NONE,
 *      APP_USBD_CLASS_DATA_SPECIFIC_DEC(my_config_t)
 * );
 * APP_USBD_CLASS_INST_DEF(
 *     some_base_class_inst,
 *     some_base_class,
 *     base_class_event_handler,
 *     CLASS_BASE_CONFIGURATION,
 *     ({12, 3}) // Example values
 * );
 * @endcode
 *
 * @param instance_name The name of created instance variable.
 *                      It would be constant variable and its type would be app_usbd_class_inst_t.
 * @param type_name     The name of the variable type. It has to be the same type that was passed to
 *                      @ref APP_USBD_CLASS_TYPEDEF
 * @param class_methods Class unified interface.
 * @param interfaces_configs The same configuration data that the one passed to @ref APP_USBD_CLASS_TYPEDEF
 * @param class_config_part  Configuration data to the type that was declared by class_data_dec when calling
 *                           @ref APP_USBD_CLASS_TYPEDEF.
 *                           Configuration data has to be provided in brackets.
 *                           It would be extracted from brackets and placed in initialization part of configuration structure.
 *                           See detailed description of this macro for more informations.
 */
#define APP_USBD_CLASS_INST_DEF(instance_name,                                  \
                                type_name,                                      \
                                class_methods,                                  \
                                interfaces_configs,                             \
                                class_config_part)                              \
    static APP_USBD_CLASS_DATA_TYPE(type_name) CONCAT_2(instance_name, _data);  \
    static const APP_USBD_CLASS_INSTANCE_TYPE(type_name) instance_name =        \
        APP_USBD_CLASS_INSTANCE_INITVAL(                                        \
            &CONCAT_2(instance_name, _data),                                    \
            class_methods,                                                      \
            interfaces_configs,                                                 \
            class_config_part)


/**
 * @brief Define the base class instance in global scope.
 *
 * This is the same macro like @ref APP_USBD_CLASS_INST_DEF but it creates the instance
 * without static keyword.
 *
 * @param instance_name      See documentation for @ref APP_USBD_CLASS_INST_DEF
 * @param type_name          See documentation for @ref APP_USBD_CLASS_INST_DEF
 * @param class_methods      See documentation for @ref APP_USBD_CLASS_INST_DEF
 * @param interfaces_configs See documentation for @ref APP_USBD_CLASS_INST_DEF
 * @param class_config_part  See documentation for @ref APP_USBD_CLASS_INST_DEF
 */
#define APP_USBD_CLASS_INST_GLOBAL_DEF(instance_name,                           \
                                       type_name,                               \
                                       class_methods,                           \
                                       interfaces_configs,                      \
                                       class_config_part)                       \
    static APP_USBD_CLASS_DATA_TYPE(type_name) CONCAT_2(instance_name, _data);  \
    const APP_USBD_CLASS_INSTANCE_TYPE(type_name) instance_name =               \
        APP_USBD_CLASS_INSTANCE_INITVAL(                                        \
            &CONCAT_2(instance_name, _data),                                    \
            class_methods,                                                      \
            interfaces_configs,                                                 \
            class_config_part)

/**
 * @brief Same as @ref APP_USBD_CLASS_INST_GLOBAL_DEF but for class with EP0 only.
 */
#define APP_USBD_CLASS_INST_NO_EP_GLOBAL_DEF(instance_name,                     \
                                             type_name,                         \
                                             class_methods,                     \
                                             interfaces_configs,                \
                                             class_config_part)                 \
    static APP_USBD_CLASS_DATA_TYPE(type_name) CONCAT_2(instance_name, _data);  \
    const APP_USBD_CLASS_INSTANCE_TYPE(type_name) instance_name =               \
        APP_USBD_CLASS_INSTANCE_NO_EP_INITVAL(                                  \
            &CONCAT_2(instance_name, _data),                                    \
            class_methods,                                                      \
            interfaces_configs,                                                 \
            class_config_part)
/**
 * @brief Access class specific configuration.
 *
 * Macro that returns class specific configuration.
 *
 * @param[in] p_inst Instance pointer.
 *
 * @return A pointer for class specific part of the instance.
 *
 * @note If macro is used on the instance that has no class specific configuration
 *       an error would be generated during compilation.
 */
#define APP_USBD_CLASS_GET_SPECIFIC_CONFIG(p_inst) (&((p_inst)->specific.class_part))

/**
 * @brief Access class specific data.
 *
 * @param[in] p_inst Instance pointer.
 *
 * @return A pointer for class specific part of writable data.
 *
 * @note If macro is used on the instance that has no class specific data
 *       an error would be generated during compilation.
 */
#define APP_USBD_CLASS_GET_SPECIFIC_DATA(p_inst) (&(((p_inst)->specific.p_data)->class_part))

/**
 * @brief Macro to get base instance from class specific instance.
 *
 * This macro may be used on class specific instance to get base instance that
 * can be processed by base instance access functions.
 * Class specific instance can be just casted to class base instance,
 * but then we would totally lost type safety.
 *
 * A little more safe is to use pointer to base member of class instance.
 * This would generate an error when used on any variable that has no base member
 * and would generate also error if this base member is wrong type.
 */
#define APP_USBD_CLASS_BASE_INSTANCE(p_inst) (&((p_inst)->base))

/*lint -emacro(142 438 616 646, APP_USBD_CLASS_DESCRIPTOR_INIT, APP_USBD_CLASS_DESCRIPTOR_BEGIN, APP_USBD_CLASS_DESCRIPTOR_YIELD, APP_USBD_CLASS_DESCRIPTOR_END, APP_USBD_CLASS_DESCRIPTOR_WRITE)*/

/**
 * @brief Initialize class descriptor.
 *
 * @param[in] p_ctx Class descriptor context.
 */

#define APP_USBD_CLASS_DESCRIPTOR_INIT(p_ctx)       \
    (p_ctx)->line = 0;

/**
 * @brief Begin class descriptor.
 *
 * @param[in] p_ctx    Class descriptor context.
 * @param[in] p_buff   Buffer to write into.
 * @param[in] max_size Size of the buffer.
 */

#define APP_USBD_CLASS_DESCRIPTOR_BEGIN(p_ctx, p_buff, max_size)            \
    ASSERT((p_ctx) != NULL);                                                \
    app_usbd_class_descriptor_state_t this_descriptor_feed;                 \
    this_descriptor_feed.p_buffer     = (p_buff);                           \
    this_descriptor_feed.current_size = 0;                                  \
    this_descriptor_feed.maximum_size = (max_size);                         \
    this_descriptor_feed.p_context    = (p_ctx);                            \
    switch ((this_descriptor_feed.p_context)->line)                         \
    {                                                                       \
        case 0:                                                             \
            ;

/**
 * @brief Yield class descriptor
 *
 */

#define APP_USBD_CLASS_DESCRIPTOR_YIELD()                   \
do                                                          \
{                                                           \
        (this_descriptor_feed.p_context)->line = __LINE__;  \
        return true;                                        \
        case __LINE__:                                      \
            ;                                               \
} while (0)

/*lint -emacro(438 527, APP_USBD_CLASS_DESCRIPTOR_END)*/

/**
 * @brief End class descriptor.
 *
 * This function has to be called at the end of class descriptor feeder function.
 * No other operations in feeder function can be done after calling it.
 */

#define APP_USBD_CLASS_DESCRIPTOR_END()             \
        APP_USBD_CLASS_DESCRIPTOR_YIELD();          \
    }                                               \
    (this_descriptor_feed.p_context)->line = 0;     \
    return false;


/**
 * @brief Write descriptor using protothreads.
 *
 * This function writes one byte to the buffer with offset. If buffer is full
 * it yields.
 *
 * It is used by the class descriptor feeders internally.
 *
 * @ref APP_USBD_CLASS_DESCRIPTOR_BEGIN has to be called before using this function.
 * @ref APP_USBD_CLASS_DESCRIPTOR_END has to be called after last use of this function.
 *
 * @param data Byte to be written to buffer.
 */
#define APP_USBD_CLASS_DESCRIPTOR_WRITE(data)                                           \
do                                                                                      \
{                                                                                       \
    (this_descriptor_feed.p_context)->data_buffer = (data);                             \
    if (this_descriptor_feed.current_size >= this_descriptor_feed.maximum_size)         \
    {                                                                                   \
        APP_USBD_CLASS_DESCRIPTOR_YIELD();                                              \
    }                                                                                   \
    if(this_descriptor_feed.p_buffer != NULL)                                           \
    {                                                                                   \
        *(this_descriptor_feed.p_buffer + this_descriptor_feed.current_size) =          \
            (this_descriptor_feed.p_context)->data_buffer;                              \
    }                                                                                   \
    this_descriptor_feed.current_size++;                                                \
} while(0);

/** @} */

#ifdef __cplusplus
}
#endif

#endif /* APP_USBD_CLASS_BASE_H__ */
