/*
 * Copyright (c) 2012-2014 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/drivers/gpio.h>

#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(app);

#define DUMMY_FILE_SIZE 0x19000 /* File size in bytes */

/* UUID of custom service */
#define BT_UUID_DUMMY_VAL BT_UUID_128_ENCODE(0x205020d0, 0xbe54, 0x48ce, 0xa4ff, 0xd992e2a85f4e)

/* UUID of notify characteristic */
#define BT_UUID_TX_VAL BT_UUID_128_ENCODE(0x205020d1, 0xbe54, 0x48ce, 0xa4ff, 0xd992e2a85f4e)

#define BT_UUID_CUSTOM_SERVICE BT_UUID_DECLARE_128(BT_UUID_DUMMY_VAL)
#define BT_UUID_CUSTOM_TX      BT_UUID_DECLARE_128(BT_UUID_TX_VAL)
/*
 * Get button configuration from the devicetree sw0 alias. This is mandatory.
 */
#define SW0_NODE               DT_ALIAS(sw0)
#if !DT_NODE_HAS_STATUS(SW0_NODE, okay)
#error "Unsupported board: sw0 devicetree alias is not defined"
#endif

static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios, {0});
static struct gpio_callback button_cb_data;

static K_SEM_DEFINE(send_data, 0, 1);

static struct bt_conn *current_conn;

static volatile bool notifications_enabled;

static void tx_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value);

/* Service Declaration */
BT_GATT_SERVICE_DEFINE(custom_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_CUSTOM_SERVICE),
		       BT_GATT_CHARACTERISTIC(BT_UUID_CUSTOM_TX, BT_GATT_CHRC_NOTIFY,
					      BT_GATT_PERM_READ, NULL, NULL, NULL),
		       BT_GATT_CCC(tx_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), );

static const struct bt_data ad[] = {
	BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
	BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
};

static int bt_custom_tx_send(struct bt_conn *conn, const uint8_t *data, uint16_t len)
{
	const struct bt_gatt_attr *attr = &custom_svc.attrs[2];

	return bt_gatt_notify(conn, attr, data, len);
}

static void tx_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
	if (value == BT_GATT_CCC_NOTIFY) {
		LOG_INF("Notifications enabled");
		notifications_enabled = true;
	} else {
		LOG_INF("Notifications disabled");
		notifications_enabled = false;
	}
}

static void connected(struct bt_conn *conn, uint8_t err)
{
	if (err) {
		LOG_INF("Connection failed (err %u)", err);
		return;
	}

	current_conn = bt_conn_ref(conn);

	LOG_INF("Connected");
}

static void disconnected(struct bt_conn *conn, uint8_t reason)
{
	LOG_INF("Disconnected (reason %u)", reason);

	if (current_conn) {
		bt_conn_unref(current_conn);
		current_conn = NULL;
		;
	}
}

void connection_param_update(struct bt_conn *conn, uint16_t interval, uint16_t latency,
			     uint16_t timeout)
{
	float result = interval * 1.25f;
	int ci = (int)result;
	int ci_fract = (result - ci) * 100;

	LOG_INF("Connection paramaters updated.\r\n - Interval: %d.%d ms\r\n - Latency: %d\r\n - "
		"Timeout: "
		"%d ms",
		ci, ci_fract, latency, timeout * 10);
}


static void update_phy(struct bt_conn *conn)
{
    int err;
    const struct bt_conn_le_phy_param preferred_phy = {
        .options = BT_CONN_LE_PHY_OPT_NONE,
        .pref_rx_phy = BT_GAP_LE_PHY_2M,
        .pref_tx_phy = BT_GAP_LE_PHY_2M,
    };
    err = bt_conn_le_phy_update(conn, &preferred_phy);
    if (err) {
        LOG_ERR("bt_conn_le_phy_update() returned %d", err);
    }
}

static const char *phy2str(uint8_t phy)
{
	switch (phy) {
	case 0: return "No packets";
	case BT_GAP_LE_PHY_1M: return "LE 1M";
	case BT_GAP_LE_PHY_2M: return "LE 2M";
	case BT_GAP_LE_PHY_CODED: return "LE Coded";
	default: return "Unknown";
	}
}

