#include "include.h"
#include "utils_hid_usage.h"
#include "ble_hid_service.h"

#define TRACE_EN                1

#if TRACE_EN
#define TRACE(...)              my_printf("[BLE] ");my_printf(__VA_ARGS__)
#define TRACE_R(...)            my_print_r(__VA_ARGS__)
#else // !TRACE_EN
#define TRACE(...)
#define TRACE_R(...)
#endif // TRACE_EN


#define BLE_HID_HANDLE_MAP                      ATT_CHARACTERISTIC_2a4b_01_VALUE_HANDLE
#define BLE_HID_HANDLE_CTRL_POINT               ATT_CHARACTERISTIC_2a4c_01_VALUE_HANDLE
#define BLE_HID_HANDLE_REP_CONSUMER             ATT_CHARACTERISTIC_2a4d_01_VALUE_HANDLE
#define BLE_HID_HANDLE_REP_CONSUMER_CCC         ATT_CHARACTERISTIC_2a4d_01_CLIENT_CONFIGURATION_HANDLE
#define BLE_HID_HANDLE_REP_KEYBOARD             ATT_CHARACTERISTIC_2a4d_02_VALUE_HANDLE
#define BLE_HID_HANDLE_REP_KEYBOARD_CCC         ATT_CHARACTERISTIC_2a4d_02_CLIENT_CONFIGURATION_HANDLE
#define BLE_HID_HANDLE_REP_LED                  ATT_CHARACTERISTIC_2a4d_03_VALUE_HANDLE
//#define BLE_HID_HANDLE_REP_MOUSE                ATT_CHARACTERISTIC_2a4d_04_VALUE_HANDLE
//#define BLE_HID_HANDLE_REP_MOUSE_CCC            ATT_CHARACTERISTIC_2a4d_04_CLIENT_CONFIGURATION_HANDLE

static att_service_handler_t    hid_service;
static uint16_t                 hid_report_client_config_consumer;
static uint16_t                 hid_report_client_config_keyboard;
static uint16_t                 hid_report_client_config_mouse;
static uint16_t                 hid_connect_handle;

static struct {
    bool                        ready;
    hid_rep_info_t              data;
} ble_hid_state AT(.ble_buf.hid.comm);

#if 0
static u8 report_map[] = {
    HID_REP_MAP_CONSUMER(1),
    HID_REP_MAP_KEYBOARD(2),
    HID_REP_MAP_MOUSE(3),
#if 0
    0x05, 0x01,         // USAGE_PAGE (Generic Desktop)
    0x09, 0x02,         // USAGE (Mouse)
    0xA1, 0x01,         // COLLECTION (Application)
    0x85, 0x01,         //     Report Id (1)
    0x09, 0x01,         //     USAGE (Pointer)
    0xA1, 0x00,         //     COLLECTION (Physical)
    0x05, 0x09,         //         USAGE_PAGE (Button)
    0x19, 0x01,         //         USAGE_MINIMUM (Button 1)
    0x29, 0x05,         //         USAGE_MAXIMUM (Button 5)
    0x15, 0x00,         //         LOGICAL_MINIMUM (0)
    0x25, 0x01,         //         LOGICAL_MAXIMUM (1)
    0x75, 0x01,         //         REPORT_SIZE (1)
    0x95, 0x05,         //         REPORT_COUNT (5)
    0x81, 0x02,         //         INPUT (Data,Var,Abs)
    0x75, 0x03,         //         REPORT_SIZE (3)
    0x95, 0x01,         //         REPORT_COUNT (1)
    0x81, 0x03,         //         INPUT (Cnst,Var,Abs)
    0x05, 0x01,         //         USAGE_PAGE (Generic Desktop)
    0x09, 0x38,         //         USAGE (Wheel)
    0x09, 0x30,         //         USAGE (X)
    0x09, 0x31,         //         USAGE (Y)
    0x15, 0x81,         //         LOGICAL_MINIMUM (-127)
    0x25, 0x7F,         //         LOGICAL_MAXIMUM (127)
    0x75, 0x08,         //         REPORT_SIZE (8)
    0x95, 0x03,         //         REPORT_COUNT (3)
    0x81, 0x06,         //         INPUT (Data,Var,Rel)
    0xC0,               //     END_COLLECTION
    0xC0,               // END_COLLECTION
#endif
};
#endif // 0

