#ifndef BLUESPY_INCLUDE_GUARD
#define BLUESPY_INCLUDE_GUARD

//!
//! @file bluespy.h
//! @brief blueSPY C API
//!

#ifndef BLUESPY_API
#if defined _WIN32 || defined __CYGWIN__
#define BLUESPY_API __declspec(dllimport)
#else
#define BLUESPY_API __attribute__((visibility("default")))
#endif
#endif

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

#ifdef __cplusplus
#include <utility>
extern "C" {
#endif

#if defined(__has_cpp_attribute)
#define BLUESPY_HASATTR(attr) __has_cpp_attribute(attr)
#elif defined(__has_c_attribute)
#define BLUESPY_HASATTR(attr) __has_c_attribute(attr)
#else
#define BLUESPY_HASATTR(attr) (0)
#endif

#if BLUESPY_HASATTR(deprecated)
#define BLUESPY_DEPRECATED_API [[deprecated]] BLUESPY_API
#else
#define BLUESPY_DEPRECATED_API BLUESPY_API
#endif

typedef enum bluespy_error {
    BLUESPY_NO_ERROR = 0,
    BLUESPY_ERROR_NO_DEVICE,
    BLUESPY_ERROR_LICENCE,
    BLUESPY_ERROR_NO_FILE,
    BLUESPY_ERROR_CAPTURE_NOT_STARTED,
    BLUESPY_ERROR_INVALID_PACKET,
    BLUESPY_ERROR_INVALID_CAPTURE_OPTIONS,
    BLUESPY_ERROR_INVALID_CIS_CONFIG,
    BLUESPY_ERROR_AUDIOPOD_DISABLED,
    BLUESPY_ERROR_AUDIOPOD_OUTPUT_DISABLED,
    BLUESPY_ERROR_INVALID_PARAMETER,
    BLUESPY_ERROR_IUT_NOT_CONNECTED,
    BLUESPY_ERROR_INVALID_TCID,
    BLUESPY_ERROR_TESTCASE_FAILURE,
    BLUESPY_ERROR_ABORTED_TESTCASES,
    BLUESPY_ERROR_NOT_INITIALIZED,
} bluespy_error;

/**
 * @brief Get message for error
 * @param[in] error
 * @return Internal pointer to null terminated string (do not free)
 */
BLUESPY_API const char* bluespy_error_string(bluespy_error error);

/**
 * @brief Any value in [0, 255] can be used for logging.
 *
 * Colour-coding will be applied in the GUI based on the top three bits, and the bottom 5 bits
 * will be available as log.severity_level_subtype
 */
typedef enum bluespy_log_level {
    BLUESPY_LOG_PASS = 0x00,
    BLUESPY_LOG_WARN = 0x20,
    BLUESPY_LOG_INFO = 0x40,
    BLUESPY_LOG_DEBUG = 0x60,
    BLUESPY_LOG_ERROR = 0x80,

} bluespy_log_level;
/**
 * @brief Initialise bluespy, run once at start of program
 */
BLUESPY_API void bluespy_init();

/**
 * @brief Clean up before program exits
 */
BLUESPY_API void bluespy_deinit();

/**
 * @brief Start a GUI instance
 *
 * The GUI exists in a background thread, this function returns immediately
 */
BLUESPY_API void bluespy_start_gui();

/**
 * @brief Connect to Moreph
 * @param[in] serial - Serial number of the Moreph
 * @return Error code
 *
 * Connect by serial number, or first found on USB if serial == -1
 */
BLUESPY_API bluespy_error bluespy_connect(uint32_t serial);

/**
 * @brief Connect to multiple Morephs
 * @param[in] serial_data - Serial numbers of the Morephs
 * @param[in] serial_size - Number of Morephs to connect
 * @return Error code
 *
 * Connect by serial numbers
 */
BLUESPY_API bluespy_error bluespy_connect_multiple(uint32_t* serial_data, uint64_t serial_size);

/**
 * @brief Lists the serial numbers of the connected morephs
 * @param[out] serials - Pointer to array of serial numbers
 * @return Returns the number of connected morephs
 */
BLUESPY_API uint64_t bluespy_morephs_connected(uint32_t** serials);

/**
 * @brief Disconnect from Morephs
 * @return Error code
 */
BLUESPY_API bluespy_error bluespy_disconnect();

/**
 * @brief Reboot Moreph
 * @param[in] serial - Serial number of the Moreph
 * @return Error code
 *
 * Reboot by serial number, or first found on if serial == -1
 * This function will cause the specified Moreph to disconnect - bluespy_connect needs to be called
 * afterwards to talk to the Moreph again
 */
BLUESPY_API bluespy_error bluespy_moreph_reboot(uint32_t serial);

/**
 * The time in nanoseconds since the epoch 1970/01/01 00:00 UTC
 * BLUESPY_TIME_INVALID (0x7fff'ffff'ffff'ffff) represents an invalid time point
 */
typedef int64_t bluespy_time_point;
#define BLUESPY_TIME_INVALID (INT64_MAX)

/**
 * @brief Prints the time in a hh:mm:ss.xxx format
 * @param[in] ts
 * @return Time string
 */
BLUESPY_API const char* bluespy_print_time(bluespy_time_point ts);

/**
 * @brief Adds a log message into the file. Pass BLUESPY_TIME_INVALID to log at the present time.
 * @param[in] level
 * @param[in] message
 * @param[in] ts
 * @return Error code
 */
BLUESPY_API bluespy_error bluespy_add_log_message(bluespy_log_level level, const char* message,
                                                  bluespy_time_point ts);

typedef enum bluespy_logic_rate {
    bluespy_logic_rate_high,
    bluespy_logic_rate_mid,
    bluespy_logic_rate_low

} bluespy_logic_rate;

typedef struct bluespy_multi_moreph_options {
    bool enable_CL;
    bool enable_LE;
    bool enable_wifi;
} bluespy_multi_moreph_options;
typedef enum bluespy_audio_channel {
    BLUESPY_STEREO,
    BLUESPY_MONO_L,
    BLUESPY_MONO_R
} bluespy_audio_channel;

typedef enum bluespy_audio_connect {
    BLUESPY_NOAUDIO,
    BLUESPY_LINE,
    BLUESPY_JACK,
    BLUESPY_HEADSET,
    BLUESPY_COAX,
    BLUESPY_OPTICAL,
    BLUESPY_MIC,
    BLUESPY_I2S
} bluespy_audio_connect;

typedef enum bluespy_audio_bias {
    BLUESPY_BIAS_OFF,
    BLUESPY_BIAS_LOW,
    BLUESPY_BIAS_MID,
    BLUESPY_BIAS_HIG,
    BLUESPY_BIAS_VDD
} bluespy_audio_bias;

typedef struct bluespy_capture_audiopod_options {
    /**
     * Valid sample rates in Hz are:
     * 8'000, 11'025, 16'000, 22'050, 32'000, 44'100, 48'000, 88'200, 96'000, 176'400, 192'000
     */
    uint32_t sample_rate;
    bluespy_audio_channel channels;
    bluespy_audio_connect output;
    bluespy_audio_connect input;
    bluespy_audio_bias bias;
    bool current_probe;
    /**
     * LA_low_voltage and LA_high_voltage must be within the range of 0.0 to 3.3
     */
    double LA_low_voltage;
    double LA_high_voltage;
    /**
     * power_supply_V must be within the range of 0.6 to 5.0
     */
    double power_supply_V;
    double VIO_dV;
    bool AGC;
    bool DRC;
    bool second_I2S_input; // Enable a second I2S input, output must be set to BLUESPY_NO_AUDIO
    double vol_in_left;
    double vol_in_right;
    double vol_out_left;
    double vol_out_right;
} bluespy_capture_audiopod_options;

typedef struct bluespy_capture_i2s_options {
    uint8_t SCLK_line;   // Valid values are [0,15]
    uint8_t WS_line;     // Valid values are [0,15]
    uint8_t SD_line;     // Valid values are [0,15]
    uint8_t n_channels;  // Valid values are [1,16]
    uint8_t bits_per_ch; // Valid values are [1,32]
    bool sample_on_rising_edge;
    bool first_chan_follows_WS_high;
    bool one_sample_delay;
    bool MSB_first;
    bool DSP_mode;
    bool master;
} bluespy_capture_i2s_options;

typedef struct bluespy_capture_options {
    bool enable_CL;
    bool enable_LE;
    bool enable_QHS;
    bool enable_15_4;
    bool enable_wifi;
    bool enable_MHDT_CL;
    bool enable_MHDT_LE;
    bool enable_Dukosi;
    bool enable_Varjo;
    bool enable_Channel_Sounding;
    bool enable_HDT;
    /**
     * Valid spectrum periods in microseconds are:
     * 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000
     *
     * 0 to disable spectrum capture.
     */
    uint16_t spectrum_period;

    uint32_t logic_mask;
    bool logic_use_external_vref;
    bluespy_logic_rate logic_rate;
    /**
     * If multiple Morephs are connected, capture options specific to each device after the first
     * (LE, CL, WiFi).
     */
    bluespy_multi_moreph_options multi_moreph_opts[32];
    bluespy_capture_audiopod_options* audiopod_opts;
    bluespy_capture_i2s_options* i2s_opts[2];

    bool enable_Proprietary_1;
    bool enable_Proprietary_2;
    bool mask_payloads;
    bool disable_HDT_dewhitening;
    bool disable_AGC;
    int16_t maximum_input_signal_dbm; // Only valid when AGC is disabled
} bluespy_capture_options;

/**
 * @brief Create a bluespy_capture_options struct
 * @return New struct
 */
BLUESPY_API bluespy_capture_options* bluespy_capture_options_alloc();

/**
 * @brief Delete a bluespy_capture_options struct
 */
BLUESPY_DEPRECATED_API void bluespy_capture_options_delete(bluespy_capture_options* opts);

/**
 * @brief Delete any of the following bluespy structs:
 * * bluespy_capture_options
 * * bluespy_capture_audiopod_options
 * * bluespy_capture_i2s_options
 * @param[in] obj - Object to delete
 */
BLUESPY_API void bluespy_delete(void* obj);

/**
 * @brief Create a bluespy_capture_audiopod_options struct
 * @return New struct
 */
BLUESPY_API bluespy_capture_audiopod_options* bluespy_capture_audiopod_options_alloc();

/**
 * @brief Create a bluespy_capture_i2s_options struct
 * @return New struct
 */
BLUESPY_API bluespy_capture_i2s_options* bluespy_capture_i2s_options_alloc();

/**
 * @brief Start a capture
 * @param[in] filename - UTF8 filename
 * @param[in] opts - Capture options
 * @return Error code
 */
BLUESPY_API bluespy_error bluespy_capture(const char* filename, bluespy_capture_options* opts);

/**
 * @brief Stop a capture
 * @return Error code
 */
BLUESPY_API bluespy_error bluespy_stop_capture();

/**
 * @brief Load a capture
 * @param[in] filename - UTF8 filename
 * @return Error code
 */
BLUESPY_API bluespy_error bluespy_load_file(const char* filename);

/**
 * @brief Close current file
 * @return Error code
 */
BLUESPY_API bluespy_error bluespy_close_file();

/**
 * @brief Number of baseband packets loaded
 * @return N
 */
BLUESPY_API uint32_t bluespy_packet_count(void);

/**
 * @brief General Identifier for an event, device, connection, or audiostream (queryable)
 *
 * BLUESPY_ID_INVALID means invalid.
 * Only use bluespy_ids returned by the API.
 * Functions accepting a bluespy_id can accept any bluespy_???_id.
 * After bluespy_load_file or bluespy_capture all previous ids are invalid.
 */
typedef uint64_t bluespy_id;

/**
 * @brief Invalid value, used to represent N/A.
 */
#define BLUESPY_ID_INVALID ((bluespy_id)(-1))

/**
 * @brief Identifier for an packet or higher layer event
 *
 * BLUESPY_ID_INVALID means invalid.
 * Only use bluespy_event_ids returned by the API.
 * After bluespy_load_file or bluespy_capture all previous ids are invalid.
 */
typedef bluespy_id bluespy_event_id;

/**
 * @brief Identifier for a device
 *
 * BLUESPY_ID_INVALID means invalid.
 * Device IDs are ordered arbitrarily.
 * Only use bluespy_device_ids returned by the API.
 * After bluespy_load_file or bluespy_capture all previous ids are invalid.
 */
typedef bluespy_id bluespy_device_id;

/**
 * @brief Identifier for a connection
 *
 * BLUESPY_ID_INVALID means invalid.
 * Connection IDs are ordered arbitrarily.
 * Only use bluespy_connection_ids returned by the API.
 * After bluespy_load_file or bluespy_capture all previous ids are invalid.
 */
typedef bluespy_id bluespy_connection_id;

/**
 * @brief Identifier for an audio stream
 *
 * BLUESPY_ID_INVALID means invalid.
 * Audio stream IDs are ordered arbitrarily.
 * Only use bluespy_audiostream_ids returned by the API.
 * After bluespy_load_file or bluespy_capture all previous ids are invalid.
 */
typedef bluespy_id bluespy_audiostream_id;

/**
 * @brief Get a baseband packet
 * @param[in] index - 0 <= index < bluespy_packet_count()
 * @return Event ID
 */
BLUESPY_API bluespy_event_id bluespy_get_baseband(uint32_t index);

/**
 * @brief Get higher layer packets
 * @param[in] event
 * @return Event ID
 */
BLUESPY_API bluespy_event_id bluespy_get_parent(bluespy_event_id event);

/**
 * @brief Get all lower layer packets
 * @param[in] event
 * @param[out] count - size of returned array
 * @return Event ID array
 *
 * Returned child array is only valid until next call, so take a copy
 * Returned child array is null-terminated with BLUESPY_ID_INVALID
 */
BLUESPY_API const bluespy_event_id* bluespy_get_children(bluespy_event_id event, uint32_t* count);

/**
 * @brief Query an event, device, connection, or audio stream.
 * @param[in] event
 * @param[in] query - query string to apply
 * @return String result of a query
 *
 * Returned value is valid until next call, so take a copy
 */
BLUESPY_API const char* bluespy_query(bluespy_id event, const char* query);

/**
 * @brief Query an event, device, connection, or audio stream.
 * @param[in] event
 * @param[in] query - query string to apply
 * @return Integer result of a query
 */
BLUESPY_API int64_t bluespy_query_int(bluespy_id event, const char* query);

/**
 * @brief Query an event, device, connection, or audio stream.
 * @param[in] event
 * @param[in] query - query string to apply
 * @return Bool result of a query
 */
BLUESPY_API bool bluespy_query_bool(bluespy_id event, const char* query);

/**
 * @brief Query an event, device, connection, or audio stream.
 * @param[in] event
 * @param[in] query - query string to apply
 * @param[out] s - return if string
 * @param[out] i - return if int
 * @param[out] b - return if bool
 * @return 0 = None, 1 = str, 2 = int, 3 = bool
 *
 * Returned value is valid until next call, so take a copy
 *
 * Use bluespy_query_get instead
 */
BLUESPY_DEPRECATED_API int bluespy_query_auto(bluespy_id event, const char* query, const char** s,
                                              int64_t* i, bool* b);

typedef struct bluespy_bytes {
    const uint8_t* data;
    size_t len;
} bluespy_bytes;

/**
 * @brief Query an event, device, connection, or audio stream.
 * @param[in] event
 * @param[in] query - query string to apply
 * @return Bytes result of a query
 */
BLUESPY_API bluespy_bytes bluespy_query_bytes(bluespy_id event, const char* query);

typedef enum bluespy_query_type {
    bluespy_query_type_invalid = -1, /* This query can never be used on this kind of ID */
    bluespy_query_type_none = 0,     /* There is no value on this specific ID */
    bluespy_query_type_bool = 1,
    bluespy_query_type_int = 2,
    bluespy_query_type_string = 3,
    bluespy_query_type_bytes = 4,
    bluespy_query_type_double = 5,
    bluespy_query_type_id = 6,
} bluespy_query_type;

typedef struct bluespy_query_value {
    bluespy_query_type type;
    union {
        bool b;
        int64_t i;
        const char* s;
        bluespy_bytes bytes;
        double d;
        bluespy_id id;
    };
} bluespy_query_value;

/**
 * @brief Query an event, device, connection, or audio stream.
 * @param[in] event
 * @param[in] query - query string to apply
 * @return bluespy_query_value
 *
 * Referenced str/bytes data is valid until next call, so take a copy
 */
BLUESPY_API bluespy_query_value bluespy_query_get(bluespy_id id, const char* query);

/**
 * @brief Indentifier for a capture file stored by the API itself.
 * -1 means invalid
 * Only use bluespy_filter_file_ids returned by the API
 * After bluespy_load_file or bluespy_capture all previous ids are invalid
 */
typedef int32_t bluespy_filter_file_id;
#define BLUESPY_FILTER_FILE_ID_INVALID ((bluespy_filter_file_id)(-1))

typedef struct bluespy_filter_file_options {
    bluespy_time_point range_start;
    bool keep_spectrum;
    bool keep_logic;
    bool keep_uart;
    bool keep_i2s_and_audiopod;
} bluespy_filter_file_options;

/**
 * @brief Create a bluespy_filter_file_options struct
 * @return New struct
 */
BLUESPY_API bluespy_filter_file_options* bluespy_filter_file_options_alloc();

/**
 * @brief Delete a bluespy_filter_file_options struct
 */
BLUESPY_API void bluespy_filter_file_options_delete(bluespy_filter_file_options* opts);

/**
 * @brief Get file name from ID
 * @param[in] id - File ID
 * @return file name
 */
BLUESPY_API const char* bluespy_get_filter_file_name(bluespy_filter_file_id id);

/**
 * @brief Create a capture file
 * @param[in] filename - File name
 * @param[in] opts - Filter File options
 * @return ID of file
 */
BLUESPY_API bluespy_filter_file_id bluespy_create_filter_file(const char* filename,
                                                              bluespy_filter_file_options* opts);

/**
 * @brief Adds an event to a capture file
 * @param[in] file_id - File ID
 * @param[in] event_id - Event ID
 * @return Error code
 */
BLUESPY_API bluespy_error bluespy_add_to_filter_file(bluespy_filter_file_id file_id,
                                                     bluespy_event_id event_id);

/**
 * @brief Close a capture file
 * @param[in] file_id - File ID
 * @return Error code
 */
BLUESPY_API bluespy_error bluespy_close_filter_file(bluespy_filter_file_id file_id);

/**
 * @brief Get the logic state at a given time
 * @param[in] ts - timestamp
 * @return Logic state mask
 */
BLUESPY_API uint32_t bluespy_get_logic_at_time(bluespy_time_point ts);

typedef struct bluespy_logic_change {
    uint32_t state;          // New logic state - 32-bit mask of the new logic state
    uint32_t change_mask;    // Logic changed - 32-bit mask of lines which changed
    bluespy_time_point time; // The time of the logic change
} bluespy_logic_change;

/**
 * @brief Get the next logic state after a given time
 * @param[in] ts - timestamp
 * @param[in] mask - logic mask for which lines to look at
 * @return Logic state mask and time of the next change
 */
BLUESPY_API bluespy_logic_change bluespy_get_next_logic_change(bluespy_time_point ts,
                                                               uint32_t mask);

/**
 * @brief Wait until any of the logic lines within the mask changes, and returns the result
 * @param[in] mask - logic mask for which lines to look at
 * @param[in] timeout - maximum time to wait in ns
 * @param[in] start_ts - time to wait from. -1 = wait from present
 * @return Struct containing the new logic state, the logic lines which changed, and the time the
 * change occured. Timeout - returns {0,0,current_time}
 */
BLUESPY_API bluespy_logic_change bluespy_wait_until_next_logic_change(uint32_t mask,
                                                                      int64_t timeout,
                                                                      bluespy_time_point start_ts);

/**
 * @brief Add a link key for decryption
 * @param[in] key - 16 bytes of binary
 * @param[in] addr0 - Bluetooth address of first device, set to 0 if unknown
 * @param[in] addr1 - Bluetooth address of second device, set to 0 if unknown
 * @return Error code
 *
 * The link key should be added before loading/capturing, or you should load again afterwards.
 */
BLUESPY_API bluespy_error bluespy_add_link_key(const unsigned char* key, uint64_t addr0,
                                               uint64_t addr1);

/**
 * @brief Add an IRK
 * @param[in] key - 16 bytes of binary
 * @param[in] addr - Array of relevant Bluetooth addresses, can be empty. Set bit 48 == 1 for
 * Random, == 0 for Public
 * @param[in] n_addresses - Length of array
 *
 * The key should be added before loading/capturing, or you should load again afterwards.
 */
BLUESPY_API void bluespy_add_IRK(const unsigned char* key, uint64_t* addr, uint64_t n_addresses);

/**
 * @brief Obtains the Device ID from an address
 * @param[in] addr - 6 bytes representing the address
 * @return Device ID. BLUESPY_ID_INVALID = Unknown or Invalid address
 */
BLUESPY_API bluespy_device_id bluespy_get_device_id(const char* addr);

typedef struct bluespy_connection_id_span {
    bluespy_connection_id* data;
    uint64_t size;
} bluespy_connection_id_span;

/**
 * @brief Obtains the IDs of the connections which a device is creating
 * @param[in] dev_id - Device ID
 * @return An array of Connection IDs
 */
BLUESPY_API bluespy_connection_id_span bluespy_get_connections(bluespy_device_id dev_id);

typedef struct bluespy_audiostream_id_span {
    bluespy_audiostream_id* data;
    uint64_t size;
} bluespy_audiostream_id_span;

/**
 * @brief Obtains the IDs of the audio streams which a device is creating or which are part of a
 * connection.
 * @param[in] id - Bluespy ID
 * @return An array of audio stream IDs
 *
 * If id is device_id: Streams which a device is creating.\n
 * If id is connection_id: Streams which are part of a connection.\n
 * If id is BLUESPY_ID_INVALID: Streams not associated with any device, e.g. audiopod streams.
 */
BLUESPY_API bluespy_audiostream_id_span bluespy_get_audiostreams(bluespy_id id);

/**
 * @brief Get the next Device ID after 'id'. Used to iterate over all devices.
 * @param[in] id - Device ID
 * @return Device ID
 *
 * id = BLUESPY_ID_INVALID returns the first device id.
 * Returns BLUESPY_ID_INVALID if there are no devices remaining.
 */
BLUESPY_API bluespy_device_id bluespy_get_next_device_id(bluespy_device_id id);

/**
 * @brief Get the first out of all available Device IDs. Used to iterate over all devices.
 * @return Device ID
 *
 */
inline bluespy_device_id bluespy_get_first_device_id() {
    return bluespy_get_next_device_id(BLUESPY_ID_INVALID);
}

/**
 * @brief Get the next Connection ID after 'id'. Used to iterate over all connections.
 * @param[in] id - Connection ID
 * @return Connection ID
 *
 * id = BLUESPY_ID_INVALID returns the first connection id.
 * Returns BLUESPY_ID_INVALID if there are no connections remaining.
 */
BLUESPY_API bluespy_connection_id bluespy_get_next_connection_id(bluespy_connection_id id);

/**
 * @brief Get the first out of all available Connection IDs. Used to iterate over all connections.
 * @return Connection ID
 */
inline bluespy_connection_id bluespy_get_first_connection_id() {
    return bluespy_get_next_connection_id(BLUESPY_ID_INVALID);
}

/**
 * @brief Get the next audio stream ID after 'id'. Used to iterate over all audio streams.
 * @param[in] id - Audio stream ID
 * @return Audio stream ID
 *
 * id = BLUESPY_ID_INVALID returns the first audio stream id.
 * Returns BLUESPY_ID_INVALID if there are no audio streams remaining.
 */
BLUESPY_API bluespy_audiostream_id bluespy_get_next_audiostream_id(bluespy_audiostream_id id);

/**
 * @brief Get the first out of all available audio stream IDs. Used to iterate over all audio
 * streams.
 * @return Audio stream ID
 *
 */
inline bluespy_audiostream_id bluespy_get_first_audiostream_id() {
    return bluespy_get_next_audiostream_id(BLUESPY_ID_INVALID);
}

typedef enum bluespy_event_types {
    bluespy_event_bt_baseband,
    bluespy_event_custom,
    bluespy_event_proprietary_1,
    bluespy_event_proprietary_2,
} bluespy_event_types;
/**
 * @brief Sets the callback for bluespy events
 * @param[in] types - The types of events that the callback should be called for
 * @param[in] callback - The callback function
 *
 * Use this function to add a callback for processing custom protocols and other propietary features
 */
BLUESPY_API void bluespy_register_event_callback(bluespy_event_types types,
                                                 void (*callback)(bluespy_event_id));

typedef struct bluespy_custom_query {
    const char* query;
    const char* pretty_name;
    const char* description;
} bluespy_custom_query;

typedef void (*bluespy_cleanup_t)(void*);
/**
 * @brief Allocates memory that bluespy will automatically clean up
 * @param[in] bytes - The number of bytes to allocate
 * @param[in] cleanup - Optional function to be called when the allocation is later deallocated.
 * Usually NULL
 *
 * This function should be primarily used in custom decoders to allocate memory for new events
 */
BLUESPY_API void* bluespy_allocate(size_t bytes, bluespy_cleanup_t cleanup);

typedef enum bluespy_custom_event_layer {
    bluespy_custom_event_layer_automatic, // One layer above its children
    bluespy_custom_event_layer_logical,
    bluespy_custom_event_layer_transaction,
    bluespy_custom_event_layer_application,
} bluespy_custom_event_layer;

typedef struct bluespy_custom_event {
    bluespy_event_id* children;
    unsigned int n_children;
    bluespy_query_value (*query)(const struct bluespy_custom_event* self, const char* query_str,
                                 bool prefer_string);
    bluespy_custom_event_layer layer;
} bluespy_custom_event;

/**
 * @brief Adds a custom event to the bluespy event log
 *
 * Used to add custom events for things like custom protocols
 */
BLUESPY_API bluespy_error bluespy_add_event(bluespy_custom_event* event);

typedef enum bluespy_latency_status {
    BLUESPY_OK,
    BLUESPY_ZEROS,
    BLUESPY_NOT_ENOUGH_DATA,
    BLUESPY_ENERGY_THRESHOLD,
    BLUESPY_AMBIG_PEAK,
    BLUESPY_PERIODIC,
    BLUESPY_OTHER_ERROR
} bluespy_latency_status;

/**
 * @brief Get message for latency status
 * @param[in] status
 * @return Internal pointer to null terminated string (do not free)
 */
BLUESPY_API const char* bluespy_latency_status_string(bluespy_latency_status status);

typedef struct bluespy_latency_result {
    int64_t time_difference_ns;
    int64_t time_difference_min_ns;
    int64_t time_difference_max_ns;
    bluespy_time_point measurement_time;
    bluespy_latency_status status;
    double total_energy;
    double peak_ratio;
    bool three_measurements_expected;
} bluespy_latency_result;

typedef struct bluespy_audio_channel_t {
    bluespy_audiostream_id ID;
    uint8_t channel_index;
} bluespy_audio_channel_t;

/**
 * @brief Measure the latency between two audio streams
 * @param[in] channel0 - Audio Channel
 * @param[in] channel1 - Audio Channel
 * @param[in] include_pres_delay - Whether to include presentation delay
 * @param[in] ts - Time to measure at
 * @return latency result
 */
BLUESPY_API bluespy_latency_result bluespy_measure_latency(bluespy_audio_channel_t channel0,
                                                           bluespy_audio_channel_t channel1,
                                                           bool include_pres_delay,
                                                           bluespy_time_point ts);

typedef struct bluespy_cis_lc3_config {
    uint64_t codec_frames_per_SDU;
    uint64_t presentation_delay_us;
    uint32_t octets_per_codec_frame;
    uint32_t frame_duration_us;
    uint32_t sampling_frequency_Hz;
    uint32_t audio_channel_allocation;
} bluespy_cis_lc3_config;

/**
 * @brief Set the configuration of a CIS stream
 * @param[in] id - Audio stream ID
 * @param[in] conf - Pointer to CIS config
 * @return error
 */
BLUESPY_API bluespy_error bluespy_set_cis_lc3_config(bluespy_audiostream_id id,
                                                     bluespy_cis_lc3_config* conf);
/**
 * @brief Play audio file to Audiopod File Playback
 * @param[in] filename - Audio file
 * @param[in] loop - Whether to loop the file
 * @return error
 */
BLUESPY_API bluespy_error bluespy_play_to_audiopod_output(const char* filename, bool loop);

/**
 * @brief Stop the audio file in Audiopod File Playback
 * @return error
 */
BLUESPY_API bluespy_error bluespy_stop_audio();

/**
 * @brief Mark an encryption key as used. Most useful in custom decoder where the custom decoder
 * performs decryption. This will highlight the key green in the GUI and cause it to be included in
 * the capture.
 */
BLUESPY_API bluespy_error bluespy_mark_key_used(const char* key, size_t len);

typedef struct bluespy_key {
    size_t length;
    uint8_t* key;
    const char* label;
} bluespy_key;

/**
 * @brief List keys from the security tab.
 * This functions performs allocation.
 * Later, call bluespy_free_keys to free the memory.
 *
 * e.g.
 * bluspy_key* keys;
 * size_t count;
 * bluespy_list_keys(&keys, &count);
 * ...
 * bluespy_free_keys(keys, count);
 */
BLUESPY_API bluespy_error bluespy_list_keys(bluespy_key** keys, size_t* count);

/**
 * @brief Free the keys returned by bluespy_list_keys
 */
BLUESPY_API bluespy_error bluespy_free_keys(bluespy_key* keys, size_t count);

/*------------------------------------------------------------------------------
 * Bluespy Codec Interface Definitions
 *----------------------------------------------------------------------------*/

/**
 * @brief Defines the API version for blueSPY Audio Codec Interface.
 *
 * Codec shared libraries must set bluespy_audio_codec_lib_info::api_version to this
 * value when returning from their init() function. This allows the host to verify
 * compatibility between itself and the loaded codec library.
 *
 * API version history:
 * - Version 1: Initial release (supports AVDTP/A2DP, CIS, and BIS containers)
 */
#define BLUESPY_AUDIO_API_VERSION 1

/**
 * @brief Library-level information describing a codec implementation.
 *
 * Each codec shared library (e.g. AAC, aptX) must expose an 'init()' function returning
 * an instance of this structure. It identifies the codec library at runtime and provides
 * a library version for compatibility checking.
 */
typedef struct bluespy_audio_codec_lib_info {
    int api_version;        // <- Must be set to BLUESPY_AUDIO_API_VERSION
    const char* codec_name; // <- Human-readable codec name (e.g., "AAC", "aptX", "LDAC")
} bluespy_audio_codec_lib_info;

/**
 * @brief Signature of the codec library initialisation function.
 *
 * Each codec library must export a C-linkage symbol named 'init()' matching this signature.
 * It provides information about the library and its capabilities.
 *
 * @return A structure containing the codec libray information.
 */
typedef bluespy_audio_codec_lib_info (*bluespy_audio_codec_lib_init_t)(void);

/**
 * @brief Enumeration of transport/container types used to deliver codec data.
 *
 * This is used to differentiate between Classic (AVDTP/A2DP) and LE Audio (LEA) transports.
 */
typedef enum bluespy_codec_container {
    BLUESPY_CODEC_AVDTP,
    BLUESPY_CODEC_CIS,
    BLUESPY_CODEC_BIS
} bluespy_codec_container;

/**
 * @brief Describes the codec configuration for a given audio stream.
 *
 * The configuration data pointed to by @ref config points to container-specific
 * payloads as obtained from signalling. The meaning depends on @ref container:
 *
 * - **BLUESPY_CODEC_AVDTP**: @ref config points to a full
 *   AVDTP_Service_Capabilities_Media_Codec_t structure (including its entire
 *   Media_Codec_Specific_Information block).
 *
 * - **BLUESPY_CODEC_CIS**: @ref config points to the Codec_Specific_Configuration
 *   part of the ASE Control Point (typically from a Set Configuration command).
 *
 * - **BLUESPY_CODEC_BIS**: @ref config points to the ACAD + AdvData fields of
 *   the Extended Advertising PDU payload. ACAD (Additional Controller Advertising Data)
 *   includes the BIG Info block. AdvData (Advertising Data) includes the BASE block.
 *
 * In all cases, @ref config_len bytes are valid.
 *
 * @note Pointers are transient: valid only during the new_codec_stream() call.
 *       Implementations must copy data they need.
 */
typedef struct bluespy_audio_codec_info {
    bluespy_codec_container container;
    const void* config;  // <- pointer to container-specific data block
    uint32_t config_len; // <- length of container-specific block
} bluespy_audio_codec_info;

/**
 * @brief Enum describing the data type of decoded audio samples.
 */
typedef enum bluespy_audio_sample_format {
    /** 16-bit Signed Integer, Little Endian (Standard PCM) */
    BLUESPY_AUDIO_FORMAT_S16_LE = 0,
} bluespy_audio_sample_format;

/**
 * @brief Describes the decoded audio format produced by a codec.
 *
 * Each codec must report its decoded sample format as part of its
 * initialisation return structure.
 */
typedef struct bluespy_audio_codec_decoded_format {
    uint32_t sample_rate; // <- Sample rate in Hz
    uint8_t n_channels;   // Number of audio channels (1=mono, 2=stereo)

    /* Format of the PCM samples. Currently only S16_LE (signed 16-bit, little endian) is supported.
     */
    bluespy_audio_sample_format sample_format;
} bluespy_audio_codec_decoded_format;

/**
 * @brief Function for delivering decoded PCM audio. Should only be called from inside
 *        the codec_decode function.
 *
 * @param pcm_data         Pointer to decoded PCM data (S16 LE interleaved).
 * @param pcm_data_len     Number of bytes at @p pcm_data.
 * @param source_id        Identifier for the source SDU / packet, used by the host
 *                         to correlate decoded audio with captured packets. Usually
 *                         passed from codec_decode.
 * @param missing_samples  Number of samples (per channel) lost immediately before this packet.
 *                         Pass 0 if continuous or unknown.
 */
BLUESPY_API void bluespy_add_audio(const uint8_t* pcm_data, uint32_t pcm_data_len,
                                   bluespy_event_id source_id, uint32_t missing_samples);

/**
 * @brief Function pointer type for the codec_decode function.
 *
 * Called once per encoded packet or frame to produce decoded PCM audio data.
 *
 * @param context Opaque handle to the codec instance state. This is the
 *                value returned in @ref bluespy_audio_codec_init_ret::context_handle
 *                during initialization.
 * @param payload Pointer to the encoded audio packet or frame.
 *
 * **For Classic (A2DP/AVDTP):** This buffer represents the contents of a single
 *   L2CAP Service Data Unit (SDU) carrying an AVDTP media packet. This typically contains:
 * - RTP header (12 bytes + 4 bytes per CSRC)
 * - Optional codec-specific headers (e.g., LDAC sync byte 0xAA)
 * - One or more codec frames
 *
 * **For LE Audio (CIS/BIS):**
 * Payload is one reconstructed ISOAL SDU.
 *
 * @param payload_len Length of the encoded data in bytes.
 * @param event_id Capture event identifier for this SDU.
 * @param sequence_number 64-bit monotonic sequence assigned by the host.
 */
typedef void (*bluespy_audio_decode_t)(uintptr_t context, const uint8_t* payload,
                                       const uint32_t payload_len, bluespy_event_id event_id,
                                       uint64_t sequence_number);

/**
 * @brief Function-pointer type for the codec de-initialisation function.
 *
 * Called when an audio stream ends so the codec can release state and resources.
 *
 * @param context Opaque handle to the codec instance state to be freed.
 *               This pointer should be cast back to the internal state struct and freed.
 */
typedef void (*bluespy_audio_codec_deinit_t)(uintptr_t context);

/**
 * @brief Collection of function pointers exposed by a codec implementation.
 *
 * Each successfully initialised codec must provide at least a
 * @ref decode and a @ref deinit function. Both pointers must be non-NULL.
 */
typedef struct bluespy_audio_codec_funcs {
    bluespy_audio_decode_t decode;
    bluespy_audio_codec_deinit_t deinit;
} bluespy_audio_codec_funcs;

/**
 * @brief Return structure for the stream initialisation function.
 *
 * Returned by @ref new_codec_stream() to provide the negotiated PCM
 * output format, function table, and any error information.
 */
typedef struct bluespy_audio_codec_init_ret {
    int error; // <- 0 = success; < 0 = failure.
    bluespy_audio_codec_decoded_format format;
    bluespy_audio_codec_funcs fns;
    /**
     * @brief Opaque handle to the plugin-allocated state.
     *        The host will pass this value back to decode() and deinit().
     */
    uintptr_t context_handle;
} bluespy_audio_codec_init_ret;

/**
 * @brief Function pointer type for a codec's stream creation routine.
 *
 * The host calls this when an audio stream is detected and needs decoding.
 *
 * The codec should:
 * 1. Verify that it supports the provided configuration.
 * 2. Allocate and initialise decoder state for the stream.
 * 3. Return the output format and function pointers.
 *
 * Each codec must export a C-linkage symbol named `new_codec_stream`
 * matching this signature:
 * @code
 * extern "C"
 * bluespy_audio_codec_init_ret new_codec_stream(
 *     bluespy_audiostream_id stream_id,
 *     const bluespy_audio_codec_info* info);
 * @endcode
 *
 * @param stream_id Gives an ID that can be passed to query functions, if information about
 *                  the stream is required that's not included in the codec info.
 * @param info Pointer to container-specific configuration data. The
 *             memory is valid only during this call; codecs must copy
 *             anything they need.
 *
 * @return On success (`error == 0`) the @ref format and @ref fns fields
 *         must contain valid values.
 *         On failure (`error < 0`) the codec must release any allocations
 *         and return immediately so that other codec implementations can
 *         be offered the same configuration.
 *
 * @note Codecs that do not recognise or support the configuration should
 *       return `error = -1` to allow fallback to alternative decoders.
 */
typedef bluespy_audio_codec_init_ret (*bluespy_audio_codec_init_t)(
    bluespy_audiostream_id stream_id, const bluespy_audio_codec_info* info);

/**
 * @brief Enable the custom decoding libraries
 */
BLUESPY_API bluespy_error bluespy_enable_custom_decoders();

/**
 * @brief Disable the custom decoding libraries
 */
BLUESPY_API bluespy_error bluespy_disable_custom_decoders();

typedef struct bluespy_custom_decoder_status_t {
    bool enabled;
    size_t count;
    const char** names;
} bluespy_custom_decoder_status_t;

BLUESPY_API bluespy_custom_decoder_status_t bluespy_custom_decoders_status();

typedef struct bluespy_spectrum_data {
    int8_t (*data)[79];
    size_t count;
    uint32_t interval; // Nanoseconds
} bluespy_spectrum_data;
/**
 * @brief Get the spectrum values
 * Set start = 0 for start of capture and end = INT64_MAX for end of capture
 */
BLUESPY_API bluespy_spectrum_data bluespy_get_spectrum(bluespy_time_point start,
                                                       bluespy_time_point end);

/**
 * @brief Export logic data to a file
 */
BLUESPY_API bluespy_error bluespy_export_logic(const char* filename);

typedef enum bluespy_audiopod_pin_current {
    bluespy_1mA = 1,
    bluespy_10mA = 10,
    bluespy_100mA = 100,
    bluespy_1A = 1000,
} bluespy_audiopod_pin_current;

/**
 * @brief Set this based on the pins you are using.
 *
 * @param current The current scale of the pins you are using in mA. Should be 1, 10, 100, or 1000.
 * The value is only used to return correctly scale mA values in bluespy_audiopod_current
 */
BLUESPY_API bluespy_error bluespy_set_pin_current(bluespy_audiopod_pin_current current);

typedef struct bluespy_audiopod_current_data {
    double* data; // In mA
    size_t count;
} bluespy_audiopod_current_data;
/**
 * @brief Get the Audiopod current values in mA.
 *
 * Set start = 0 for start of capture and end = INT64_MAX for end of capture
 * Call bluespy_set_pin_current first to ensure the values returned by this function are correctly
 * scaled
 * Sampling rate is 125kHz
 */
BLUESPY_API bluespy_audiopod_current_data bluespy_audiopod_current(bluespy_time_point start,
                                                                   bluespy_time_point end);

/**
 * @brief returns the blueSPY version as an integer.
 * e.g. 260328 for 26.03.28
 */
BLUESPY_API uint32_t bluespy_version();

/**
 * @brief Control loading/saving of keys in local storage.
 * @param[in] enable - Enable loading/saving of keys; default value is enabled if this function has
 * not been called.
 *
 * Encryption keys and IRKs are saved to your local storage and reused when loading/capturing.
 * To disable this behaviour (e.g. for repeatable behaviour in a test system), use this function to
 * disable loading and saving of keys.
 *
 * Calling bluespy_enable_key_storage(false) removes all keys from blueSPY's list of keys.
 */
BLUESPY_API void bluespy_enable_key_storage(bool enable);

#ifdef __cplusplus
}

namespace bluespy {
template <class T, class... Args>
T* allocate(Args&&... args) {
    T* p = static_cast<T*>(bluespy_allocate(sizeof(T), [](void* p) { ((T*)p)->~T(); }));
    new (p) T(std::forward<Args>(args)...);
    return p;
}

inline bluespy_error connect(uint32_t serial = -1) { return bluespy_connect(serial); }
} // namespace bluespy

#endif

#endif
