#include <stdint.h>
#include "ble.h"
#include "nrf_sdh.h"
#include "nrf_sdh_ble.h"
#include "ble_gap.h"
#include "ble_types.h"
#include "app_error.h"
#include "app_scheduler.h"
#include "app_timer.h"
#include "ser_dbg_sd_str.h"
#include "tp_service.h"
#include "tp_peripheral.h"

#define NRF_LOG_MODULE_NAME logic
#define NRF_LOG_LEVEL       4
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();

static tp_handle_t m_handle;

APP_TIMER_DEF(m_notify_timer);
APP_TIMER_DEF(m_indicate_timer);

static void on_disconnected(void *p_event_data, uint16_t event_size)
{
    tp_adv_start();
}

static void on_phy_update_req(ble_evt_t const *p_ble_evt)
{
    ble_gap_phys_t const phys = {
        .rx_phys = BLE_GAP_PHY_AUTO,
        .tx_phys = BLE_GAP_PHY_AUTO,
    };
    APP_ERROR_CHECK(sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys));
}

static void on_data_length_update_req(ble_evt_t const *p_ble_evt)
{
    ble_gap_data_length_params_t dl_params;

    // Clearing the struct will effectively set members to @ref BLE_GAP_DATA_LENGTH_AUTO.
    memset(&dl_params, 0, sizeof(ble_gap_data_length_params_t));
    APP_ERROR_CHECK(sd_ble_gap_data_length_update(p_ble_evt->evt.gap_evt.conn_handle, &dl_params, NULL));
}

static void on_write(ble_evt_t const *p_ble_evt)
{
    ble_gatts_evt_write_t const *p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;

    NRF_LOG_DEBUG("handle: %d.", p_evt_write->handle);
    NRF_LOG_DEBUG("uuid: 0x%x.", p_evt_write->uuid.uuid);
    NRF_LOG_DEBUG("type: %d.", p_evt_write->uuid.type);
    NRF_LOG_DEBUG("op: %d.", p_evt_write->op);
    NRF_LOG_DEBUG("auth_required: %d.", p_evt_write->auth_required);
    NRF_LOG_DEBUG("offset: %d.", p_evt_write->offset);
    NRF_LOG_DEBUG("len: %d.", p_evt_write->len);
    NRF_LOG_HEXDUMP_DEBUG(p_evt_write->data, p_evt_write->len);

    NRF_LOG_DEBUG("p2c ser handle: %d.", m_handle.p2c.service);
    NRF_LOG_DEBUG("p2c notify value_handle: %d.", m_handle.p2c.notify.value_handle);
    NRF_LOG_DEBUG("p2c notify user_desc_handle: %d.", m_handle.p2c.notify.value_handle);
    NRF_LOG_DEBUG("p2c notify cccd_handle: %d.", m_handle.p2c.notify.value_handle);
    NRF_LOG_DEBUG("p2c notify sccd_handle: %d.", m_handle.p2c.notify.value_handle);

    NRF_LOG_DEBUG("p2c indicate_handle value_handle: %d.", m_handle.p2c.indicate.value_handle);
    NRF_LOG_DEBUG("p2c indicate_handle user_desc_handle: %d.", m_handle.p2c.indicate.value_handle);
    NRF_LOG_DEBUG("p2c indicate cccd: %d.", m_handle.p2c.indicate.value_handle);
    NRF_LOG_DEBUG("p2c indicate sccd: %d.", m_handle.p2c.indicate.value_handle);

    NRF_LOG_DEBUG("c2p ser handle: %d.", m_handle.c2p.service);
    NRF_LOG_DEBUG("c2p r_w value: %d.", m_handle.c2p.r_w.value_handle);
    NRF_LOG_DEBUG("c2p r_w user_desc: %d.", m_handle.c2p.r_w.value_handle);
    NRF_LOG_DEBUG("c2p r_w cccd: %d.", m_handle.c2p.r_w.value_handle);
    NRF_LOG_DEBUG("c2p r_w sccd: %d.", m_handle.c2p.r_w.value_handle);

    NRF_LOG_DEBUG("c2p r_wno value: %d.", m_handle.c2p.r_wno.value_handle);
    NRF_LOG_DEBUG("c2p r_wno user_desc: %d.", m_handle.c2p.r_wno.value_handle);
    NRF_LOG_DEBUG("c2p r_wno cccd: %d.", m_handle.c2p.r_wno.value_handle);
    NRF_LOG_DEBUG("c2p r_wno sccd: %d.", m_handle.c2p.r_wno.value_handle);
    ble_uuid_t p2c_uuid = { .uuid = BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG, .type = BLE_UUID_TYPE_BLE };

    // ble_uuid_t c2p_uuid = { .uuid = BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG, .type = BLE_UUID_TYPE_BLE };

    // Notify, Indicate CCCD.
    if (BLE_UUID_EQ(&p_evt_write->uuid, &p2c_uuid)) {
        if (p_evt_write->data[0] == 01) {
            //Todo. Enable notify.
            APP_ERROR_CHECK(app_timer_start(m_notify_timer, APP_TIMER_TICKS(1), NULL));
        } else if (p_evt_write->data[0] == 02) {
            //Todo. Enable indicate.
            APP_ERROR_CHECK(app_timer_start(m_indicate_timer, APP_TIMER_TICKS(1), NULL));
        } else {
            APP_ERROR_CHECK(app_timer_stop(m_notify_timer));
            APP_ERROR_CHECK(app_timer_stop(m_indicate_timer));
            //Todo. disable notify/indicate.
        }
    }
}

