/* SCG Scene Switch - 4 buttons
 *
 * BLE Configuration mode
 *
 * (C)2025-2026 St(u)dio of Computer Games
 */

#include <stdint.h>
#include <stdbool.h>

#include "boards.h"
#include "ctrl_led.h"
#include "button.h"

#include "nordic_common.h"
#include "nrf.h"
#include "nrf_sdh.h"
#include "nrf_sdh_ble.h"
#include "ble_cfg.h"
#include "ble_gap.h"
#include "ble.h"
#include "ble_advdata.h"
#include "ble_conn_params.h"
#include "nrf_ble_gatt.h"

#include "app_error.h"
#include "app_timer.h"
#include "scene_switch_4_mc.h"

#include "mesh_stack.h"
#include "mesh_config.h"
#include "model_config_file.h"
#include "example_common.h"

#include "log.h"



/*** Definitions */

#define APP_BLE_CONN_CFG_TAG                    1

/* UUID: 73c90000-fd68-4edf-9a9f-0c0b05a08f4d */
#define SCENE_SWITCH_4_SERVICE_UUID_BASE        { 0x4d, 0x8f, 0xa0, 0x05, 0x0b, 0x0c, 0x9f, 0x9a, 0xdf, 0x4e, 0x68, 0xfd, 0x00, 0x00, 0xc9, 0x73 }
#define SCENE_SWITCH_4_SERVICE_UUID             0x0001
#define SCENE_SWITCH_4_CHAR_WRITE_UUID          0x0002

#define APP_ADV_INTERVAL                        64
#define APP_ADV_DURATION                        BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED
#define APP_BLE_OBSERVER_PRIO                   3

#define CFG_DATA_SIZE                           (CLIENT_MODEL_INSTANCE_COUNT * sizeof(scene_switch_4_ble_cfg_t))
#define BLE_CFG_TIMEOUT_MS                      SEC_TO_MS(60)
#define BLE_RESTART_TIMEOUT_MS                  SEC_TO_MS(5)

#define LED_CFG                                 LED_3
#define LED_CFG_PERIOD_MS                       200



/*** Type definitions */

typedef struct __attribute__((packed))
{
    uint16_t scene_number[SCENE_SWITCH_4_SCENES_MAX];
    uint32_t transition_time_ms;
    uint32_t delay_ms;
} scene_switch_4_ble_cfg_t;



/*** Forward declarations */

static void cfg_timeout_handler(void * p_context);
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context);



/*** Global variables */

static const uint8_t *m_button_scenes_handle;

APP_TIMER_DEF(m_cfg_timer_id);

/* advertising */
static uint8_t m_enc_advdata[BLE_GAP_ADV_SET_DATA_SIZE_MAX];
static uint8_t m_enc_scan_rsp_data[BLE_GAP_ADV_SET_DATA_SIZE_MAX];
static ble_gap_adv_data_t m_adv_data = {
    .adv_data = {
        .p_data = m_enc_advdata,
        .len    = sizeof(m_enc_advdata)
    },
    .scan_rsp_data = {
        .p_data = m_enc_scan_rsp_data,
        .len    = sizeof(m_enc_scan_rsp_data)
    }
};
static uint8_t m_adv_handle;


static uint16_t m_service_handle;
static ble_gatts_char_handles_t m_char_handles;


NRF_BLE_GATT_DEF(m_gatt);



/*** Static functions */

static void btn_event_handler(uint32_t button_pin, btn_evt_t btn_evt)
{
    /* suppress the spurious button press that persists after a soft reboot */
}


/* reboot the device if there are no connections */
static void cfg_timeout_handler(void * p_context)
{
    __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "%s():\n", __func__);
    NVIC_SystemReset();
}



/* recevied configuration data */
static void on_config_characteristic_write(ble_gatts_evt_write_t const * p_evt_write)
{
    ret_code_t err_code;
    const scene_switch_4_ble_cfg_t *ble_cfg = (const scene_switch_4_ble_cfg_t *)p_evt_write->data;

    __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "%s(): len=%d\n", __func__, p_evt_write->len);
    if (p_evt_write->len == CFG_DATA_SIZE) {
        for (int j = 0; j < CLIENT_MODEL_INSTANCE_COUNT; j++) {
            scene_switch_4_cfg_t value;

            for (int i = 0; i < SCENE_SWITCH_4_SCENES_MAX; i++) {
                value.scene_number[i] = ble_cfg[j].scene_number[i];
                __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "%s(): scene_number=%d\n", __func__, value.scene_number[i]);
            }
            value.transition_time_ms = ble_cfg[j].transition_time_ms;
            value.delay_ms = ble_cfg[j].delay_ms;

            __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "%s(): transition_time_ms=%d, delay_ms=%d\n", __func__, value.transition_time_ms, value.delay_ms);

            err_code = scene_switch_4_mc_state_set(m_button_scenes_handle[j], &value);
            APP_ERROR_CHECK(err_code);
        }
    }
}


static void ble_stack_init(void)
{
    ret_code_t err_code;

    err_code = nrf_sdh_enable_request();
    APP_ERROR_CHECK(err_code);

    uint32_t ram_start = 0;
    err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_sdh_ble_enable(&ram_start);
    APP_ERROR_CHECK(err_code);

    NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
}


static void gap_params_init(void)
{
    ret_code_t err_code;
    ble_gap_conn_sec_mode_t sec_mode;

    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);

    err_code = sd_ble_gap_device_name_set(&sec_mode,
                                          (const uint8_t *)GAP_DEVICE_NAME,
                                          strlen(GAP_DEVICE_NAME));
    APP_ERROR_CHECK(err_code);
}


static void gatt_init(void)
{
    ret_code_t err_code = nrf_ble_gatt_init(&m_gatt, NULL);
    APP_ERROR_CHECK(err_code);
}


