/*
 * Copyright (c) 2018 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
 */

#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>

#include "custom_client.h"
#include "../ble.h"
#include "../serial.h"

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(custom_c, CONFIG_BT_CUSTOM_CLIENT_LOG_LEVEL);

static uint8_t on_association_value_read(struct bt_conn *conn, uint8_t err,
					   struct bt_gatt_read_params *params,
					   const void *data, uint16_t length);

enum 
{
	CUSTOM_C_INITIALIZED,
	CUSTOM_C_TX_NOTIF_ENABLED,
	CUSTOM_C_RX_NOTIF_ENABLED,
	CUSTOM_C_RX_WRITE_PENDING
};
// This is if we receive on the tx characteristic, which clearly won't happen and we can likely lose
static uint8_t on_received_tx(struct bt_conn *conn,
			struct bt_gatt_subscribe_params *params,
			const void *data, uint16_t length)
{
	struct bt_custom_client *custom;

	/* Retrieve CUSTOM Client module context. */
	custom = CONTAINER_OF(params, struct bt_custom_client, tx_notif_params);

	if (!data) 
	{
		LOG_DBG("[UNSUBSCRIBED]");
		params->value_handle = 0;
		atomic_clear_bit(&custom->state, CUSTOM_C_TX_NOTIF_ENABLED);
#ifdef CALLBACK_METHOD
		if (custom->cb.unsubscribed) 
		{
			custom->cb.unsubscribed(custom);
		}
#endif		
		return BT_GATT_ITER_STOP;
	}

	LOG_DBG("[NOTIFICATION] data %p length %u", data, length);
#ifdef CALLBACK_METHOD	
	if (custom->cb.received_tx) 
	{
		return custom->cb.received_tx(custom, data, length);
	}
#endif
	return BT_GATT_ITER_CONTINUE;
}

static uint8_t on_received_rx(struct bt_conn *conn,
			struct bt_gatt_subscribe_params *params,
			const void *data, uint16_t length)
{
	struct bt_custom_client *custom;

	/* Retrieve CUSTOM Client module context. */
	custom = CONTAINER_OF(params, struct bt_custom_client, rx_notif_params);

	if (!data) 
	{
		LOG_DBG("[UNSUBSCRIBED]");
		params->value_handle = 0;
		atomic_clear_bit(&custom->state, CUSTOM_C_RX_NOTIF_ENABLED);
		return BT_GATT_ITER_STOP;
	}

	LOG_DBG("[NOTIFICATION] data %p length %u", data, length);
	store_received_ble_data(data, length);	


	return BT_GATT_ITER_CONTINUE;
}

static void on_sent(struct bt_conn *conn, uint8_t err,
		    struct bt_gatt_write_params *params)
{
	struct bt_custom_client *custom_c;
	const void *data;
	uint16_t length;

	/* Retrieve CUSTOM Client module context. */
	custom_c = CONTAINER_OF(params, struct bt_custom_client, tx_write_params);

	/* Make a copy of volatile data that is required by the callback. */
	data = params->data;
	length = params->length;

	atomic_clear_bit(&custom_c->state, CUSTOM_C_RX_WRITE_PENDING);
	
	set_ble_data_sent();	// This is  giving a sempahore in the serial code

}


int bt_custom_client_send(struct bt_custom_client *custom_c, const uint8_t *data,
		       uint16_t len)
{
	int err;

	if (!custom_c->conn) 
	{
		return -ENOTCONN;
	}

	if (atomic_test_and_set_bit(&custom_c->state, CUSTOM_C_RX_WRITE_PENDING)) 
	{
		return -EALREADY;
	}

	custom_c->tx_write_params.func = on_sent;
	custom_c->tx_write_params.handle = custom_c->handles.tx;
	custom_c->tx_write_params.offset = 0;
	custom_c->tx_write_params.data = data;
	custom_c->tx_write_params.length = len;

	err = bt_gatt_write(custom_c->conn, &custom_c->tx_write_params);
	if (err) 
	{
		atomic_clear_bit(&custom_c->state, CUSTOM_C_RX_WRITE_PENDING);
	}

	return err;
}