static void hid_service_event_packet_handle(uint8_t event_type, uint8_t *param, uint16_t size)
{
    switch(event_type){
        case BLE_EVT_CONNECT:{
            memcpy(&hid_connect_handle, &param[7], 2);
            TRACE("Connected - con_handle: 0x%04x\n", hid_connect_handle);
            ble_hid_state.ready = true;
        } break;

        case BLE_EVT_DISCONNECT:{
            uint16_t con_handle;
            memcpy(&con_handle, &param[0], 2);
            TRACE("Disconnected - con_handle: 0x%04x\n", con_handle);
            if (con_handle == hid_connect_handle) {
                hid_connect_handle = 0;

                // TODO: The CCCD value shall be persistent across connections for bonded devices.
                hid_report_client_config_consumer = CCCD_DFT;
                hid_report_client_config_keyboard = CCCD_DFT;
                hid_report_client_config_mouse    = CCCD_DFT;
            }
            ble_hid_state.ready = false;
        } break;

        default:
            break;
    }
}

static uint16_t hid_service_read_callback(uint16_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size)
{
    u8 data_len = 0;
    //u8 hid_report_map_len = sizeof(report_map);

    switch (attribute_handle) {
      #if 0
        case BLE_HID_HANDLE_MAP:
            if (buffer) {
                data_len = (buffer_size < (hid_report_map_len - offset))? buffer_size: (hid_report_map_len - offset);
                memcpy(buffer, report_map + offset, data_len);
                TRACE("Read Request - handle: 0x%04x len: %d data:\n", attribute_handle, buffer_size);
                TRACE_R(buffer, buffer_size);
            } else {
                data_len = hid_report_map_len;
            }
            break;

        case BLE_HID_HANDLE_REP_CONSUMER_CCC:
            if (buffer) {
                TRACE("Read CCC Consumer - val: 0x%04x\n", hid_report_client_config_consumer);
                data_len = 2;
                memcpy(buffer, (u8 *)&hid_report_client_config_consumer, 2);
            } else {
                data_len = 2;
            }
            break;

        case BLE_HID_HANDLE_REP_KEYBOARD_CCC:
            if (buffer) {
                TRACE("Read CCC Keyboard - val: 0x%04x\n", hid_report_client_config_keyboard);
                data_len = 2;
                memcpy(buffer, (u8 *)&hid_report_client_config_keyboard, 2);
            } else {
                data_len = 2;
            }
            break;

        case BLE_HID_HANDLE_REP_MOUSE_CCC:
            if (buffer) {
                TRACE("Read CCC Mouse - val: 0x%04x\n", hid_report_client_config_mouse);
                data_len = 2;
                memcpy(buffer, (u8 *)&hid_report_client_config_mouse, 2);
            } else {
                data_len = 2;
            }
            break;

        case BLE_HID_HANDLE_REP_CONSUMER:
        case BLE_HID_HANDLE_REP_KEYBOARD:
        case BLE_HID_HANDLE_REP_MOUSE:
            if (buffer) {
                /* Response default value that all zere */
                hid_rep_info_t hid_rep_info;
                memset(&hid_rep_info, 0x00, sizeof(hid_rep_info));
                data_len = sizeof(hid_rep_info.pdu);
                TRACE("Read Val - handle: 0x%04x val[%d]: ", attribute_handle, data_len);
                TRACE_R(&hid_rep_info.pdu, data_len);
            } else {
                hid_rep_info_t hid_rep_info;
                data_len = sizeof(hid_rep_info.pdu);
            }
            break;
       #endif // 0
        default:
            TRACE("No process read - handle: 0x%04x\n", attribute_handle);
            break;
    }

	return data_len;
}

