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

#include "zephyr/bluetooth/audio/audio.h"
#include "zephyr/bluetooth/bluetooth.h"
#include "zephyr/bluetooth/conn.h"
#include "zephyr/bluetooth/uuid.h"
#include "zephyr/kernel.h"
#include "zephyr/logging/log.h"
#include "zephyr/net_buf.h"

#include "broadcast_source.h"
#include "bt_connection_manager.h"
#include "bt_mgmt.h"
#include "led_assignments.h"
#include "led.h"
#include "macros_common.h"
#include "zephyr/sys/util.h"

LOG_MODULE_REGISTER(bt_connection_manager, CONFIG_BROADCAST_SOURCE_LOG_LEVEL);
BUILD_ASSERT(BROADCAST_NAME_MAX_LENGTH == BROADCAST_SOURCE_ADV_NAME_MAX,
			"Maximum broadcast name length mismatch");

struct bt_le_ext_adv *ext_adv[NUM_CHANNELS];
size_t ext_adv_buf_cnt[NUM_CHANNELS] = {0, 0};
size_t per_adv_buf_cnt[NUM_CHANNELS] = {0, 0};

/* Buffer for the UUIDs. */
#define EXT_ADV_UUID_BUF_SIZE (128)
NET_BUF_SIMPLE_DEFINE_STATIC(uuid_data, EXT_ADV_UUID_BUF_SIZE);
NET_BUF_SIMPLE_DEFINE_STATIC(uuid_data2, EXT_ADV_UUID_BUF_SIZE);

/* Buffer for periodic advertising BASE data. */
NET_BUF_SIMPLE_DEFINE_STATIC(base_data, 128);
NET_BUF_SIMPLE_DEFINE_STATIC(base_data2, 128);

/* Extended advertising buffer. */
static struct bt_data ext_adv_buf[CONFIG_BT_ISO_MAX_BIG][CONFIG_EXT_ADV_BUF_MAX];

/* Periodic advertising buffer. */
static struct bt_data per_adv_buf[CONFIG_BT_ISO_MAX_BIG];


#if (CONFIG_AURACAST)
/* Total size of the PBA buffer includes the 16-bit UUID, 8-bit features and the
 * meta data size.
 */
#define BROADCAST_SRC_PBA_BUF_SIZE                                                                 \
	(BROADCAST_SOURCE_PBA_HEADER_SIZE + CONFIG_BT_AUDIO_BROADCAST_PBA_METADATA_SIZE)

/* Number of metadata items that can be assigned. */
#define BROADCAST_SOURCE_PBA_METADATA_VACANT                                                       \
	(CONFIG_BT_AUDIO_BROADCAST_PBA_METADATA_SIZE / (sizeof(struct bt_data)))

/* Make sure pba_buf is large enough for a 16bit UUID and meta data
 * (any addition to pba_buf requires an increase of this value)
 */
uint8_t pba_data[CONFIG_BT_ISO_MAX_BIG][BROADCAST_SRC_PBA_BUF_SIZE];

/**
 * @brief	Broadcast source static extended advertising data.
 */
static struct broadcast_source_ext_adv_data ext_adv_data[] = {
	{.uuid_buf = &uuid_data,
	 .pba_metadata_vacant_cnt = BROADCAST_SOURCE_PBA_METADATA_VACANT,
	 .pba_buf = pba_data[0]},
	{.uuid_buf = &uuid_data2,
	 .pba_metadata_vacant_cnt = BROADCAST_SOURCE_PBA_METADATA_VACANT,
	 .pba_buf = pba_data[1]}};
#else
/**
 * @brief	Broadcast source static extended advertising data.
 */
static struct broadcast_source_ext_adv_data ext_adv_data[NUM_CHANNELS] = {{.uuid_buf = &uuid_data},
							      {.uuid_buf = &uuid_data2}};
#endif /* (CONFIG_AURACAST) */

/**
 * @brief	Broadcast source static periodic advertising data.
 */
static struct broadcast_source_per_adv_data per_adv_data[NUM_CHANNELS] = {{.base_buf = &base_data},
							      {.base_buf = &base_data2}};


static struct broadcast_source_big broadcast_param[NUM_CHANNELS];

// TODO: check initial bt has initialised
static bool bt_enabled[NUM_CHANNELS] = {true, true};
static char broadcast_name[NUM_CHANNELS][BROADCAST_NAME_MAX_LENGTH];

struct k_work_big {  // for submitting BIG related tasks to work queues
	struct k_work work;
	uint8_t big_index;
};

