/*
 * Copyright (c) 2022-2023 Nordic Semiconductor ASA
 * Copyright 2023 NXP
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#include <zephyr/sys/byteorder.h>

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/pacs.h>
#include <zephyr/bluetooth/audio/bap_lc3_preset.h>
#include <zephyr/bluetooth/audio/tmap.h>

#define SEM_TIMEOUT K_SECONDS(10)
#define PA_SYNC_SKIP         5
#define SYNC_RETRY_COUNT     6 /* similar to retries for connections */
#define INVALID_BROADCAST_ID 0xFFFFFFFF

static bool tmap_bms_found;

static K_SEM_DEFINE(sem_pa_synced, 0U, 1U);
static K_SEM_DEFINE(sem_base_received, 0U, 1U);
static K_SEM_DEFINE(sem_syncable, 0U, 1U);
static K_SEM_DEFINE(sem_pa_sync_lost, 0U, 1U);

static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info,
				struct net_buf_simple *ad);
static void broadcast_scan_timeout(void);

static void broadcast_pa_synced(struct bt_le_per_adv_sync *sync,
				struct bt_le_per_adv_sync_synced_info *info);
static void broadcast_pa_recv(struct bt_le_per_adv_sync *sync,
			      const struct bt_le_per_adv_sync_recv_info *info,
			      struct net_buf_simple *buf);
static void broadcast_pa_terminated(struct bt_le_per_adv_sync *sync,
				    const struct bt_le_per_adv_sync_term_info *info);

static struct bt_le_scan_cb broadcast_scan_cb = {
	.recv = broadcast_scan_recv,
	.timeout = broadcast_scan_timeout
};

static struct bt_le_per_adv_sync_cb broadcast_sync_cb = {
	.synced = broadcast_pa_synced,
	.recv = broadcast_pa_recv,
	.term = broadcast_pa_terminated,
};

static struct bt_bap_broadcast_sink *broadcast_sink;
static uint32_t bcast_id;
static struct bt_le_per_adv_sync *bcast_pa_sync;

static struct bt_bap_stream streams[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT];
struct bt_bap_stream *streams_p[ARRAY_SIZE(streams)];

static const struct bt_audio_codec_cap codec = BT_AUDIO_CODEC_CAP_LC3(
	BT_AUDIO_CODEC_LC3_FREQ_48KHZ, BT_AUDIO_CODEC_LC3_DURATION_10,
	BT_AUDIO_CODEC_LC3_CHAN_COUNT_SUPPORT(1), 40u, 60u, 1u, (BT_AUDIO_CONTEXT_TYPE_MEDIA));

/* Create a mask for the maximum BIS we can sync to using the number of streams
 * we have. We add an additional 1 since the bis indexes start from 1 and not
 * 0.
 */
static const uint32_t bis_index_mask = BIT_MASK(ARRAY_SIZE(streams) + 1U);
static uint32_t bis_index_bitfield;


static void stream_started_cb(struct bt_bap_stream *stream)
{
	printk("Stream %p started\n", stream);
}

static void stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason)
{
	printk("Stream %p stopped with reason 0x%02X\n", stream, reason);
}

static void stream_recv_cb(struct bt_bap_stream *stream,
			   const struct bt_iso_recv_info *info,
			   struct net_buf *buf)
{
	static uint32_t recv_cnt;

	recv_cnt++;
	if ((recv_cnt % 20U) == 0U) {
		printk("Received %u total ISO packets\n", recv_cnt);
	}
}

static struct bt_bap_stream_ops stream_ops = {
	.started = stream_started_cb,
	.stopped = stream_stopped_cb,
	.recv = stream_recv_cb
};

static struct bt_pacs_cap cap = {
	.codec_cap = &codec,
};

static uint16_t interval_to_sync_timeout(uint16_t interval)
{
	uint32_t interval_ms;
	uint16_t timeout;

	/* Ensure that the following calculation does not overflow silently */
	__ASSERT(SYNC_RETRY_COUNT < 10, "SYNC_RETRY_COUNT shall be less than 10");

	/* Add retries and convert to unit in 10's of ms */
	interval_ms = BT_GAP_PER_ADV_INTERVAL_TO_MS(interval);
	timeout = (interval_ms * SYNC_RETRY_COUNT) / 10;

	/* Enforce restraints */
	timeout = CLAMP(timeout, BT_GAP_PER_ADV_MIN_TIMEOUT,
			BT_GAP_PER_ADV_MAX_TIMEOUT);

	return timeout;
}

