/* main.c - BLE HID Keyboard sample for nRF Connect SDK */
#include <errno.h>                    /* Error number definitions for return codes */
#include <stddef.h>                   /* Standard definitions (NULL, size_t, etc.) */
#include <string.h>                   /* String manipulation functions (memcpy, memset) */
#include <zephyr/kernel.h>            /* Zephyr kernel API (k_sleep, k_sem) */
#include <zephyr/sys/printk.h>        /* Print functions for debug output */
#include <zephyr/types.h>             /* Zephyr type definitions */

#include <zephyr/settings/settings.h> /* Settings subsystem for persistent storage */

#include <zephyr/bluetooth/bluetooth.h> /* Core Bluetooth API */
#include <zephyr/bluetooth/conn.h>      /* Bluetooth connection management */
#include <zephyr/bluetooth/gatt.h>      /* GATT (Generic Attribute Profile) */
#include <zephyr/bluetooth/uuid.h>      /* Bluetooth UUID definitions */
#include <zephyr/bluetooth/addr.h>      /* Bluetooth address utilities */

#include <bluetooth/services/hids.h>    /* HID Service implementation */
#include <dk_buttons_and_leds.h>        /* Nordic DK button and LED functions */

#include <zephyr/device.h>              /* Device tree and device API */

#include <zephyr/sys/util.h>            /* Utility macros (ARRAY_SIZE) */
#include <zephyr/bluetooth/hci.h>        /* HCI (Host Controller Interface) */

/* Device name & size (CONFIG_BT_DEVICE_NAME) - Used in advertising data */
#define DEVICE_NAME CONFIG_BT_DEVICE_NAME
#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)

/* HID related definitions - Standard HID keyboard specifications */
#define BASE_USB_HID_SPEC_VERSION    0x0101  /* HID Class Specification release version 1.1 */
#define KEY_PRESS_MAX                4       /* Maximum number of simultaneous non-modifier keys (HID standard) */
#define INPUT_REPORT_KEYS_MAX_LEN    1  /* Report format: 1 byte for single key */

enum { INPUT_REP_KEYS_IDX = 0 };      /* Index for the keyboard input report */

/* Lookup table for button to key mapping - Efficient button-to-key conversion */
static const struct button_key_map {
    uint32_t button_mask;  /* Button bit mask for identification */
    uint8_t key_value;     /* Corresponding HID key code */
} button_key_lookup[] = {
    {DK_BTN1_MSK, 0x04}, /* HID keycode 0x04 = 'a' - Button 1 maps to 'a' */
    {DK_BTN2_MSK, 0x05}, /* HID keycode 0x05 = 'b' - Button 2 maps to 'b' */
    {DK_BTN3_MSK, 0x06}, /* HID keycode 0x06 = 'c' - Button 3 maps to 'c' */
    {DK_BTN4_MSK, 0x07}, /* HID keycode 0x07 = 'd' - Button 4 maps to 'd' */
};

#define BUTTON_KEY_COUNT ARRAY_SIZE(button_key_lookup)  /* Number of button mappings */

/* HID service instance (HIDS) - Global instance for keyboard functionality */
BT_HIDS_DEF(hids_obj, 0, INPUT_REPORT_KEYS_MAX_LEN);

/* TX Power value for advertising - Set to 9 dBm */
static int8_t adv_tx_power = 9;

/* Advertising data: flags, appearance (Keyboard), service UUIDs (HIDS), and TX power - Optimized for efficiency */
static const struct bt_data ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), /* General discoverable, LE only */
    BT_DATA_BYTES(BT_DATA_GAP_APPEARANCE,    /* Device appearance as keyboard (0x03C1) */
                  (CONFIG_BT_DEVICE_APPEARANCE >> 0) & 0xFF,
                  (CONFIG_BT_DEVICE_APPEARANCE >> 8) & 0xFF),
    BT_DATA_BYTES(BT_DATA_UUID16_ALL,        /* Include HID Service UUID in advertising */
                   BT_UUID_16_ENCODE(BT_UUID_HIDS_VAL)),
    BT_DATA(BT_DATA_TX_POWER, &adv_tx_power, sizeof(adv_tx_power))  /* Include TX power in advertising */
};
/* Scan response data: Complete device name - Sent when host requests additional info */
static const struct bt_data sd[] = {
    BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
};