int bt_custom_handles_assign(struct bt_gatt_dm *dm, struct bt_custom_client *custom_c)
{


	const struct bt_gatt_dm_attr *gatt_service_attr = bt_gatt_dm_service_get(dm);
	const struct bt_gatt_service_val *gatt_service = bt_gatt_dm_attr_service_val(gatt_service_attr);
	const struct bt_gatt_dm_attr *gatt_chrc;
	const struct bt_gatt_dm_attr *gatt_desc;

	if (bt_uuid_cmp(gatt_service->uuid, BT_UUID_CUSTOM_SERVICE)) 
	{
		return -ENOTSUP;
	}
	LOG_DBG("Getting handles from CUSTOM service.");
	memset(&custom_c->handles, 0xFF, sizeof(custom_c->handles));

	/* CUSTOM TX Characteristic */
	gatt_chrc = bt_gatt_dm_char_by_uuid(dm, BT_UUID_CUSTOM_TX);
	if (NULL == gatt_chrc) 
	{
		LOG_ERR("Missing CUSTOM TX characteristic.");
		return -EINVAL;
	}
	/* CUSTOM TX */
	gatt_desc = bt_gatt_dm_desc_by_uuid(dm, gatt_chrc, BT_UUID_CUSTOM_TX);
	if (NULL == gatt_desc) 
	{
		LOG_ERR("Missing CUSTOM TX value descriptor in characteristic.");
		return -EINVAL;
	}
	LOG_DBG("Found handle for CUSTOM TX characteristic.");
	custom_c->handles.tx = gatt_desc->handle;
	/* CUSTOM TX CCC */
	gatt_desc = bt_gatt_dm_desc_by_uuid(dm, gatt_chrc, BT_UUID_GATT_CCC);
	if (NULL == gatt_desc) 
	{
		LOG_ERR("Missing CUSTOM TX CCC in characteristic.");
		return -EINVAL;
	}
	LOG_DBG("Found handle for CCC of CUSTOM TX characteristic.");
	custom_c->handles.tx_ccc = gatt_desc->handle;

	/* CUSTOM RX Characteristic */
	gatt_chrc = bt_gatt_dm_char_by_uuid(dm, BT_UUID_CUSTOM_RX);
	if (NULL == gatt_chrc) 
	{
		LOG_ERR("Missing CUSTOM RX characteristic.");
		return -EINVAL;
	}
	/* CUSTOM RX */
	gatt_desc = bt_gatt_dm_desc_by_uuid(dm, gatt_chrc, BT_UUID_CUSTOM_RX);
	if (NULL == gatt_desc) 
	{
		LOG_ERR("Missing CUSTOM RX value descriptor in characteristic.");
		return -EINVAL;
	}
	LOG_DBG("Found handle for CUSTOM RX characteristic.");
	custom_c->handles.rx = gatt_desc->handle;

		/* CUSTOM RX CCC */
	gatt_desc = bt_gatt_dm_desc_by_uuid(dm, gatt_chrc, BT_UUID_GATT_CCC);
	if (!gatt_desc) 
	{
		LOG_ERR("Missing CUSTOM TX CCC in characteristic.");
		return -EINVAL;
	}

	LOG_DBG("Found handle for CCC of CUSTOM RX characteristic.");
	custom_c->handles.rx_ccc = gatt_desc->handle;

#ifdef DISCOVERY	
	/* CUSTOM Association */
	gatt_chrc = bt_gatt_dm_char_by_uuid(dm, BT_UUID_CUSTOM_DISC);	
	if (NULL == gatt_chrc) 
	{
		LOG_ERR("Missing CUSTOM Discovery value descriptor in characteristic.");
		return -EINVAL;
	}
	gatt_desc = bt_gatt_dm_desc_by_uuid(dm, gatt_chrc, BT_UUID_CUSTOM_DISC);
	if (NULL == gatt_desc) 
	{
		LOG_ERR("Missing CUSTOM Discovery value descriptor in characteristic.");
		return -EINVAL;
	}
	LOG_DBG("Found handle for CUSTOM Discovery characteristic.");
	custom_c->handles.association = gatt_desc->handle;
#endif

	/* Assign connection instance. */
	custom_c->conn = bt_gatt_dm_conn_get(dm);
	return NO_ERROR;
}


int bt_custom_subscribe_tx_receive(struct bt_custom_client *custom_c)
{
	int err;

	if (atomic_test_and_set_bit(&custom_c->state, CUSTOM_C_TX_NOTIF_ENABLED)) 
	{
		return -EALREADY;
	}

	custom_c->tx_notif_params.notify = on_received_tx;
	custom_c->tx_notif_params.value = BT_GATT_CCC_NOTIFY;
	custom_c->tx_notif_params.value_handle = custom_c->handles.tx;
	custom_c->tx_notif_params.ccc_handle = custom_c->handles.tx_ccc;
	atomic_set_bit(custom_c->tx_notif_params.flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE);

	err = bt_gatt_subscribe(custom_c->conn, &custom_c->tx_notif_params);
	if (err) 
	{
		LOG_ERR("Subscribe failed (err %d)", err);
		atomic_clear_bit(&custom_c->state, CUSTOM_C_TX_NOTIF_ENABLED);
	} 
	else 
	{
		LOG_DBG("[SUBSCRIBED]");
	}

	return err;
}


int bt_custom_subscribe_rx_receive(struct bt_custom_client *custom_c)
{
	int err;

	custom_c->rx_notif_params.notify = on_received_rx;
	custom_c->rx_notif_params.value = BT_GATT_CCC_NOTIFY;
	custom_c->rx_notif_params.value_handle = custom_c->handles.rx;
	custom_c->rx_notif_params.ccc_handle = custom_c->handles.rx_ccc;

	err = bt_gatt_subscribe(custom_c->conn, &custom_c->rx_notif_params);
	if (err) 
	{
		LOG_ERR("Subscribe failed (err %d)", err);
		atomic_clear_bit(&custom_c->state, CUSTOM_C_TX_NOTIF_ENABLED);
	} 	
	else 
	{
		LOG_DBG("[SUBSCRIBED]");
	}

	return err;
}
#ifdef DISCOVERY	
// Read the value that says whether the XLRT is in discovery mode
int init_association_discovery(struct bt_custom_client *xlrt_connection_ptr)
{

	int ret_val;

	struct bt_gatt_read_params *params;


	params = &xlrt_connection_ptr->association_parameters.read_params;
	params->handle_count = 1;
	params->single.handle = xlrt_connection_ptr->handles.association;
	params->single.offset = 0;
	params->func = on_association_value_read;

	ret_val = bt_gatt_read(xlrt_connection_ptr->conn, params);

	return ret_val;

}

static uint8_t on_association_value_read(struct bt_conn *conn, uint8_t err,
					   struct bt_gatt_read_params *params,
					   const void *data, uint16_t length)
{

	uint8_t ret_val;
	uint8_t read_value;
	read_value = 0;
	ret_val = EINVAL;
	if ((NULL != conn) && (NULL != params) && (NULL != data) && (NO_ERROR == err))
	{
		read_value = *(uint8_t *)data;
		set_association_value(read_value);
		ret_val = NO_ERROR;
	}
	return ret_val;

}

#endif