static void services_init(void)
{
    uint32_t err_code;
    ble_uuid_t ble_uuid;
    ble_uuid128_t base_uuid = { SCENE_SWITCH_4_SERVICE_UUID_BASE };
    uint8_t uuid_type;

    /* registering a base 128-bit UUID in the SoftDevice */
    err_code = sd_ble_uuid_vs_add(&base_uuid, &uuid_type);
    APP_ERROR_CHECK(err_code);

    /* add the service itself */
    ble_uuid.type = uuid_type;
    ble_uuid.uuid = SCENE_SWITCH_4_SERVICE_UUID;
    err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &m_service_handle);
    APP_ERROR_CHECK(err_code);

    /* configuring access rights for the characteristic (Unencrypted write / Open) */
    ble_gatts_char_md_t char_md;
    ble_gatts_attr_md_t attr_md;
    ble_gatts_attr_t attr_char_value;

    memset(&char_md, 0, sizeof(char_md));
    char_md.char_props.write = 1; /* write with response */

    memset(&attr_md, 0, sizeof(attr_md));
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm);
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm);
    attr_md.vloc = BLE_GATTS_VLOC_STACK; /* memory is allocated in the SoftDevice stack */

    /* configuring the characteristic itself (24 bytes for 12 scenes) */
    ble_uuid.uuid = SCENE_SWITCH_4_CHAR_WRITE_UUID;

    memset(&attr_char_value, 0, sizeof(attr_char_value));
    attr_char_value.p_uuid = &ble_uuid;
    attr_char_value.p_attr_md = &attr_md;
    attr_char_value.init_len = 0;
    attr_char_value.init_offs = 0;
    attr_char_value.max_len = CFG_DATA_SIZE;

    err_code = sd_ble_gatts_characteristic_add(m_service_handle, &char_md, &attr_char_value, &m_char_handles);
    APP_ERROR_CHECK(err_code);
}


static void advertising_init(void)
{
    ret_code_t err_code;

    /* advertising data settings (device name) */
    ble_advdata_t advdata;
    memset(&advdata, 0, sizeof(advdata));
    advdata.name_type = BLE_ADVDATA_FULL_NAME;
    advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;

    /* initialize the advertizing  parameters structure */
    ble_gap_adv_params_t adv_params;
    memset(&adv_params, 0, sizeof(adv_params));
    adv_params.properties.type = BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED;
    adv_params.interval = APP_ADV_INTERVAL;
    adv_params.duration = APP_ADV_DURATION;

    /* registered and started advertising */
    err_code = ble_advdata_encode(&advdata, m_adv_data.adv_data.p_data, &m_adv_data.adv_data.len);
    APP_ERROR_CHECK(err_code);

    err_code = sd_ble_gap_adv_set_configure(&m_adv_handle, &m_adv_data, &adv_params);
    APP_ERROR_CHECK(err_code);
}


static void advertising_start(void)
{
    ret_code_t err_code;

    err_code = sd_ble_gap_adv_start(m_adv_handle, APP_BLE_CONN_CFG_TAG);
    APP_ERROR_CHECK(err_code);

    ctrl_led_flash(LED_CFG, LED_CFG_PERIOD_MS);
    err_code = app_timer_start(m_cfg_timer_id,
                               APP_TIMER_TICKS(BLE_CFG_TIMEOUT_MS),
                               NULL);
    APP_ERROR_CHECK(err_code);
}


static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
    ret_code_t err_code;

    switch (p_ble_evt->header.evt_id) {
    case BLE_GAP_EVT_CONNECTED:
        __LOG(LOG_SRC_APP, LOG_LEVEL_DBG1, "%s(): connected\n", __func__);
        app_timer_stop(m_cfg_timer_id);
        ctrl_led_set(LED_CFG, true);
        break;

    case BLE_GAP_EVT_DISCONNECTED:
        __LOG(LOG_SRC_APP, LOG_LEVEL_DBG1, "%s(): disconnected\n", __func__);
        ctrl_led_flash(LED_CFG, LED_CFG_PERIOD_MS);
        err_code = app_timer_start(m_cfg_timer_id,
                                   APP_TIMER_TICKS(BLE_RESTART_TIMEOUT_MS),
                                   NULL);
        APP_ERROR_CHECK(err_code);
        break;

    case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
        /* pairing not supported */
        err_code = sd_ble_gap_sec_params_reply(p_ble_evt->evt.gap_evt.conn_handle,
                                               BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP,
                                               NULL,
                                               NULL);
        APP_ERROR_CHECK(err_code);
        break;

    case BLE_GATTS_EVT_WRITE:
        {
            const ble_gatts_evt_write_t *p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;
            if (p_evt_write->handle == m_char_handles.value_handle) {
                on_config_characteristic_write(p_evt_write);
            }
        }
        break;

    default:
        break;
    }
}



/*** Interface functions */

void ble_cfg_init(const uint8_t *button_scenes_handle)
{
    ret_code_t err_code;

    m_button_scenes_handle = button_scenes_handle;
    m_adv_handle = BLE_GAP_ADV_SET_HANDLE_NOT_SET;

    err_code = app_timer_init();
    APP_ERROR_CHECK(err_code);

    ctrl_led_init();
    btn_init(btn_event_handler);

    ble_stack_init();
    err_code = sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE);
    APP_ERROR_CHECK(err_code);

    gap_params_init();
    gatt_init();
    services_init();
    advertising_init();

    err_code = app_timer_create(&m_cfg_timer_id,
                                 APP_TIMER_MODE_SINGLE_SHOT,
                                 cfg_timeout_handler);
    APP_ERROR_CHECK(err_code);
}


void ble_cfg_start(void)
{
    advertising_start();
}