/* Bluetooth connection parameters for better low power consumption - Optimized for battery life */
struct bt_le_conn_param param = { 
    .interval_min = 40, // 50ms (40 * 1.25ms) - Minimum connection interval
    .interval_max = 80, // 100ms (80 * 1.25ms) - Maximum connection interval
    .latency = 4, // slave can skip 4 connection events - Reduces power consumption
    .timeout = 500 // 5 second supervision timeout - Connection lost detection
};

/* Default values for connection parameters
    .interval_min = 30, // 37.5ms (30 * 1.25ms) - Minimum connection interval
    .interval_max = 40, // 50ms (40 * 1.25ms) - Maximum connection interval
    .latency = 0, // slave can skip 0 connection events
    .timeout = 400 // 4 second supervision timeout - Connection lost detection
*/

/* Semaphore used to pause the main loop until a wake-up event (e.g., button) occurs - Power management */
K_SEM_DEFINE(wakeup_sem, 0, 1);

/* Application state structure - Centralized state management */
struct app_state {
    uint8_t is_adv;      /* Flag to indicate if advertising is ongoing - State tracking */
    uint8_t is_con;      /* Flag to indicate if a connection is active - State tracking */
    uint8_t is_dis;      /* Flag to indicate if system gets disconnected - State tracking */
    uint8_t is_sleep;    /* Flag to indicate if system is in sleep mode - Power management */
    struct bt_conn *current_conn;  /* Current connection reference - Tracks active connection for key reporting */
};

/* Global application state instance - Centralized state management */
static struct app_state app_state = {
    .is_adv = false,
    .is_con = false,
    .is_dis = false,
    .is_sleep = false,
    .current_conn = NULL
};

/* Start Bluetooth advertising with TX power - Device discovery and connection setup */
static void advertising_start(void)
{
/* 
 * Create advertising parameters:
 *  - Use connectable advertising (BT_LE_ADV_OPT_CONN) for device connections
 *  - Include TX power information (BT_LE_ADV_OPT_USE_TX_POWER) for better range estimation
 *  - Use fast advertising intervals (BT_GAP_ADV_FAST_INT_MIN_2/MAX_2) for quick discovery
 *  - Undirected advertising (NULL peer address) for general discoverability
 *  
 * BT_LE_ADV_PARAM macro creates a bt_le_adv_param structure with these settings
 */
    const struct bt_le_adv_param *adv_param = BT_LE_ADV_PARAM(
        BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_USE_TX_POWER,  /* Connectable advertising with TX power information */
        BT_GAP_ADV_FAST_INT_MIN_2,    /* Fast advertising interval minimum */
        BT_GAP_ADV_FAST_INT_MAX_2,    /* Fast advertising interval maximum */
        NULL);                        /* No specific peer address (undirected) */
    
    int err = bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
    if (err) {
        if (err == -EALREADY) {
            printk("Advertising continued\n");  /* Already advertising - normal case */
        } else {
            printk("Advertising failed to start (err %d)\n", err);  /* Error handling */
        }
        return;
    }
    app_state.is_adv = true;  /* Set advertising flag - Update state */
}


/* Bluetooth connection callback: handles new connections - Called when a client connects */
static void connected(struct bt_conn *conn, uint8_t err)
{
    char addr[BT_ADDR_LE_STR_LEN];  /* Buffer for storing peer address string */
    bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));  /* Get peer address for logging */
    if (err) {
        printk("Failed to connect (err %u)\n", err);  /* Connection failed */
        return;
    }
    if(app_state.current_conn!=conn){
        if(app_state.current_conn){
            bt_unpair(BT_ID_DEFAULT, bt_conn_get_dst(app_state.current_conn));
        }
        app_state.current_conn = conn;  /* Store connection reference - For key reporting */
    }
    printk("Connected\n");  /* Successful connection */
    bt_le_adv_stop();  /* Stop advertising once connected - No longer need to be discoverable */
    app_state.is_adv = false;  /* Update advertising state */
    app_state.is_con = true;  /* Set connection flag - Update state */
    
    dk_set_led_on(DK_LED2);  /* CON_STATUS_LED on - Visual connection indicator */
    dk_set_led_off(DK_LED1);  /* Advertise led off - Visual state update */

    /* Update connection parameters for latency - Optimize power consumption */
    err = bt_conn_le_param_update(conn, &param);  /* Request connection parameter update */
    if (err) {
        printk("Failed to request connection parameter update (err %d)\n", err);  /* Parameter update failed */
  }

    /* Inform the HID service of the new connection - Initialize HID for this connection */
    int hids_err = bt_hids_connected(&hids_obj, conn);
    if (hids_err) {
        printk("Failed to notify HID service about connection (err %d)\n", hids_err);  /* HID service error */
        return;
    }
    
    /* Request security level 2 (encryption) for HID service - Security requirement */
    err = bt_conn_set_security(conn, BT_SECURITY_L1);
    if (err) {
        printk("Failed to set security level (err %d)\n", err);  /* Security setup failed */
    } else {
        printk("Security level 1 requested\n");  /* Security setup successful */
    }    
}

