/*

*/

#include "remote.h"

#define LOG_MODULE_NAME remote
LOG_MODULE_REGISTER (LOG_MODULE_NAME, 4);
#define DEVICE_NAME CONFIG_BT_DEVICE_NAME //lấy name từ prj.conf
#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME)-1)


static K_SEM_DEFINE(ble_init_ok, 1, 1);

static uint8_t button_value=0;
extern uint8_t btn_count;
enum bt_button_notifications_enabled notifications_enabled;

static struct bt_remote_service_cb remote_callbacks;

/* Define advertising data (ad). có 2 phần, ad (advertising) và sd (respone data)
    Đầu tiên periphral sẽ adverting data tới active scaner, scaner sẽ hỏi còn dữ liẹu gì không
    lúc đó sẽ gửi response data.
    BT_LE_AD_NO_BREDR - tức không support classic BT
*/
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, DEVICE_NAME, DEVICE_NAME_LEN),
};

// scan response data (sd), ở đây response với UUID
static const struct bt_data sd[] = {
	BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_REMOTE_SERV_VAL),
};

/* Khai báo hàm callback cho GATT characteristic */
static ssize_t read_button_characteristic_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset);
void button_chrc_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value);
static ssize_t on_write(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags);

/* Định nghĩa GATT service cho bluetooth */
BT_GATT_SERVICE_DEFINE(remote_srv,
BT_GATT_PRIMARY_SERVICE(BT_UUID_REMOTE_SERVICE), //BT_UUID_REMOVE_SERVICE
    //add characteristic vào service bên trên
    BT_GATT_CHARACTERISTIC( BT_UUID_REMOTE_BUTTON_CHRC, // UUID của button
                            BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,  // property characteristic = read
                            BT_GATT_PERM_READ,  // permission = read -> user được phép đọc
                            read_button_characteristic_cb,               // hàm read callback
                            NULL, NULL ),        // write callback và user data
    // ccc = client characteristic configuration
    // nó sẽ belong to characteristic ở ngay bên trên, khai báo cho phép read và write
    BT_GATT_CCC(button_chrc_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),

    // Characteristic để nhận data từ central tới periphreal
    BT_GATT_CHARACTERISTIC(BT_UUID_REMOTE_MESSAGE_CHRC,
                    BT_GATT_CHRC_WRITE_WITHOUT_RESP,
                    BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
                    NULL, on_write, NULL),
);

/* callback của GATT characteristic và CCC (Client Characteristic configuation)*/
static ssize_t on_write(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf,
			            uint16_t len,
			            uint16_t offset,
			            uint8_t flags)
{
	LOG_INF("Received data, handle %d, conn %p", attr->handle, (void *)conn);

	if (remote_callbacks.data_received) {
		remote_callbacks.data_received(conn, buf, len);
    }
	return len; //return len vì ssize_t yêu cầu
}

/*  khi bấm button sẽ chạy vào đây, nhưng không vào mà văng ra luôn
    Khi không subcribe notifications bên central, cũng không vào on_sent làm
*/
void on_sent(struct bt_conn *conn, void *user_data)
{
    ARG_UNUSED(user_data);
    LOG_INF("Notification sent on connection %p", (void *)conn);
}

static ssize_t read_button_characteristic_cb (struct bt_conn *conn, const struct bt_gatt_attr *attr,
				                                void *buf, uint16_t len, uint16_t offset) {

    /* cấu trúc phải theo hàm bt_gatt_attr_read
    Khi mà central connected và cố gắng đọc BT_GATT_CHARACTERISTIC ở trên sẽ trigger call back
    'read_button_characteristic_cb khai báo ở trên thì sẽ return tới bt_gatt_attr_read
    */
    return bt_gatt_attr_read(conn, attr, buf, len, offset, &button_value, sizeof(button_value));
}

void button_chrc_ccc_cfg_changed (const struct bt_gatt_attr *attr, uint16_t value){
    bool notif_enabled = (value == BT_GATT_CCC_NOTIFY);
    LOG_INF("notification %s", notif_enabled? "enabled":"disable");

    notifications_enabled = notif_enabled?BT_BUTTON_NOTIFICATIONS_ENABLED:BT_BUTTON_NOTIFICATIONS_DISABLED;

    if (remote_callbacks.notif_changed) {
        remote_callbacks.notif_changed(notifications_enabled);
    }
}

/*  bt_ready is a callback that contains code that you want to run after the Bluetooth host is enabled.
    For instance, you’ll want to catch any error code that the bt subsystem returns,
    as well as configure connection callbacks, initialize your service, and start advertising.

    hàm này make sure bluetooth done. Để đảm bảo quá trình init OK, trong hàm này ta thêm
    semaphore để đợi bt_ready xong mới thực hiện tiếp
*/
static void bt_ready(int err)
{
	if (err) {
		printk("BLE init failed with error code %d\n", err);
		return;
	}
    k_sem_give(&ble_init_ok); // k_sem_give ở callback này. báo cho k_sem_take(&ble_init_ok, K_FOREVER) đã ok
}

/* Custom functions */
int send_button_notification(struct bt_conn *conn, uint8_t value, uint16_t length){
    int err=0;
    struct bt_gatt_notify_params params = {0};
    const struct bt_gatt_attr *attr = &remote_srv.attrs[2];

    params.attr = attr;
    params.data = &value;
    params.len = length;
    params.func = on_sent; //callback func

    err = bt_gatt_notify_cb(conn, &params);
    LOG_INF("send button notification code: %d", err);
    return err;
}

void set_button_value (uint8_t btn_value){
    button_value = btn_value;
}

int bluetooth_init(struct bt_conn_cb *bt_cb, struct bt_remote_service_cb *remote_cb)
{
    int err=0;
    LOG_INF("Initializing bluetooth");

    if (bt_cb == NULL || remote_cb == NULL) {
        printk("bt_cb NULL");
        return -NRFX_ERROR_NULL;
    }
    // trước khi enable thì cần đăng ký vài connection callback,
    bt_conn_cb_register(bt_cb);

    //register remote service callback manualy
    remote_callbacks.notif_changed = remote_cb->notif_changed; //đăng ký notification change

    //thêm vào hàm nhận data
    remote_callbacks.data_received = remote_cb->data_received;
    //enable bluetooth, bên prj.conf phải thêm CONFIG_BT=y
    err = bt_enable(bt_ready); //phải khai báo bt_ready làm hàm callback ở trên.
    // dùng bt_is_ready cho kết quả tương tự !!! dùng cách nào.
    if (err) {
        LOG_ERR("bt_enable returned %d", err);
        return err;
    }

    k_sem_take(&ble_init_ok, K_FOREVER);

    
	err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
    if (err)
	{
		LOG_ERR("Advertising failed to start (err %d)\n", err);
		return err;
	}

    return err;
}
