#include <stdint.h>
#include <string.h>
#include "sdk_common.h"
#include "ble.h"
#include "nrf_sdh.h"
#include "nrf_sdh_ble.h"
#include "ble_gap.h"
#include "ble_gatts.h"
#include "tp_service.h"
#include "app_error.h"

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

/* clang-format off */
#define CHAR_INIT_VALUE    "Throughput"
#define BASE_UUID_128BIT   {{0x46,0x5b,0xA5,0xCF,0x78,0x1E,0x44,0xC3,0x8B,0x0C,0x63,0x66,0x39,0x53,0x06,0x40}}
#define PER2CEN_SER_UUID   0x1000
#define NOTIFY_CHAR_UUID   0x1100
#define NOTIFY_DESC_UUID   0x1101
// #define NOTIFY_DESC_UUID   0x2902
#define INDICATE_CHAR_UUID 0x1200
#define INDICATE_DESC_UUID 0x1201
// #define INDICATE_DESC_UUID 0x2902
#define CEN2PER_SER_UUID   0x2000
#define WRITE_UUID         0x2100
#define WRITE_NO_UUID      0x2200
/* clang-format on */

#define NOTIFY_ENABLE       1
#define INDICATE_ENABLE     2
#define WRITE_ENABLE        3
#define WRITE_NO_RSP_ENABLE 4

#define SEC_OPEN               1
#define OPCODE_LENGTH          1
#define HANDLE_LENGTH          2
#define BLE_VALUE_MAX_DATA_LEN (NRF_SDH_BLE_GATT_MAX_MTU_SIZE - OPCODE_LENGTH - HANDLE_LENGTH)

typedef struct {
    uint16_t                  uuid;
    uint16_t                  max_len;
    uint16_t                  init_len;
    uint8_t *                 p_init_value;
    ble_gatt_char_props_t     char_props;
    ble_gatt_char_ext_props_t char_ext_props;
    uint8_t                   read_access;
    uint8_t                   write_access;
    uint8_t                   cccd_write_access;
} ble_add_char_params_t;

typedef struct {
    uint16_t uuid;
    uint16_t max_len;
    uint16_t init_len;
    uint8_t *p_value;
    uint16_t init_offs;
} ble_add_descr_params_t;

static tp_handle_t *mp_handle;

static void add_base_uuid(void)
{
    ble_uuid128_t uuid_128  = BASE_UUID_128BIT;
    uint8_t       uuid_type = BLE_UUID_TYPE_VENDOR_BEGIN;

    APP_ERROR_CHECK(sd_ble_uuid_vs_add(&uuid_128, &uuid_type));
}

static void add_service(uint16_t *p_ser_handle, uint8_t ser_type, uint16_t uuid)
{
    ble_uuid_t p2c_uuid = {
        .uuid = uuid,
        .type = BLE_UUID_TYPE_BLE,
    };
    APP_ERROR_CHECK(sd_ble_gatts_service_add(ser_type, &p2c_uuid, p_ser_handle));
}

static uint32_t set_characteristic(uint16_t                  service_handle,
                                   ble_add_char_params_t *   p_char_props,
                                   ble_gatts_char_handles_t *p_char_handle)
{
    ble_gatts_char_md_t char_md;
    ble_gatts_attr_t    attr_char_value;
    ble_uuid_t          char_uuid;
    ble_gatts_attr_md_t attr_md;
    ble_gatts_attr_md_t cccd_md;

    char_uuid.type = BLE_UUID_TYPE_BLE;
    char_uuid.uuid = p_char_props->uuid;

    memset(&attr_md, 0, sizeof(ble_gatts_attr_md_t));
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm);
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm);
    attr_md.rd_auth = 0;
    attr_md.wr_auth = 0;
    attr_md.vlen    = 0;
    attr_md.vloc    = BLE_GATTS_VLOC_STACK;

    memset(&char_md, 0, sizeof(ble_gatts_char_md_t));
    if ((p_char_props->char_props.notify == 1) || (p_char_props->char_props.indicate == 1)) {
        memset(&cccd_md, 0, sizeof(cccd_md));
        BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm);
        BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);

        cccd_md.vloc      = BLE_GATTS_VLOC_STACK;
        char_md.p_cccd_md = &cccd_md;
    }
    char_md.char_props     = p_char_props->char_props;
    char_md.char_ext_props = p_char_props->char_ext_props;

    memset(&attr_char_value, 0, sizeof(ble_gatts_attr_t));
    attr_char_value.p_uuid    = &char_uuid;
    attr_char_value.p_attr_md = &attr_md;
    attr_char_value.max_len   = p_char_props->max_len;
    if (p_char_props->p_init_value != NULL) {
        attr_char_value.init_len = p_char_props->init_len;
        attr_char_value.p_value  = p_char_props->p_init_value;
    }
    return sd_ble_gatts_characteristic_add(service_handle, &char_md, &attr_char_value, p_char_handle);
}