static void enable_bt_work_handler(struct k_work *work);
static void disable_bt_work_handler(struct k_work *work);
static void change_broadcast_name_work_handler(struct k_work *work);

static int ext_adv_populate(uint8_t big_index, struct broadcast_source_ext_adv_data *ext_adv_data,
			    struct bt_data *ext_adv_buf, size_t ext_adv_buf_size,
			    size_t *ext_adv_count);

static int per_adv_populate(uint8_t big_index, struct broadcast_source_per_adv_data *pre_adv_data,
			    struct bt_data *per_adv_buf, size_t per_adv_buf_size,
			    size_t *per_adv_count);

int initialise_broadcast(uint8_t big_index, const char *initial_name, size_t len){
	LOG_INF("set initial broadcast name '%s' (%i chars)", initial_name, len);
	if(len == 0 || len >= BROADCAST_SOURCE_ADV_NAME_MAX){
		LOG_WRN("broadcast name too short or too long (%i chars)", len);
		return -EINVAL;
	}

	memcpy(broadcast_name[big_index], initial_name, len);
	broadcast_name[big_index][len] = '\0';

	broadcast_source_default_create(&broadcast_param[big_index], broadcast_name[big_index], len);

	/* Only one BIG supported at the moment */
	int ret = broadcast_source_enable(&broadcast_param[big_index], big_index);
	ERR_CHK_MSG(ret, "Failed to enable broadcaster(s)");

	/* Get advertising set for BIG0 */
	ret = ext_adv_populate(big_index, &ext_adv_data[big_index], ext_adv_buf[big_index], ARRAY_SIZE(ext_adv_buf[big_index]),
			       &ext_adv_buf_cnt[big_index]);
	ERR_CHK_MSG(ret, "Failed to populate extended advertising data");

	ret = per_adv_populate(big_index, &per_adv_data[big_index], &per_adv_buf[big_index], 1, &per_adv_buf_cnt[big_index]);
	ERR_CHK_MSG(ret, "Failed to populate periodic advertising data");

	/* Start broadcaster */
	ret = bt_mgmt_adv_start(big_index, ext_adv_buf[big_index], ext_adv_buf_cnt[big_index], &per_adv_buf[big_index],
				per_adv_buf_cnt[big_index], false);
	ERR_CHK_MSG(ret, "Failed to start advertiser");

    return ret;
}



const char* get_broadcast_name(uint8_t big_index)
{
	return broadcast_name[big_index];
}

static void disconnect_cb(struct bt_conn *conn, void *data)
{
	int ret = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
	ERR_CHK_MSG(ret, "FAILURE disconnecting bt_conn");
}

static struct k_work_big enable_bt_work;
void enable_bt(uint8_t big_index) {
	enable_bt_work.big_index = big_index;
	k_work_init(&enable_bt_work.work, enable_bt_work_handler);
	k_work_submit(&enable_bt_work.work);
}

static void enable_bt_impl(uint8_t big_index){
	LOG_DBG("enable channel %u", big_index);
	if(!bt_enabled[big_index]) {
		int ret = broadcast_source_enable(&broadcast_param[big_index], big_index);
		ERR_CHK_MSG(ret, "FAILURE enabling bluetooth");
		ret = bt_mgmt_adv_start(big_index, NULL, 0, NULL, 0, true);
		ERR_CHK_MSG(ret, "FAILURE restarting advertising");
		bt_enabled[big_index] = true;
		ERR_CHK_MSG(ret, "FAILURE setting LED AUDIO_CONN STATUS blink");
	}
}

static struct k_work_big disable_bt_work;
void disable_bt(uint8_t big_index){
	disable_bt_work.big_index = big_index;
	k_work_init(&disable_bt_work.work, disable_bt_work_handler);
	k_work_submit(&disable_bt_work.work);
}

static void enable_bt_work_handler(struct k_work *work)
{
	struct k_work_big *work_big = CONTAINER_OF(work, struct k_work_big, work);
    enable_bt_impl(work_big->big_index);
}

static void disable_bt_impl(uint8_t big_index) {
	LOG_DBG("disable channel %u", big_index);
	if(bt_enabled[big_index]){
		int ret = bt_le_ext_adv_stop(ext_adv[big_index]);
		ERR_CHK_MSG(ret, "FAILURE stopping ext adv");
		ret = bt_mgmt_per_adv_stop(big_index);
		ERR_CHK_MSG(ret, "FAILURE stopping bt periodic advertising");
		bt_conn_foreach(BT_CONN_TYPE_ALL, disconnect_cb, NULL);
		ret = broadcast_source_disable(big_index);
		ERR_CHK_MSG(ret, "FAILURE disabling broadcast source");
		bt_enabled[big_index] = false;
		ERR_CHK_MSG(ret, "FAILURE setting LED AUDIO_CONN STATUS on");
	}
}