static int hid_service_write_callback(uint16_t con_handle, uint16_t attribute_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size)
{
    switch (attribute_handle) {
        case BLE_HID_HANDLE_CTRL_POINT:
            /* 0: suspend  1: exit suspend, maybe not used because some central will disc when sleep */
            TRACE("Write Suspend status - val: 0x%02x\n", buffer[0]);
            break;

        case BLE_HID_HANDLE_REP_CONSUMER_CCC:
            hid_report_client_config_consumer = GET_LE16(buffer);
            TRACE("Write CCC Consumer - val: 0x%04x\n", hid_report_client_config_consumer);
            break;

        case BLE_HID_HANDLE_REP_KEYBOARD_CCC:
            hid_report_client_config_keyboard = GET_LE16(buffer);
            TRACE("Write CCC Keyboard - val: 0x%04x\n", hid_report_client_config_keyboard);
            break;

        //case BLE_HID_HANDLE_REP_MOUSE_CCC:
        //    hid_report_client_config_mouse = GET_LE16(buffer);
        //    TRACE("Write CCC Mouse - val: 0x%04x\n", hid_report_client_config_mouse);
        //    break;

        case BLE_HID_HANDLE_REP_LED:
            break;

        default:
            TRACE("No process write - handle: 0x%04x\n", attribute_handle);
            break;
    }

	return ATT_ERR_NO_ERR;
}

int ble_hid_mouse_report(hid_rep_info_t *param)
{
    //uint16_t handle;
    int ret = -1;

    //if (param->report_id == HID_REP_ID_CONSUMER) {
    //    handle = BLE_HID_HANDLE_REP_CONSUMER;
    //} else if (param->report_id == HID_REP_ID_KEYBOARD) {
    //    handle = BLE_HID_HANDLE_REP_KEYBOARD;
    //} else if (param->report_id == HID_REP_ID_MOUSE) {
    //    handle = BLE_HID_HANDLE_REP_MOUSE;
    //} else {
    //    return ret;
    //}

    // TRACE("Report - handle: 0x%04x data[%d]: ", handle, sizeof(param->pdu));
    // TRACE_R(&param->pdu, sizeof(param->pdu));

    /* Some Central-Device do not subscribe again after being connected back. Do not determine the client config here */
    // TODO: The CCCD value shall be persistent across connections for bonded devices.

    lowpwr_pwroff_delay_reset();
    lowpwr_sleep_delay_reset();

    /*Prevent acl buffer is full before connection really success when report mouse data*/
    //if (!hid_report_client_config_mouse && (handle == BLE_HID_HANDLE_REP_MOUSE)) {
    //    return ret;
    //}

    /*Prevent acl buffer is full before connection really success*/
    //if (!txpkt_is_full(&ble_tx)) {
    //    ret = ble_notify_for_handle(hid_connect_handle, handle, (u8 *)&param->pdu, sizeof(param->pdu));
    //}
    return ret;
}

void ble_hid_service_init(void)
{
    printf("%s\n", __func__);

    hid_service.start_handle   = ATT_SERVICE_1812_START_HANDLE;
    hid_service.end_handle     = ATT_SERVICE_1812_END_HANDLE;
	hid_service.read_callback  = &hid_service_read_callback;
	hid_service.write_callback = &hid_service_write_callback;
	hid_service.event_handler  = &hid_service_event_packet_handle;
	att_server_register_service_handler(&hid_service);

    hid_report_client_config_consumer = CCCD_DFT;
    hid_report_client_config_keyboard = CCCD_DFT;
    hid_report_client_config_mouse    = CCCD_DFT;

    memset(&ble_hid_state, 0x00, sizeof(ble_hid_state));
}

void ble_hid_service_proc(void)
{
    if (hid_connect_handle) {
        if (ble_hid_state.ready) {

        } else {
            /* Retry */
            ble_hid_state.ready = (0 == ble_hid_mouse_report(&ble_hid_state.data));
        }
    }
}

void ble_hid_set_mouse_cccd()
{
    hid_report_client_config_mouse = 1;
}