/* Bluetooth disconnection callback: handle cleanup and wakeup main function from sleep once disconnected */
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
    const bt_addr_le_t *peer = bt_conn_get_dst(conn);  /* Get peer address for cleanup */
    char s[BT_ADDR_LE_STR_LEN];  /* Buffer for storing peer address string */
    bt_addr_le_to_str(peer, s, sizeof(s));  /* Convert address to string for logging */

    printk("Disconnected reason 0x%02X, waking up to restart advertising\n", reason);

    // app_state.current_conn = NULL;  /* Clear connection reference immediately */
    bt_hids_disconnected(&hids_obj, conn);  /* Inform the HID service of the disconnection - Clean up HID state */

    dk_set_led_off(DK_LED2);  /* turn off CON_STATUS_LED if no connections - Visual state update */
    dk_set_led_off(DK_LED1);  /* turn off ADV_STATUS_LED if no connections - Visual state update */

    app_state.is_con = false;  /* Reset connection flag - Update state */
    app_state.is_adv = false;  /* Reset advertising flag - Update state */

    app_state.is_dis = true;  /* Set disconnection flag - Trigger re-advertising */
    if(app_state.is_sleep){
        app_state.is_sleep = false;  /* Reset sleep flag - Wake up from sleep */
        k_sem_give(&wakeup_sem);  /* Wake up the main loop - Resume operation */
    }
}

/* Security level changed callback (e.g. after pairing completes) - Security status monitoring */
static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err)
{
    char addr[BT_ADDR_LE_STR_LEN];  /* Buffer for storing peer address string */
    bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));  /* Get peer address for logging */
    if (!err) {
        printk("Security (encryption) established, level %u\n", level);  /* Security established */
    } else {
        printk("Security failed, level %u, err %d\n", level, err);  /* Security failed */
    }
}

/* Register the connection callbacks - Set up Bluetooth event handlers */
BT_CONN_CB_DEFINE(conn_callbacks) = {
    .connected = connected,        /* Called when connection established */
    .disconnected = disconnected,  /* Called when connection lost */
    .security_changed = security_changed  /* Called when security level changes */
};