static void sh_le_phy_updated(struct bt_conn *conn,
			   struct bt_conn_le_phy_info *param)
{
	LOG_INF("LE PHY updated: TX PHY %s, RX PHY %s\n",
	       phy2str(param->tx_phy), phy2str(param->rx_phy));
}

BT_CONN_CB_DEFINE(conn_callbacks) = {
	.connected = connected,
	.disconnected = disconnected,
	.le_param_updated = connection_param_update,
	.le_phy_updated = sh_le_phy_updated,
};

void att_mtu_updated(struct bt_conn *conn, uint16_t tx, uint16_t rx)
{
	LOG_INF("att MTU updated to %d bytes", rx);
}

static struct bt_gatt_cb gatt_cb = {
	.att_mtu_updated = att_mtu_updated,
};

static void send_dummy_data(void)
{
	int err;
	uint16_t max_pkt_len = bt_gatt_get_mtu(current_conn) - 3;
	uint8_t dummy_data[max_pkt_len];
	uint16_t pkt_len;
	uint32_t bytes_remaining = DUMMY_FILE_SIZE;
	uint64_t stamp;
	int64_t delta;

	LOG_INF("Notification size : %d", max_pkt_len);

	// err = flash_read(nand_flash, page_addr, page, m_cb.sh_sn_dev.bytes_per_page);
	// if (err != SH_DL_SUCCESS) {
	// 	LOG_ERR("Nand flash write err code: %x \r\n", err);
	// 	return -1;
	// }

	memset(dummy_data, 0xAA, sizeof(dummy_data));

	LOG_INF("Starting file transfer");

	stamp = k_uptime_get_32();
	do {
		pkt_len = (max_pkt_len < bytes_remaining) ? max_pkt_len : bytes_remaining;

		err = bt_custom_tx_send(current_conn, dummy_data, pkt_len);
		if (err) {
			LOG_INF("bt_custom_tx_send() failed (err %d)", err);
			/* Wait for queued notifications elements to be freed */
			k_msleep(5);
		} else {
			bytes_remaining -= pkt_len;
		}

	} while (bytes_remaining != 0);

	delta = k_uptime_delta(&stamp);

	LOG_INF("Sent %u bytes (%u KB) in %lld ms at %llu kbps", DUMMY_FILE_SIZE,
		DUMMY_FILE_SIZE / 1024, delta, ((uint64_t)DUMMY_FILE_SIZE * 8 / delta));
}

void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
	if (notifications_enabled) {
		k_sem_give(&send_data);
	} else {
		LOG_ERR("Notifications are not enabled. Enable notifications and try again");
	}
}

void button_init(void)
{
	int err;

	err = gpio_pin_configure_dt(&button, GPIO_INPUT);
	if (err != 0) {
		LOG_INF("Error %d: failed to configure %s pin %d", err, button.port->name,
			button.pin);
		return;
	}
	err = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_LEVEL_ACTIVE);
	if (err != 0) {
		LOG_INF("Error %d: failed to configure interrupt on %s pin %d", err,
			button.port->name, button.pin);
		return;
	}
	gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
	gpio_add_callback(button.port, &button_cb_data);
}

int main(void)
{
	int err;

	LOG_INF("Peripheral sample with custom service starting.");

	button_init();

	err = bt_enable(NULL);
	if (err) {
		LOG_INF("Bluetooth init failed (err %d)", err);
		return 0;
	}

	// update_phy(current_conn);

	bt_gatt_cb_register(&gatt_cb);

	err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad), NULL, 0);
	if (err) {
		LOG_INF("Advertising failed to start (err %d)", err);
		return 0;
	}

	while (1) {
		k_sem_take(&send_data, K_FOREVER);
		k_msleep(50);
		/* Try to acquire the semaphore again in case of button debouncing. */
		k_sem_take(&send_data, K_NO_WAIT);
		send_dummy_data();
	}
}