static void sync_broadcast_pa(const struct bt_le_scan_recv_info *info,
			      uint32_t broadcast_id)
{
	struct bt_le_per_adv_sync_param param;
	int err;

	/* Unregister the callbacks to prevent broadcast_scan_recv to be called again */
	bt_le_scan_cb_unregister(&broadcast_scan_cb);
	err = bt_le_scan_stop();
	if (err != 0) {
		printk("Could not stop scan: %d", err);
	}

	bt_addr_le_copy(&param.addr, info->addr);
	param.options = 0;
	param.sid = info->sid;
	param.skip = PA_SYNC_SKIP;
	param.timeout = interval_to_sync_timeout(info->interval);
	err = bt_le_per_adv_sync_create(&param, &bcast_pa_sync);
	if (err != 0) {
		printk("Could not sync to PA: %d", err);
	} else {
		bcast_id = broadcast_id;
	}
}

static bool scan_check_and_sync_broadcast(struct bt_data *data, void *user_data)
{
	uint32_t *broadcast_id = user_data;
	struct bt_uuid_16 adv_uuid;

	if (data->type != BT_DATA_SVC_DATA16) {
		return true;
	}

	if (!bt_uuid_create(&adv_uuid.uuid, data->data, BT_UUID_SIZE_16)) {
		return true;
	}

	if (!bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_BROADCAST_AUDIO)) {
		*broadcast_id = sys_get_le24(data->data + BT_UUID_SIZE_16);
		return true;
	}

	if (!bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_TMAS)) {
		struct net_buf_simple tmas_svc_data;
		uint16_t uuid_val;
		uint16_t peer_tmap_role = 0;

		net_buf_simple_init_with_data(&tmas_svc_data,
						(void *)data->data,
						data->data_len);
		uuid_val = net_buf_simple_pull_le16(&tmas_svc_data);
		if (tmas_svc_data.len < sizeof(peer_tmap_role)) {
			return false;
		}

		peer_tmap_role = net_buf_simple_pull_le16(&tmas_svc_data);
		if ((peer_tmap_role & BT_TMAP_ROLE_BMS)) {
			printk("Found TMAP BMS\n");
			tmap_bms_found = true;
		}

		return true;
	}

	return true;
}

static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info,
				struct net_buf_simple *ad)
{
	uint32_t broadcast_id;

	tmap_bms_found = false;

	/* We are only interested in non-connectable periodic advertisers */
	if ((info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) ||
	     info->interval == 0) {
		return;
	}

	broadcast_id = INVALID_BROADCAST_ID;
	bt_data_parse(ad, scan_check_and_sync_broadcast, (void *)&broadcast_id);

	if ((broadcast_id != INVALID_BROADCAST_ID) && tmap_bms_found) {
		sync_broadcast_pa(info, broadcast_id);
	}
}

static void broadcast_scan_timeout(void)
{
	printk("Broadcast scan timed out\n");
}

static bool pa_decode_base(struct bt_data *data, void *user_data)
{
	uint32_t base_bis_index_bitfield = 0U;
	struct bt_bap_base base = { 0 };

	if (data->type != BT_DATA_SVC_DATA16) {
		return true;
	}

	if (data->data_len < BT_BAP_BASE_MIN_SIZE) {
		return true;
	}

	if (bt_bap_decode_base(data, &base) != 0) {
		return false;
	}

	for (size_t i = 0U; i < base.subgroup_count; i++) {
		for (size_t j = 0U; j < base.subgroups[i].bis_count; j++) {
			const uint8_t index = base.subgroups[i].bis_data[j].index;

			base_bis_index_bitfield |= BIT(index);
		}
	}

	bis_index_bitfield = base_bis_index_bitfield & bis_index_mask;
	k_sem_give(&sem_base_received);

	return false;
}

static void broadcast_pa_recv(struct bt_le_per_adv_sync *sync,
			      const struct bt_le_per_adv_sync_recv_info *info,
			      struct net_buf_simple *buf)
{
	bt_data_parse(buf, pa_decode_base, NULL);
}