/* Initialize the HID service and report map - Set up keyboard functionality */
static void hid_init(void)
{
    /* Define the HID Report Map (descriptor) for a simple keyboard*/
    static const uint8_t report_map[] = {
        0x05, 0x01,       /* Usage Page (Generic Desktop) */
        0x09, 0x06,       /* Usage (Keyboard) */
        0xA1, 0x01,       /* Collection (Application) */
        
        /* Single key (1 byte) - Direct key code  */
        0x95, 0x01,       /* Report Count (1) - One key byte */
        0x75, 0x08,       /* Report Size (8) - 8 bits per key code */
        0x15, 0x00,       /* Logical Minimum (0) - For key release */
        0x25, 0x07,       /* Logical Maximum (7) - Max key code used (0x07 = 'd') */
        0x05, 0x07,       /* Usage Page (Key Codes) - Standard keyboard usage page */
        0x19, 0x00,       /* Usage Minimum (0) - Allow key release (0x00) */
        0x29, 0x07,       /* Usage Maximum (7) - Max key code used (0x07 = 'd') */
        0x81, 0x00,       /* Input (Data, Array) – single key (1 byte) */
        
        0xC0              /* End Collection */
    };

    /* Initialize HID service configuration structure - Set up HID service parameters */
    struct bt_hids_init_param hids_init_obj = { 0 };  /* Initialize to zero */
    hids_init_obj.rep_map.data = report_map;  /* Set report map data */
    hids_init_obj.rep_map.size = sizeof(report_map);  /* Set report map size */

    /* HID information - Standard HID device information */
    hids_init_obj.info.bcd_hid = BASE_USB_HID_SPEC_VERSION;  /* HID specification version */
    hids_init_obj.info.b_country_code = 0x00;  /* Country code (0 = not specified) */
    hids_init_obj.info.flags = BT_HIDS_REMOTE_WAKE | BT_HIDS_NORMALLY_CONNECTABLE;  /* Device capabilities */

    /* Set security requirements for HID service - Encryption required for key data */
    hids_init_obj.inp_rep_group_init.reports[INPUT_REP_KEYS_IDX].perm = BT_GATT_PERM_READ_ENCRYPT;
    
     /* Initialize input report (keyboard keys) - Configure keyboard report */
    struct bt_hids_inp_rep *inp_rep = &hids_init_obj.inp_rep_group_init.reports[INPUT_REP_KEYS_IDX];
    inp_rep->size = INPUT_REPORT_KEYS_MAX_LEN;  /* Set report size */
    inp_rep->id   = 0;  /* Report ID (if using, else 0) - Single report */
    hids_init_obj.inp_rep_group_init.cnt++;  /* Increment report count */
   
    int err;  /* Error code for HID service initialization */
    err = bt_hids_init(&hids_obj, &hids_init_obj);  /* Register the HID service with the stack */
    __ASSERT(err == 0, "HIDS initialization failed (err %d)\n", err);  /* Assert on initialization failure */
}

/* Called when pairing completes - Security pairing status */
static void pairing_complete(struct bt_conn *conn, bool bonded)
{
    char addr_str[BT_ADDR_LE_STR_LEN];  /* Buffer for storing peer address string */
    const bt_addr_le_t *addr = bt_conn_get_dst(conn);  /* Get peer address for logging */

    bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));  /* Convert address to string for logging */
    printk("Pairing completed, bonded=%d\n", bonded);  /* Log pairing result */
}

// Called when pairing fails - Security pairing error handling
static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason) {
  char addr[BT_ADDR_LE_STR_LEN];  /* Buffer for storing peer address string */

  bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));  /* Convert address to string for logging */

  printk("Pairing failed, reason %d %s\n", reason,
         bt_security_err_to_str(reason));  /* Log pairing failure details */
  
  /* If pairing failed due to authentication issues, try to clean up any partial bonds */
  if (reason == BT_SECURITY_ERR_AUTH_FAIL || 
      reason == BT_SECURITY_ERR_PAIR_NOT_SUPPORTED ||
      reason == BT_SECURITY_ERR_PAIR_NOT_ALLOWED) {
    printk("Cleaning up bonds for failed pairing\n");  /* Clean up failed bonds */
    bt_unpair(BT_ID_DEFAULT, bt_conn_get_dst(conn)); //clean up stale/partial bonding info
  }
}

// Register the pairing-complete and pairing-failed callbacks - Security event handlers
static struct bt_conn_auth_info_cb auth_info_cb = {
    .pairing_complete = pairing_complete,  /* Called when pairing succeeds */
    .pairing_failed   = pairing_failed     /* Called when pairing fails */
};



/* Handle key press or release then send updated report directly - Optimized single key handling */
static int hid_buttons_action(uint8_t key, bool is_press) {
  if (is_press) {
    dk_set_led_on(DK_LED3);  /* Turn on activity LED - Visual feedback */
  } else {
    dk_set_led_off(DK_LED3);  /* Turn off activity LED - Visual feedback */
    key = 0;  /* Key release = 0 */
  }
return bt_hids_inp_rep_send(&hids_obj, app_state.current_conn, INPUT_REP_KEYS_IDX, &key, 1, NULL); 
}

