/*
 * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#pragma once

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include "esp_err.h"
#include "usb/usb_types_stack.h"
#include "libuvc_def.h"
#include "arduino_config.h"

#ifdef __cplusplus
extern "C" {
#endif

#define FRAME_RESOLUTION_ANY              __UINT16_MAX__        /*!< any uvc frame resolution */
#define UAC_FREQUENCY_ANY                 __UINT32_MAX__        /*!< any uac sample frequency */
#define UAC_BITS_ANY                      __UINT16_MAX__        /*!< any uac bit resolution */
#define UAC_CH_ANY                        0                     /*!< any uac channel number */
#define FPS2INTERVAL(fps)                 (10000000ul / fps)    /*!< convert fps to uvc frame interval */
#define FRAME_INTERVAL_FPS_5              FPS2INTERVAL(5)       /*!< 5 fps */
#define FRAME_INTERVAL_FPS_10             FPS2INTERVAL(10)      /*!< 10 fps */
#define FRAME_INTERVAL_FPS_15             FPS2INTERVAL(15)      /*!< 15 fps */
#define FRAME_INTERVAL_FPS_20             FPS2INTERVAL(20)      /*!< 20 fps */
#define FRAME_INTERVAL_FPS_30             FPS2INTERVAL(25)      /*!< 25 fps */
#define FLAG_UVC_SUSPEND_AFTER_START      (1 << 0)              /*!< suspend uvc after usb_streaming_start */
#define FLAG_UAC_SPK_SUSPEND_AFTER_START  (1 << 1)              /*!< suspend uac speaker after usb_streaming_start */
#define FLAG_UAC_MIC_SUSPEND_AFTER_START  (1 << 2)              /*!< suspend uac microphone after usb_streaming_start */

/**
 * @brief UVC stream usb transfer type, most camera using isochronous mode,
 * bulk mode can also be support for higher bandwidth
 */
typedef enum {
    UVC_XFER_ISOC = 0,  /*!< Isochronous Transfer Mode */
    UVC_XFER_BULK,      /*!< Bulk Transfer Mode */
    UVC_XFER_UNKNOWN,   /*!< Unknown Mode */
} uvc_xfer_t;

/**
 * @brief Stream id, used for control
 * 
 */
typedef enum {
    STREAM_UVC = 0,     /*!< usb video stream */
    STREAM_UAC_SPK,     /*!< usb audio speaker stream */
    STREAM_UAC_MIC,     /*!< usb audio microphone stream */
    STREAM_MAX,         /*!< max stream id */
} usb_stream_t;

/**
 * @brief USB device connection status
 * 
 */
typedef enum {
    STREAM_CONNECTED = 0,
    STREAM_DISCONNECTED,
} usb_stream_state_t;

/**
 * @brief Stream control type, which also depends on if device support
 * 
 */
typedef enum {
    CTRL_NONE = 0,     /*!< None */
    CTRL_SUSPEND,      /*!< streaming suspend control. ctrl_data NULL */
    CTRL_RESUME,       /*!< streaming resume control. ctrl_data NULL */
    CTRL_UAC_MUTE,     /*!< mute control. ctrl_data (false/true) */
    CTRL_UAC_VOLUME,   /*!< volume control. ctrl_data (0~100) */
    CTRL_MAX,          /*!< max type value */
} stream_ctrl_t;

/**
 * @brief UVC configurations, for params with (optional) label, users do not need to specify manually, 
 * unless there is a problem with descriptors, or users want to skip the get and process descriptors steps
 */