static uint32_t set_descriptor(uint16_t char_handle, ble_add_descr_params_t *p_descr_props, uint16_t *p_descr_handle)
{
    ble_gatts_attr_t    descr_params;
    ble_uuid_t          desc_uuid;
    ble_gatts_attr_md_t attr_md;

    memset(&descr_params, 0, sizeof(descr_params));
    desc_uuid.type = BLE_UUID_TYPE_BLE;
    desc_uuid.uuid = p_descr_props->uuid;

    descr_params.p_uuid = &desc_uuid;

    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm);
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm);

    attr_md.rd_auth        = 0;
    attr_md.wr_auth        = 0;
    attr_md.vlen           = 0;
    attr_md.vloc           = BLE_GATTS_VLOC_STACK;
    descr_params.p_attr_md = &attr_md;
    descr_params.max_len   = p_descr_props->max_len;

    if (p_descr_props->p_value != NULL) {
        descr_params.init_len  = p_descr_props->init_len;
        descr_params.p_value   = p_descr_props->p_value;
        descr_params.init_offs = p_descr_props->init_offs;
    }

    return sd_ble_gatts_descriptor_add(char_handle, &descr_params, p_descr_handle);
}

static void add_char_notify(uint16_t ser_handle, ble_gatts_char_handles_t *p_char_handle)
{
    ble_add_char_params_t add_char_params;
    memset(&add_char_params, 0, sizeof(add_char_params));

    add_char_params.uuid              = NOTIFY_CHAR_UUID;
    add_char_params.max_len           = BLE_VALUE_MAX_DATA_LEN;
    add_char_params.p_init_value      = "TP";
    add_char_params.init_len          = strlen(add_char_params.p_init_value);
    add_char_params.char_props.notify = 1;

    add_char_params.read_access       = SEC_OPEN;
    add_char_params.write_access      = SEC_OPEN;
    add_char_params.cccd_write_access = SEC_OPEN;

    APP_ERROR_CHECK(set_characteristic(ser_handle, &add_char_params, p_char_handle));
}

#ifdef NOTIFY_HAS_DESC_ENABLE
static void add_notify_desc(uint16_t char_handle, uint16_t *p_descr_handle)
{
    ble_add_descr_params_t add_descr_params;

    add_descr_params.uuid      = NOTIFY_DESC_UUID;
    add_descr_params.max_len   = BLE_VALUE_MAX_DATA_LEN;
    add_descr_params.init_len  = sizeof(uint32_t);
    add_descr_params.p_value   = NULL;
    add_descr_params.init_offs = 0;

    APP_ERROR_CHECK(set_descriptor(char_handle, &add_descr_params, p_descr_handle));
}
#endif

static void add_char_indicate(uint16_t ser_handle, ble_gatts_char_handles_t *p_char_handle)
{
    ble_add_char_params_t add_char_params;
    memset(&add_char_params, 0, sizeof(add_char_params));

    add_char_params.uuid                = INDICATE_CHAR_UUID;
    add_char_params.max_len             = BLE_VALUE_MAX_DATA_LEN;
    add_char_params.p_init_value        = "TP";
    add_char_params.init_len            = strlen(add_char_params.p_init_value);
    add_char_params.char_props.indicate = 1;

    add_char_params.cccd_write_access = SEC_OPEN;

    APP_ERROR_CHECK(set_characteristic(ser_handle, &add_char_params, p_char_handle));
}