static void disable_bt_work_handler(struct k_work *work)
{
	struct k_work_big *work_big = CONTAINER_OF(work, struct k_work_big, work);
    disable_bt_impl(work_big->big_index);
}

static void initialise_work_handler(struct k_work *work) {
	struct k_work_big *work_big = CONTAINER_OF(work, struct k_work_big, work);
	const uint8_t big_index = work_big->big_index;

	initialise_broadcast(big_index, broadcast_name[big_index], strlen(broadcast_name[big_index]));
}

static struct k_work_big initialise_bc_work;
int initialise_bc(uint8_t big_index, char *name, size_t len){
	LOG_DBG("initialise channel %u", big_index);
	LOG_DBG("set initial broadcast name (%i chars)", len);
	if(len == 0 || len >= BROADCAST_SOURCE_ADV_NAME_MAX){
		LOG_WRN("broadcast name too short or too long (%i chars)", len);
		return -EINVAL;
	}

	memcpy(broadcast_name[big_index], name, len);
	broadcast_name[big_index][len] = '\0';
	LOG_DBG("initial name: %s", broadcast_name[big_index]);
	initialise_bc_work.big_index = big_index;

	k_work_init(&initialise_bc_work.work, initialise_work_handler);
	k_work_submit(&initialise_bc_work.work);
	return 0;
}

static struct k_work_big change_broadcast_name_work;
int change_broadcast_name(uint8_t big_index, const char *new_name, size_t len)
{
	LOG_DBG("Change name on channel %u", big_index);
	LOG_DBG("set new broadcast name (%i chars)", len);
	if(len == 0 || len >= BROADCAST_SOURCE_ADV_NAME_MAX){
		LOG_WRN("broadcast name too short or too long (%i chars)", len);
		return -EINVAL;
	}

	memcpy(broadcast_name[big_index], new_name, len);
	broadcast_name[big_index][len] = '\0';
	LOG_DBG("new name: %s", broadcast_name[big_index]);
	change_broadcast_name_work.big_index = big_index;
	k_work_init(&change_broadcast_name_work.work, change_broadcast_name_work_handler);
	k_work_submit(&change_broadcast_name_work.work);
	return 0;
}

static void change_broadcast_name_work_handler(struct k_work *work) {
	int ret;
	struct k_work_big *work_big = CONTAINER_OF(work, struct k_work_big, work);
	const uint8_t big_index = work_big->big_index;
	disable_bt_impl(big_index);

	LOG_INF("%s", broadcast_name[big_index]);

	broadcast_source_default_create(&broadcast_param[big_index], broadcast_name[big_index], strlen(broadcast_name[big_index])+1);
	/* Get advertising set for BIG0 */
	ret = ext_adv_populate(big_index, &ext_adv_data[big_index], ext_adv_buf[big_index], ARRAY_SIZE(ext_adv_buf[big_index]),
			       &ext_adv_buf_cnt[big_index]);
	ERR_CHK(ret);
	LOG_DBG("ext adv brdcast name = %s", ext_adv_data[big_index].brdcst_name_buf);
	// enable_bt_impl();
}

int broadcast_start(uint8_t big_index){
	return broadcast_source_start(big_index, ext_adv[big_index]);
}

int broadcast_stop(uint8_t big_index){
	return broadcast_source_stop(big_index);
}

int broadcast_send(uint8_t big_index, struct net_buf const *const audio_frame){
	return broadcast_source_send(audio_frame, big_index, 0);
}

void update_ext_adv_data(uint8_t big_index, struct bt_le_ext_adv *ext_adv_data){
	ext_adv[big_index] = ext_adv_data;
}

/*
 * @brief  The following configures the data for the extended advertising.
 *         This includes the Broadcast Audio Announcements [BAP 3.7.2.1] and Broadcast_ID
 *         [BAP 3.7.2.1.1] in the AUX_ADV_IND Extended Announcements.
 *
 * @param  big_index         Index of the Broadcast Isochronous Group (BIG) to get
 *                           advertising data for.
 * @param  ext_adv_data      Pointer to the extended advertising buffers.
 * @param  ext_adv_buf       Pointer to the bt_data used for extended advertising.
 * @param  ext_adv_buf_size  Size of @p ext_adv_buf.
 * @param  ext_adv_count     Pointer to the number of elements added to @p adv_buf.
 *
 * @return  0 for success, error otherwise.
 */
