/* SPDX-FileCopyrightText: 2024 Google LLC */
/* SPDX-License-Identifier: Apache-2.0 */

#pragma once

#include "applib/bluetooth/ble_client.h"
#include "util/attributes.h"

#include "gap_le_task.h"

#include <bluetooth/mtu.h>

struct GAPLEConnection;

#define MAX_ATT_WRITE_PAYLOAD_SIZE (ATT_MAX_SUPPORTED_MTU - 3)
#define GATT_CLIENT_SUBSCRIPTIONS_BUFFER_SIZE \
  ((MAX_ATT_WRITE_PAYLOAD_SIZE + sizeof(GATTBufferedNotificationHeader)) * 4)

//! Data structure representing a subscription of a specific client for
//! noticications or indications of a GATT characteristic for a specific
//! client (GAPLEClientApp or GAPLEClientKernel). The GAPLEConnection struct has
//! the head for each BLE connection.
typedef struct {
  ListNode node;

  //! The characteristic to which the client is subscribed
  BLECharacteristic characteristic;

  //! Cached ATT handle of the characteristic
  uint16_t att_handle;

  //! Array of subscription types for each client
  BLESubscription subscriptions[GAPLEClientNum];

  //! For each client, whether it is waiting for an event to confirm the subscription
  bool pending_confirmation[GAPLEClientNum];
} GATTClientSubscriptionNode;

//! Data structure representing a serialized GATT notification header.
typedef struct PACKED {
  BLECharacteristic characteristic;
  uint16_t value_length;
  uint8_t value[];
} GATTBufferedNotificationHeader;

BTErrno gatt_client_subscriptions_subscribe(BLECharacteristic characteristic,
                                            BLESubscription subscription_type,
                                            GAPLEClient client);

//! Gets the length of the next notification in the buffer that was received.
//! @param[out] header_out The header of the notification, containing the value length and the
//! characteristic reference.
//! @return True if there is a notification in the buffer, false if not
bool gatt_client_subscriptions_get_notification_header(GAPLEClient client,
                                                       GATTBufferedNotificationHeader *header_out);

//! Copies the data of the next notification and marks it as "consumed".
//! The client *MUST* keep on calling gatt_client_subscriptions_consume_notification() in a loop
//! until 0 is returned.
//! @see gatt_client_subscriptions_get_notification_value_length() to get the length of the next
//! notification.
//! @param[in,out] value_length_in_out Cannot be NULL. In: the size of the value_out buffer.
//! Out: the number of bytes copied into the value_out buffer.
//! @param[out] has_more_out Cannot be NULL. Will be set to true if there are more notifications
//! in the buffer, or to false if there are no more notifiations in the buffer.
//! @return The length of the next notification's payload, if there is any (has_more_out is true),
//! undefined otherwise.
uint16_t gatt_client_subscriptions_consume_notification(BLECharacteristic *characteristic_ref_out,
                                                        uint8_t *value_out,
                                                        uint16_t *value_length_in_out,
                                                        GAPLEClient client, bool *has_more_out);

//! Indicates that the client wants to pause processing notifications and yield to keep the system
//! responsive. This puts a new event on the queue so the client can continue processing later on.
void gatt_client_subscriptions_reschedule(GAPLEClient c);

//! Unsubscribes all subscriptions associated with the client. This function
//! assumes the connection is still alive and will write to the CCCD to
//! "unsubscribe" from the remote as well, if the specified client was the
//! last one to be registered for a particular characteristic.
void gatt_client_subscriptions_cleanup_by_client(GAPLEClient client);

//! Frees the GATTClientSubscriptionNode nodes that might have been associated
//! with the connection as result of gatt_client_subscriptions_subscribe calls.
//! @param should_unsubscribe If true, the current subscriptions will be unsubscribed before
//! cleanup. If false, the current subscriptions will not be unsubscribed (this is useful when
//! the connection is already severed.) No unsubscription events will be emitted regardless of the
//! value of this argument.
void gatt_client_subscriptions_cleanup_by_connection(struct GAPLEConnection *connection,
                                                     bool should_unsubscribe);

//! Called once at boot.
void gatt_client_subscription_boot(void);