/* Application-specific handling of physical buttons: map to keyboard actions - Button event processing
* Called by dk_buttons_init.
* For each button (1..4), on press it sends the corresponding HID key (a/b/c/d),
 and on release clears it.
* Only sends when a connection is active (is_con).
*/
static void handle_button_changed(uint32_t button_state, uint32_t has_changed)
{
    if(has_changed && !app_state.is_con){
        if(app_state.is_sleep){
            app_state.is_sleep = false;  /* Reset sleep flag - Wake up from sleep mode */
            k_sem_give(&wakeup_sem);  /* Wake up the main loop - Resume operation */
        }
    return; // No connection - Don't send keys
    }
    
    /* Process each button using lookup table - Efficient button processing */
    for (int i = 0; i < BUTTON_KEY_COUNT; i++) {
        if (has_changed & button_key_lookup[i].button_mask) {  /* Check if this button changed */
            bool pressed = button_state & button_key_lookup[i].button_mask;  /* Check if button is pressed */
            hid_buttons_action(button_key_lookup[i].key_value, pressed);  /* Send key press/release */
        }
    }
}

int main(void)
{
    printk("Starting Bluetooth LE HID Keyboard example\n");  /* Application startup message */
    int err;  /* Error code for initialization functions */


    /* Initialize buttons and LEDs on the DK - Hardware initialization */
    err = dk_buttons_init(handle_button_changed);  /* Set up button interrupt handler */
    if (err) {
        printk("Buttons init failed (err %d)\n", err);  /* Button initialization error */
    }
    err = dk_leds_init();  /* Initialize LED hardware */
    if (err) {
        printk("LEDs init failed (err %d)\n", err);  /* LED initialization error */
    }

    /* Register BLE pairing callback - Security event handling */
    bt_conn_auth_info_cb_register(&auth_info_cb);

    // Initializes the HID service - Set up keyboard functionality
    hid_init();

    // Initializes settings subsystem if enabled (for bond storage) - Persistent storage
    if (IS_ENABLED(CONFIG_SETTINGS)) {
        err = settings_subsys_init();  /* Initialize settings system */
        if (err) {
        printk("settings_subsys_init failed (err %d)\n", err);  /* Settings initialization error */
        return 0;
        }
    }

    /* Enable Bluetooth subsystem - Initialize BLE stack */
    err = bt_enable(NULL);  /* Start Bluetooth with default parameters */
    if (err) {
        printk("Bluetooth init failed (err %d)\n", err);  /* Bluetooth initialization error */
        return 0;
    }
    printk("Bluetooth initialized\n");  /* Bluetooth startup successful */

    /* Load bond information (if CONFIG_SETTINGS enabled for storing bonds) - Restore pairings */
    if (IS_ENABLED(CONFIG_SETTINGS)) {
        settings_load();  /* Load previously stored bonds */
    }
    

    while (1) {  /* Main application loop - Continuous operation */
        if(!app_state.is_con && !app_state.is_adv){  /* Not connected and not advertising - Start advertising */
            printk("Starting advertising\n");  /* Normal advertising with Public address - DA:F5:C0:BE:DB:AD*/
            advertising_start();  /* Start BLE advertising */
            dk_set_led_on(DK_LED1);  /* Turn on advertising LED - Visual indicator */
            
            /* Wait for connection or timeout, but check for events periodically */
            for (int i = 0; i < 30 && !app_state.is_con && !app_state.is_dis; i++) {
                k_sleep(K_SECONDS(1));  /* Sleep for 1 second at a time */
            }
            
            /* Only stop advertising if not connected and not disconnected */
            if(app_state.is_adv && !app_state.is_con && !app_state.is_dis){
                bt_le_adv_stop();  /* Stop advertising to save power */
                printk("Advertising stopped\n");
                dk_set_led_off(DK_LED1);  /* Turn off advertising LED */
                app_state.is_adv = false;  /* Update advertising state */
            }
            if(app_state.is_dis){  /* Disconnection occurred - Re-advertise */
                app_state.is_dis = false;  /* Clear disconnection flag */
                continue;  // If disconnected, continue to re-advertise
            }
        }
        
        // Drain the semaphore before sleeping - Clear any pending wake events
        while (k_sem_count_get(&wakeup_sem)) {
            k_sem_take(&wakeup_sem, K_NO_WAIT);  /* Remove pending semaphore */
        }
        app_state.is_sleep = true;  // Set sleep flag - Enter sleep mode
        printk("Going to sleep\n");
        k_sem_take(&wakeup_sem, K_FOREVER);  /* Wait for wake event - Power saving sleep */
        if(app_state.is_dis)
            app_state.is_dis = false;  /* Clear disconnection flag if set during sleep */
    }
}