typedef struct uvc_config {
    uint16_t frame_width;           /*!< Picture width, set FRAME_RESOLUTION_ANY for any resolution */
    uint16_t frame_height;          /*!< Picture height, set FRAME_RESOLUTION_ANY for any resolution */
    uint32_t frame_interval;        /*!< Frame interval in 100-ns units, 666666 ~ 15 Fps*/
    uint32_t xfer_buffer_size;      /*!< Transfer buffer size, using double buffer here, must larger than one frame size */
    uint8_t *xfer_buffer_a;         /*!< Buffer a for usb payload */
    uint8_t *xfer_buffer_b;         /*!< Buffer b for usb payload */
    uint32_t frame_buffer_size;     /*!< Frame buffer size, must larger than one frame size */
    uint8_t *frame_buffer;          /*!< Buffer for one frame */
    uvc_frame_callback_t *frame_cb; /*!< callback function to handle incoming frame */
    void *frame_cb_arg;             /*!< callback function arg */
    /*!< Optional configs, Users need to specify parameters manually when they want to
    skip the get and process descriptors steps (used to speed up startup)*/
    uvc_xfer_t xfer_type;           /*!< (optional) UVC stream transfer type, UVC_XFER_ISOC or UVC_XFER_BULK */
    uint8_t format_index;           /*!< (optional) Format index of MJPEG */
    uint8_t frame_index;            /*!< (optional) Frame index, to choose resolution */
    uint16_t interface;             /*!< (optional) UVC stream interface number */
    uint16_t interface_alt;         /*!< (optional) UVC stream alternate interface, to choose MPS (Max Packet Size), bulk fix to 0*/
    uint8_t ep_addr;                /*!< (optional) endpoint address of selected alternate interface*/
    uint32_t ep_mps;                /*!< (optional) MPS of selected interface_alt */
    uint32_t flags;                 /*!< (optional) flags to control the driver behavers */
} uvc_config_t;

/**
 * @brief mic frame type 
 * */ 
typedef struct {
    void *data;                 /*!< mic data */
    uint32_t data_bytes;        /*!< mic data size */
    uint16_t bit_resolution;    /*!< bit resolution in buffer */
    uint32_t samples_frequence; /*!< mic sample frequency */
} mic_frame_t;

/**
 * @brief uvc frame type 
 * */
typedef struct {
    uint16_t width;             /*!< frame width */
    uint16_t height;            /*!< frame height */
    uint32_t interval;          /*!< frame interval */
    uint32_t interval_min;      /*!< frame min interval */
    uint32_t interval_max;      /*!< frame max interval */
    uint32_t interval_step;     /*!< frame interval step */
} uvc_frame_size_t;

/**
 * @brief uac frame type 
 * */
typedef struct {
    uint8_t ch_num;                 /*!< channel numbers */
    uint16_t bit_resolution;        /*!< bit resolution in buffer */
    uint32_t samples_frequence;     /*!< sample frequency */
    uint32_t samples_frequence_min; /*!< min sample frequency */
    uint32_t samples_frequence_max; /*!< max sample frequency */
} uac_frame_size_t;

/**
 * @brief user callback function to handle incoming mic frames
 * 
 */
typedef void(mic_callback_t)(mic_frame_t *frame, void *user_ptr);

/**
 * @brief user callback function to handle usb device connection status
 * 
 */
typedef void(state_callback_t)(usb_stream_state_t state, void *user_ptr);

/**
 * @brief UAC configurations, for params with (optional) label, users do not need to specify manually, 
 * unless there is a problem with descriptor parse, or a problem with the device descriptor
 */
typedef struct {
    uint8_t spk_ch_num;                  /*!< speaker channel numbers, UAC_CH_ANY for any channel number  */
    uint8_t mic_ch_num;                  /*!< microphone channel numbers, UAC_CH_ANY for any channel number */
    uint16_t mic_bit_resolution;         /*!< microphone resolution(bits), UAC_BITS_ANY for any bit resolution */
    uint32_t mic_samples_frequence;      /*!< microphone frequence(Hz), UAC_FREQUENCY_ANY for any frequency */
    uint16_t spk_bit_resolution;         /*!< speaker resolution(bits), UAC_BITS_ANY for any */
    uint32_t spk_samples_frequence;      /*!< speaker frequence(Hz), UAC_FREQUENCY_ANY for any frequency */
    uint32_t spk_buf_size;               /*!< size of speaker send buffer, should be a multiple of spk_ep_mps */
    uint32_t mic_buf_size;               /*!< mic receive buffer size, 0 if not use */
    mic_callback_t *mic_cb;              /*!< mic callback, can not block in here!, NULL if not use */
    void *mic_cb_arg;                    /*!< mic callback args, NULL if not use */
    /*!< Optional configs, Users need to specify parameters manually when they want to
    skip the get and process descriptors steps (used to speed up startup)*/
    uint16_t mic_interface;              /*!< (optional) microphone stream interface number, set 0 if not use */
    uint8_t  mic_ep_addr;                /*!< (optional) microphone interface endpoint address */
    uint32_t mic_ep_mps;                 /*!< (optional) microphone interface endpoint mps */
    uint16_t spk_interface;              /*!< (optional) speaker stream interface number, set 0 if not use */
    uint8_t  spk_ep_addr;                /*!< (optional) speaker interface endpoint address */
    uint32_t spk_ep_mps;                 /*!< (optional) speaker interface endpoint mps */
    uint16_t ac_interface;               /*!< (optional) audio control interface number, set 0 if not use */
    uint8_t  mic_fu_id;                  /*!< (optional) microphone feature unit id, set 0 if not use */
    uint8_t  spk_fu_id;                  /*!< (optional) speaker feature unit id, set 0 if not use */
    uint32_t flags;                      /*!< (optional) flags to control the driver behavers */
} uac_config_t;