static void ble_evt_handler(ble_evt_t const *p_ble_evt, void *p_context)
{
    NRF_LOG_INFO("p_ble_evt->header.evt_id :%s.", ser_dbg_sd_evt_str_get(p_ble_evt->header.evt_id));
    switch (p_ble_evt->header.evt_id) {
        case BLE_GAP_EVT_CONNECTED:
            break;
        case BLE_GAP_EVT_DISCONNECTED:
            APP_ERROR_CHECK(app_sched_event_put(NULL, 0, on_disconnected));
            break;
        case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
            on_phy_update_req(p_ble_evt);
            break;
        case BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST:
            on_data_length_update_req(p_ble_evt);
            break;
        case BLE_GATTS_EVT_WRITE:
            on_write(p_ble_evt);
            break;
        case BLE_GATTC_EVT_TIMEOUT:
        case BLE_GATTS_EVT_TIMEOUT:
            APP_ERROR_CHECK(
                sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION));
            break;
        default:
            break;
    }
}

static void notify_handler(void *p_context)
{
    if (m_handle.conn_handle == BLE_CONN_HANDLE_INVALID) {
        return;
    }

    static uint32_t data = 0;
    data++;

    uint16_t               len = sizeof(data);
    ble_gatts_hvx_params_t hvx_params;
    memset(&hvx_params, 0, sizeof(hvx_params));

    hvx_params.handle = m_handle.p2c.notify.value_handle;
    hvx_params.type   = BLE_GATT_HVX_NOTIFICATION;
    hvx_params.offset = 0;
    hvx_params.p_len  = &len;
    hvx_params.p_data = (uint8_t *)&data;

    sd_ble_gatts_hvx(m_handle.conn_handle, &hvx_params);
}

static void indicate_handler(void *p_context)
{
    if (m_handle.conn_handle == BLE_CONN_HANDLE_INVALID) {
        return;
    }

    static uint32_t data = 0;
    data++;
    uint16_t len = sizeof(data);

    ble_gatts_hvx_params_t hvx_params;
    memset(&hvx_params, 0, sizeof(hvx_params));

    hvx_params.handle = m_handle.p2c.indicate.value_handle;
    hvx_params.type   = BLE_GATT_HVX_INDICATION;
    hvx_params.offset = 0;
    hvx_params.p_len  = &len;
    hvx_params.p_data = (uint8_t *)&data;

    sd_ble_gatts_hvx(m_handle.conn_handle, &hvx_params);
}

void tp_logic_init(void)
{
    NRF_SDH_BLE_OBSERVER(m_ble_observer, 4, ble_evt_handler, NULL);
    tp_peripheral_init();
    tp_service_init(&m_handle);

    APP_ERROR_CHECK(app_timer_create(&m_notify_timer, APP_TIMER_MODE_REPEATED, notify_handler));
    APP_ERROR_CHECK(app_timer_create(&m_indicate_timer, APP_TIMER_MODE_REPEATED, indicate_handler));

    tp_adv_start();
}