static void syncable_cb(struct bt_bap_broadcast_sink *sink, bool encrypted)
{
	k_sem_give(&sem_syncable);
}

static void base_recv_cb(struct bt_bap_broadcast_sink *sink, const struct bt_bap_base *base)
{
	k_sem_give(&sem_base_received);
}

static struct bt_bap_broadcast_sink_cb broadcast_sink_cbs = {
	.syncable = syncable_cb,
	.base_recv = base_recv_cb,
};

static void broadcast_pa_synced(struct bt_le_per_adv_sync *sync,
				struct bt_le_per_adv_sync_synced_info *info)
{
	if (sync == bcast_pa_sync) {
		printk("PA sync %p synced for broadcast sink with broadcast ID 0x%06X\n", sync,
		       bcast_id);

		k_sem_give(&sem_pa_synced);
	}
}

static void broadcast_pa_terminated(struct bt_le_per_adv_sync *sync,
				    const struct bt_le_per_adv_sync_term_info *info)
{
	if (sync == bcast_pa_sync) {
		printk("PA sync %p lost with reason %u\n", sync, info->reason);
		bcast_pa_sync = NULL;

		k_sem_give(&sem_pa_sync_lost);
	}
}

static int reset(void)
{
	if (broadcast_sink != NULL) {
		int err = bt_bap_broadcast_sink_delete(broadcast_sink);

		if (err) {
			printk("Deleting broadcast sink failed (err %d)\n", err);

			return err;
		}

		broadcast_sink = NULL;
	}

	k_sem_reset(&sem_pa_synced);
	k_sem_reset(&sem_base_received);
	k_sem_reset(&sem_syncable);
	k_sem_reset(&sem_pa_sync_lost);

	return 0;
}

int bap_broadcast_sink_init(void)
{
	int err;

	bt_bap_broadcast_sink_register_cb(&broadcast_sink_cbs);
	bt_le_per_adv_sync_cb_register(&broadcast_sync_cb);

	err = bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &cap);
	if (err) {
		printk("Capability register failed (err %d)\n", err);
		return err;
	}

	for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) {
		streams[i].ops = &stream_ops;
	}

	for (size_t i = 0U; i < ARRAY_SIZE(streams_p); i++) {
		streams_p[i] = &streams[i];
	}

	return 0;
}

int bap_broadcast_sink_run(void)
{
	while (true) {
		int err = reset();

		if (err != 0) {
			printk("Resetting failed: %d - Aborting\n", err);
			return err;
		}

		bt_le_scan_cb_register(&broadcast_scan_cb);
		/* Start scanning */
		err = bt_le_scan_start(BT_LE_SCAN_ACTIVE, NULL);
		if (err) {
			printk("Scan start failed (err %d)\n", err);
			return err;
		}

		/* Wait for PA sync */
		err = k_sem_take(&sem_pa_synced, SEM_TIMEOUT);
		if (err != 0) {
			printk("sem_pa_synced timed out\n");
			return err;
		}
		printk("Broadcast source PA synced, waiting for BASE\n");

		/* Wait for BASE decode */
		err = k_sem_take(&sem_base_received, SEM_TIMEOUT);
		if (err != 0) {
			printk("sem_base_received timed out\n");
			return err;
		}

		/* Create broadcast sink */
		printk("BASE received, creating broadcast sink\n");
		err = bt_bap_broadcast_sink_create(bcast_pa_sync, bcast_id, &broadcast_sink);
		if (err != 0) {
			printk("bt_bap_broadcast_sink_create failed: %d\n", err);
			return err;
		}

		k_sem_take(&sem_syncable, SEM_TIMEOUT);
		if (err != 0) {
			printk("sem_syncable timed out\n");
			return err;
		}

		/* Sync to broadcast source */
		printk("Syncing to broadcast\n");
		err = bt_bap_broadcast_sink_sync(broadcast_sink, bis_index_bitfield,
						streams_p, NULL);
		if (err != 0) {
			printk("Unable to sync to broadcast source: %d\n", err);
			return err;
		}

		k_sem_take(&sem_pa_sync_lost, K_FOREVER);
	}

	return 0;
}