/**
 * @brief Config UVC streaming with user defined parameters.For normal use, user only need to specify 
 * no-optional parameters, and set optional parameters to 0 (the driver will find the correct value from the device descriptors).
 * For quick start mode, user should specify all parameters manually to skip get and process descriptors steps.
 * 
 * @param config parameters defined in uvc_config_t
 * @return esp_err_t 
 *      ESP_ERR_INVALID_STATE USB streaming is running, user need to stop streaming first
 *      ESP_ERR_INVALID_ARG Invalid argument
 *      ESP_OK Success
 */
esp_err_t uvc_streaming_config(const uvc_config_t *config);

/**
 * @brief Config UAC streaming with user defined parameters.For normal use, user only need to specify 
 * no-optional parameters, and set optional parameters to 0 (the driver will find the correct value from the device descriptors).
 * For quick start mode, user should specify all parameters manually to skip get and process descriptors steps.
 * 
 * @param config parameters defined in uvc_config_t
 * @return esp_err_t 
 *      ESP_ERR_INVALID_STATE USB streaming is running, user need to stop streaming first
 *      ESP_ERR_INVALID_ARG Invalid argument
 *      ESP_OK Success
 */
esp_err_t uac_streaming_config(const uac_config_t *config);

/**
 * @brief Start usb streaming with pre-configs, usb driver will create internal tasks
 * to handle usb data from stream pipe, and run user's callback after new frame ready.
 * 
 * @return
 *         ESP_ERR_INVALID_STATE streaming not configured, or streaming running already
 *         ESP_FAIL start failed
 *         ESP_OK start succeed
 */
esp_err_t usb_streaming_start(void);

/**
 * @brief Stop current usb streaming, internal tasks will be delete, related resourse will be free
 * 
 * @return 
 *         ESP_ERR_INVALID_STATE streaming not started
 *         ESP_ERR_TIMEOUT stop wait timeout
 *         ESP_OK stop succeed
 */
esp_err_t usb_streaming_stop(void);

/**
 * @brief Wait for USB device connection
 * 
 * @param timeout_ms timeout in ms
 * @return esp_err_t 
 *      ESP_ERR_INVALID_STATE: usb streaming not started
 *      ESP_ERR_TIMEOUT: timeout
 *      ESP_OK: device connected
 */
esp_err_t usb_streaming_connect_wait(size_t timeout_ms);

/**
 * @brief This function registers a callback for USB streaming, please note that only one callback
 *  can be registered, the later registered callback will overwrite the previous one.
 * 
 * @param cb A pointer to a function that will be called when the USB streaming state changes.
 * @param user_ptr user_ptr is a void pointer.
 * 
 * @return esp_err_t
 *    - ESP_OK Success
 *    - ESP_ERR_INVALID_STATE USB streaming is running, callback need register before start
 */
esp_err_t usb_streaming_state_register(state_callback_t *cb, void *user_ptr);

/**
 * @brief Control USB streaming with specific stream and control type
 * @param stream stream type defined in usb_stream_t
 * @param ctrl_type control type defined in stream_ctrl_t
 * @param ctrl_value control value
 * @return
 *         ESP_ERR_INVALID_ARG invalid arg
 *         ESP_ERR_INVALID_STATE driver not configured or not started
 *         ESP_ERR_NOT_SUPPORTED current device not support this control type
 *         ESP_FAIL control failed
 *         ESP_OK succeed
 */
