/*
 * Copyright (c) 2022 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/sys/printk.h>

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

#include <bluetooth/gatt_dm.h>
#include <zephyr/logging/log.h>

#define UUID_DFU_SERVICE BT_UUID_DECLARE_16(0xfe59)

LOG_MODULE_REGISTER(gatt_write_common_c, LOG_LEVEL_DBG);

static struct bt_gatt_exchange_params mtu_exchange_params;
static uint32_t write_count;
static uint32_t write_len;
static uint32_t write_rate;
struct bt_conn *conn_connected;
uint32_t last_write_rate;
void (*start_scan_func)(void);

bool gbl_ServicesResolved;

static void write_cmd_cb(struct bt_conn *conn, void *user_data)
{
	static uint32_t cycle_stamp;
	uint64_t delta;

	delta = k_cycle_get_32() - cycle_stamp;
	delta = k_cyc_to_ns_floor64(delta);

	/* if last data rx-ed was greater than 1 second in the past,
	 * reset the metrics.
	 */
	if (delta > (1U * NSEC_PER_SEC)) {
		printk("%s: count= %u, len= %u, rate= %u bps.\n", __func__,
		       write_count, write_len, write_rate);

		last_write_rate = write_rate;

		write_count = 0U;
		write_len = 0U;
		write_rate = 0U;
		cycle_stamp = k_cycle_get_32();
	} else {
		uint16_t len;

		write_count++;

		/* Extract the 16-bit data length stored in user_data */
		len = (uint32_t)user_data & 0xFFFF;

		write_len += len;
		write_rate = ((uint64_t)write_len << 3) * (1U * NSEC_PER_SEC) /
			     delta;
	}
}

static void mtu_exchange_cb(struct bt_conn *conn, uint8_t err,
			    struct bt_gatt_exchange_params *params)
{
	printk("%s: MTU exchange %s (%u)\n", __func__,
	       err == 0U ? "successful" : "failed",
	       bt_gatt_get_mtu(conn));
}

static int mtu_exchange(struct bt_conn *conn)
{
	int err;

	printk("%s: Current MTU = %u\n", __func__, bt_gatt_get_mtu(conn));

	mtu_exchange_params.func = mtu_exchange_cb;

	printk("%s: Exchange MTU...\n", __func__);
	err = bt_gatt_exchange_mtu(conn, &mtu_exchange_params);
	if (err) {
		printk("%s: MTU exchange failed (err %d)", __func__, err);
	}

	return err;
}

static void discovery_complete(struct bt_gatt_dm *dm, void *context)
{
	/*
	const struct bt_gatt_dm_attr *attr = bt_gatt_dm_service_get(dm);
	char str[255];
	bt_uuid_to_str(attr->uuid, str, sizeof(str));
	LOG_DBG("Handle: 0x%04X    permissions: 0x%04X    UUID:<%s>", attr->handle, attr->perm, str);

	while (NULL != (attr = bt_gatt_dm_attr_next(dm, attr))) 
	{
		char str[255] = {0};

		bt_uuid_to_str(attr->uuid, str, sizeof(str));
		//printk("ATT[%u]: \tUUID: 0x%s\tHandle: 0x%04X\tValue:\n",(unsigned int)(attr - dm->attrs), str, attr->handle);
		LOG_DBG("Handle: 0x%04X    permissions: 0x%02X    UUID:<%s>", attr->handle, attr->perm, str);
	}
	*/

	//struct bt_nus_client *nus = context;

/*
ATT[1]:         UUID: 0x2803    Handle: 0x000F  Value: Characteristic: 0x8ec90003-f315-4f60-9fb8-838830daea50  Properties: 0x0028
ATT[2]:         UUID: 0x8ec90003-f315-4f60-9fb8-838830daea50    Handle: 0x0010  Value:
ATT[3]:         UUID: 0x2902    Handle: 0x0011  Value:  CCCD

*/
	bt_gatt_dm_data_print(dm);

	//bt_nus_handles_assign(dm, nus);
	//bt_nus_subscribe_receive(nus);	// Enable notifications

	//bt_gatt_dm_data_release(dm);

	gbl_ServicesResolved = true;
}
static void discovery_service_not_found(struct bt_conn *conn, void *context)
{
	LOG_INF("Service not found");
}

static void discovery_error(struct bt_conn *conn, int err, void *context)
{
	LOG_WRN("Error while discovering GATT database: (%d)", err);
}

static struct bt_gatt_dm_cb discovery_cb = 
{
	.completed         = discovery_complete,
	.service_not_found = discovery_service_not_found,
	.error_found       = discovery_error,
};