static int ext_adv_populate(uint8_t big_index, struct broadcast_source_ext_adv_data *ext_adv_data,
			    struct bt_data *ext_adv_buf, size_t ext_adv_buf_size,
			    size_t *ext_adv_count)
{
	int ret;
	size_t ext_adv_buf_cnt = 0;

	if (sizeof(broadcast_name[big_index]) >
		ARRAY_SIZE(ext_adv_data->brdcst_name_buf)) {
		LOG_ERR("broadcast_name is too long");
		return -EINVAL;
	}

	size_t brdcst_name_size = strlen(broadcast_name[big_index]);

	memcpy(ext_adv_data->brdcst_name_buf, broadcast_name[big_index],
			brdcst_name_size+1);

	ext_adv_buf[ext_adv_buf_cnt].type = BT_DATA_UUID16_ALL;
	ext_adv_buf[ext_adv_buf_cnt].data = ext_adv_data->uuid_buf->data;
	ext_adv_buf_cnt++;

	ret = bt_mgmt_manufacturer_uuid_populate(ext_adv_data->uuid_buf,
						 CONFIG_BT_DEVICE_MANUFACTURER_ID);
	if (ret) {
		LOG_ERR("Failed to add adv data with manufacturer ID: %d", ret);
		return ret;
	}

	uint32_t broadcast_id = 0x000000;
	bool fixed_id = !IS_ENABLED(CONFIG_BT_AUDIO_USE_BROADCAST_ID_RANDOM);

	if (IS_ENABLED(CONFIG_BT_AUDIO_USE_BROADCAST_ID_FICR)) {
		LOG_DBG("Using FICR ID");
		broadcast_id = NRF_FICR->NFC.TAGHEADER0 >> 8;
	} else if (IS_ENABLED(CONFIG_BT_AUDIO_USE_BROADCAST_ID_FIXED)) {
		LOG_DBG("Using fixed ID");
		broadcast_id = CONFIG_BT_AUDIO_BROADCAST_ID_FIXED;
	} else {
		LOG_DBG("Using random broadcast ID");
		/* The random broadcast ID will be generated by the Bluetooth module */
	}

	ret = broadcast_source_ext_adv_populate(big_index, fixed_id, broadcast_id, ext_adv_data,
						&ext_adv_buf[ext_adv_buf_cnt],
						ext_adv_buf_size - ext_adv_buf_cnt);
	if (ret < 0) {
		LOG_ERR("Failed to add ext adv data for broadcast source: %d", ret);
		return ret;
	}

	ext_adv_buf_cnt += ret;

	/* Add the number of UUIDs */
	ext_adv_buf[0].data_len = ext_adv_data->uuid_buf->len;

	LOG_DBG("Size of adv data: %d, num_elements: %d", sizeof(struct bt_data) * ext_adv_buf_cnt,
		ext_adv_buf_cnt);

	*ext_adv_count = ext_adv_buf_cnt;

	return 0;
}

/*
 * @brief  The following configures the data for the periodic advertising.
 *         This includes the Basic Audio Announcement, including the
 *         BASE [BAP 3.7.2.2] and BIGInfo.
 *
 * @param  big_index         Index of the Broadcast Isochronous Group (BIG) to get
 *                           advertising data for.
 * @param  pre_adv_data      Pointer to the periodic advertising buffers.
 * @param  per_adv_buf       Pointer to the bt_data used for periodic advertising.
 * @param  per_adv_buf_size  Size of @p ext_adv_buf.
 * @param  per_adv_count     Pointer to the number of elements added to @p adv_buf.
 *
 * @return  0 for success, error otherwise.
 */
static int per_adv_populate(uint8_t big_index, struct broadcast_source_per_adv_data *pre_adv_data,
			    struct bt_data *per_adv_buf, size_t per_adv_buf_size,
			    size_t *per_adv_count)
{
	int ret;
	size_t per_adv_buf_cnt = 0;

	ret = broadcast_source_per_adv_populate(big_index, pre_adv_data, per_adv_buf,
						per_adv_buf_size - per_adv_buf_cnt);
	if (ret < 0) {
		LOG_ERR("Failed to add per adv data for broadcast source: %d", ret);
		return ret;
	}

	per_adv_buf_cnt += ret;

	LOG_DBG("Size of per adv data: %d, num_elements: %d",
		sizeof(struct bt_data) * per_adv_buf_cnt, per_adv_buf_cnt);

	*per_adv_count = per_adv_buf_cnt;

	return 0;
}