esp_err_t usb_streaming_control(usb_stream_t stream, stream_ctrl_t ctrl_type, void *ctrl_value);

/**
 * @brief Write data to the speaker buffer, will be send out when USB device is ready
 * 
 * @param data The data to be written.
 * @param data_bytes The size of the data to be written.
 * @param timeout_ms The timeout value for writing data to the buffer.
 * 
 * @return
 *         ESP_ERR_INVALID_STATE spk stream not config
 *         ESP_ERR_NOT_FOUND spk interface not found
 *         ESP_ERR_TIMEOUT spk ringbuf full
 *         ESP_OK succeed
 */
esp_err_t uac_spk_streaming_write(void *data, size_t data_bytes, size_t timeout_ms);

/**
 * @brief Read data from internal mic buffer, the actual size will be returned
 * 
 * @param buf pointer to the buffer to store the received data
 * @param buf_size The size of the data buffer.
 * @param data_bytes The actual size read from buffer
 * @param timeout_ms The timeout value for the read operation.
 * 
 * @return
 *         ESP_ERR_INVALID_ARG parameter error
 *         ESP_ERR_INVALID_STATE mic stream not config
 *         ESP_ERR_NOT_FOUND mic interface not found
 *         ESP_TIMEOUT timeout
 *         ESP_OK succeed
 */
esp_err_t uac_mic_streaming_read(void *buf, size_t buf_size, size_t *data_bytes, size_t timeout_ms);

/**
 * @brief Get the audio frame size list of current stream, the list contains audio channel number, bit resolution and samples frequence.
 * IF list_size equals 1 and the samples_frequence equals 0, which means the frequency can be set to any value between samples_frequence_min
 * and samples_frequence_max.
 * 
 * 
 * @param stream the stream type
 * @param frame_list the output frame list, NULL to only get the list size
 * @param list_size frame list size
 * @param cur_index current frame index
 * @return esp_err_t
 *      - ESP_ERR_INVALID_ARG Parameter error
 *      - ESP_ERR_INVALID_STATE USB device not active
 *      - ESP_OK Success
 */
esp_err_t uac_frame_size_list_get(usb_stream_t stream, uac_frame_size_t *frame_list, size_t *list_size, size_t *cur_index);

/**
 * @brief Reset audio channel number, bit resolution and samples frequence, please reset when the streaming
 * in suspend state. The new configs will be effective after streaming resume.
 * 
 * @param stream stream type
 * @param ch_num audio channel numbers
 * @param bit_resolution audio bit resolution
 * @param samples_frequence audio samples frequence
 * @return esp_err_t
 *       - ESP_ERR_INVALID_ARG Parameter error
 *       - ESP_ERR_INVALID_STATE USB device not active
 *       - ESP_ERR_NOT_FOUND frequency not found
 *       - ESP_OK Success
 *       - ESP_FAIL Reset failed
 */
esp_err_t uac_frame_size_reset(usb_stream_t stream, uint8_t ch_num, uint16_t bit_resolution, uint32_t samples_frequence);

/**
 * @brief Get the frame size list of current connected camera
 * 
 * @param frame_list the frame size list, can be NULL if only want to get list size
 * @param list_size the list size
 * @param cur_index current frame index
 * @return esp_err_t 
 *       ESP_ERR_INVALID_ARG parameter error
 *       ESP_ERR_INVALID_STATE uvc stream not config or not active
 *       ESP_OK succeed
 */
esp_err_t uvc_frame_size_list_get(uvc_frame_size_t *frame_list, size_t *list_size, size_t *cur_index);

/**
 * @brief Reset the expected frame size and frame interval, please reset when uvc streaming
 * in suspend state.The new configs will be effective after streaming resume.
 * 
 * Note: frame_width and frame_height can be set to 0 at the same time, which means
 * no change on frame size.
 * 
 * @param frame_width frame width, FRAME_RESOLUTION_ANY means any width
 * @param frame_height frame height, FRAME_RESOLUTION_ANY means any height
 * @param frame_interval frame interval, 0 means no change
 * @return esp_err_t 
 */
esp_err_t uvc_frame_size_reset(uint16_t frame_width, uint16_t frame_height, uint32_t frame_interval);

#ifdef __cplusplus
}
#endif
