#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"

#ifdef __cplusplus
extern "C" {
#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_PARAMETER,
} 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();

/**
 * 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 ((bluespy_time_point)0x7FFFFFFFFFFFFFFF)

/**
 * @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
 * @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 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;
    /**
     * 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_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_API void bluespy_capture_options_delete(bluespy_capture_options* opts);

/**
 * @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 a 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 valid until next call, so take a copy
 */
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
 */
BLUESPY_API int bluespy_query_auto(bluespy_id event, const char* query, const char** s, int64_t* i,
                                   bool* b);
/**
 * @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
 * @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,
                                                                      bluespy_time_point 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);
}

#ifdef __cplusplus
}
#endif

#endif