#ifdef INDICATE_HAS_DESC_ENABLE
static void add_indicate_desc(uint16_t char_handle, uint16_t *p_descr_handle)
{
    ble_add_descr_params_t add_descr_params;

    add_descr_params.uuid      = INDICATE_DESC_UUID;
    add_descr_params.max_len   = BLE_VALUE_MAX_DATA_LEN;
    add_descr_params.init_len  = sizeof(uint32_t);
    add_descr_params.p_value   = NULL;
    add_descr_params.init_offs = 0;

    APP_ERROR_CHECK(set_descriptor(char_handle, &add_descr_params, p_descr_handle));
}
#endif

static void add_char_rw(uint16_t ser_handle, ble_gatts_char_handles_t *p_char_handle)
{
    ble_add_char_params_t add_char_params;
    memset(&add_char_params, 0, sizeof(add_char_params));

    add_char_params.uuid             = WRITE_UUID;
    add_char_params.max_len          = BLE_VALUE_MAX_DATA_LEN;
    add_char_params.p_init_value     = "TP";
    add_char_params.init_len         = strlen(add_char_params.p_init_value);
    add_char_params.char_props.read  = 1;
    add_char_params.char_props.write = 1;

    add_char_params.read_access  = SEC_OPEN;
    add_char_params.write_access = SEC_OPEN;

    APP_ERROR_CHECK(set_characteristic(ser_handle, &add_char_params, p_char_handle));
}

static void add_char_rw_no(uint16_t ser_handle, ble_gatts_char_handles_t *p_char_handle)
{
    ble_add_char_params_t add_char_params;
    memset(&add_char_params, 0, sizeof(add_char_params));

    add_char_params.uuid                     = WRITE_NO_UUID;
    add_char_params.max_len                  = BLE_VALUE_MAX_DATA_LEN;
    add_char_params.p_init_value             = "TP";
    add_char_params.init_len                 = strlen(add_char_params.p_init_value);
    add_char_params.char_props.read          = 1;
    add_char_params.char_props.write_wo_resp = 1;

    add_char_params.read_access  = SEC_OPEN;
    add_char_params.write_access = SEC_OPEN;

    APP_ERROR_CHECK(set_characteristic(ser_handle, &add_char_params, p_char_handle));
}

static void set_service(void)
{
    add_base_uuid();

    add_service(&mp_handle->p2c.service, BLE_GATTS_SRVC_TYPE_PRIMARY, PER2CEN_SER_UUID);
    add_char_notify(mp_handle->p2c.service, &mp_handle->p2c.notify);
#ifdef NOTIFY_HAS_DESC_ENABLE
    add_notify_desc(mp_handle->p2c.notify.value_handle, &mp_handle->p2c.notify_desc);
#endif
    add_char_indicate(mp_handle->p2c.service, &mp_handle->p2c.indicate);
#ifdef INDICATE_HAS_DESC_ENABLE
    add_indicate_desc(mp_handle->p2c.indicate.value_handle, &mp_handle->p2c.indicate_desc);
#endif
    add_service(&mp_handle->c2p.service, BLE_GATTS_SRVC_TYPE_PRIMARY, CEN2PER_SER_UUID);
    add_char_rw(mp_handle->c2p.service, &mp_handle->c2p.r_w);
    add_char_rw_no(mp_handle->c2p.service, &mp_handle->c2p.r_wno);
}

static void ble_evt_handler(ble_evt_t const *p_ble_evt, void *p_context)
{
    switch (p_ble_evt->header.evt_id) {
        case BLE_GAP_EVT_CONNECTED:
            mp_handle->conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
            break;
        case BLE_GAP_EVT_DISCONNECTED:
            mp_handle->conn_handle = BLE_CONN_HANDLE_INVALID;
            break;
        case BLE_GATTS_EVT_SYS_ATTR_MISSING: {
            ble_gap_evt_t const *const p_gap = &p_ble_evt->evt.gap_evt;

            APP_ERROR_CHECK(sd_ble_gatts_sys_attr_set(p_gap->conn_handle, NULL, 0, 0));
            NRF_LOG_DEBUG("Finished handling conn sec update");
        } break;
        default:
            break;
    }
}

void tp_service_init(tp_handle_t *p_handle)
{
    ASSERT(p_handle != NULL);

    mp_handle = p_handle;

    NRF_SDH_BLE_OBSERVER(m_ble_observer, 3, ble_evt_handler, NULL);
    set_service();
    mp_handle->conn_handle = BLE_CONN_HANDLE_INVALID;
}

void tp_handle_get(tp_handle_t **pp_handle)
{
    *pp_handle = mp_handle;
}