/**
 * Copyright (c) 2016 - 2017, 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__

#ifdef __cplusplus
extern "C" {
#endif

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

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

/**
 * @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_class_data_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
     *
     * @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 get descriptors
     *
     * The function used by every class instance.
     * @param[in,out] p_inst  Instance of the class
     * @param[out]    p_size  Descriptor size
     *
     * @return Class descriptors start address
     */
    const void * (* get_descriptors)(app_usbd_class_inst_t const * const p_inst,
                                     size_t * p_size);
} 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 creates continuous array in the memory so it is possible to get
 * interface with index 0 and the 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(PARAM_CBRACE, __VA_ARGS__)

    /**
     * @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))

    #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 witch 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.
 */
#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
 */
#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 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 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 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 have 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 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 have 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))

/** @} */

#ifdef __cplusplus
}
#endif

#endif /* APP_USBD_CLASS_BASE_H__ */