static void connected(struct bt_conn *conn, uint8_t conn_err)
{
	struct bt_conn_info conn_info;
	char addr[BT_ADDR_LE_STR_LEN];
	int err;

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

	if (conn_err) {
		printk("%s: Failed to connect to %s (%u)\n", __func__, addr,
		       conn_err);
		return;
	}

	err = bt_conn_get_info(conn, &conn_info);
	if (err) {
		printk("Failed to get connection info (%d).\n", err);
		return;
	}

	printk("CONNECTED   %s: %s role %u\n", __func__, addr, conn_info.role);

	conn_connected = bt_conn_ref(conn);

	(void)mtu_exchange(conn);

#if defined(CONFIG_BT_SMP)
	if (conn_info.role == BT_CONN_ROLE_CENTRAL) {
		err = bt_conn_set_security(conn, BT_SECURITY_L2);
		if (err) {
			printk("Failed to set security (%d).\n", err);
		}
	}
#endif

	err = bt_gatt_dm_start(conn, UUID_DFU_SERVICE, &discovery_cb, NULL);

}

static void disconnected(struct bt_conn *conn, uint8_t reason)
{
	struct bt_conn_info conn_info;
	char addr[BT_ADDR_LE_STR_LEN];
	int err;

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

	err = bt_conn_get_info(conn, &conn_info);
	if (err) {
		printk("Failed to get connection info (%d).\n", err);
		return;
	}

	printk("%s: %s role %u (reason %u)\n", __func__, addr, conn_info.role,
	       reason);

	conn_connected = NULL;

	bt_conn_unref(conn);

	if (conn_info.role == BT_CONN_ROLE_CENTRAL) {
		start_scan_func();
	}
}

static bool le_param_req(struct bt_conn *conn, struct bt_le_conn_param *param)
{
	printk("%s: int (0x%04x, 0x%04x) lat %u to %u\n", __func__,
	       param->interval_min, param->interval_max, param->latency,
	       param->timeout);

	return true;
}

static void le_param_updated(struct bt_conn *conn, uint16_t interval,
			     uint16_t latency, uint16_t timeout)
{
	printk("%s: int 0x%04x lat %u to %u\n", __func__, interval,
	       latency, timeout);
}

#if defined(CONFIG_BT_SMP)
static void security_changed(struct bt_conn *conn, bt_security_t level,
			     enum bt_security_err err)
{
	printk("%s: to level %u (err %u)\n", __func__, level, err);
}
#endif

BT_CONN_CB_DEFINE(conn_callbacks) = {
	.connected = connected,
	.disconnected = disconnected,
	.le_param_req = le_param_req,
	.le_param_updated = le_param_updated,
#if defined(CONFIG_BT_SMP)
	.security_changed = security_changed,
#endif
};

int write_cmd(struct bt_conn *conn)
{
	static uint8_t data[BT_ATT_MAX_ATTRIBUTE_LEN] = {0, };
	static uint16_t data_len;
	uint16_t data_len_max;
	int err;

	data_len_max = bt_gatt_get_mtu(conn) - 3;
	if (data_len_max > BT_ATT_MAX_ATTRIBUTE_LEN) {
		data_len_max = BT_ATT_MAX_ATTRIBUTE_LEN;
	}

#if TEST_FRAGMENTATION_WITH_VARIABLE_LENGTH_DATA
	/* Use incremental length data for every write command */
	/* TODO: Include test case in BabbleSim tests */
	static bool decrement;

	if (decrement) {
		data_len--;
		if (data_len <= 1) {
			data_len = 1;
			decrement = false;
		}
	} else {
		data_len++;
		if (data_len >= data_len_max) {
			data_len = data_len_max;
			decrement = true;
		}
	}
#else
	/* Use fixed length data for every write command */
	data_len = 1;//data_len_max;
#endif

	/* Pass the 16-bit data length value (instead of reference) in
	 * user_data so that unique value is pass for each write callback.
	 * Using handle 0x0001, we do not care if it is writable, we just want
	 * to transmit the data across.
	 *
	err = bt_gatt_write_without_response_cb(conn, 0x0001, data, data_len,
						false, write_cmd_cb,
						(void *)((uint32_t)data_len));
	*/
	err = bt_gatt_write_without_response_cb(conn, 0x0010, 0x01, data_len, false, NULL, NULL);
	if (err) 
	{
		printk("%s: Write cmd failed (%d).\n", __func__, err);
	}

	return err;
}
