/*
This software is subject to the license described in the license.txt file
included with this software distribution. You may not use this file except in compliance
with this license.

Copyright (c) Dynastream Innovations Inc. 2014
All rights reserved.
*/

#include "ble_controllable_hub.h"
#include <string.h>
#include <stdbool.h>
#include "nrf_error.h"
#include "ble.h"
#include "app_util.h"
#include "asc_pages.h"
#include "asc_events.h"

/**
 * @brief Verify module's initialization status. Returns NRF_ERROR_INVALID_STATE it is not.
 */
#define VERIFY_MODULE_INITIALIZED()                                                               \
        do                                                                                        \
        {                                                                                         \
            if (!m_module_initialized)                                                            \
            {                                                                                     \
                 return (NRF_ERROR_INVALID_STATE);                                                \
            }                                                                                     \
        } while(0)

/**
 * @brief Verify NULL parameters are not passed to API.
 */
#define VERIFY_NOT_NULL_PARAM(PARAM)                                                              \
        if ((PARAM) == NULL)                                                                      \
        {                                                                                         \
            return (NRF_ERROR_NULL);                                                              \
        }
/**@} */


/**@brief 128-bit UUID base List. */
static const ble_uuid128_t m_base_uuid128 =
{
   {
       0x29, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15,
       0xBC, 0xEF, 0x12, 0x24, 0x00, 0x00, 0x00, 0x00
   }
};


#define BLE_CCCD_VALUE_LEN            2                                             /**< The length of a CCCD value. */
#define COMPANY_IDENTIFIER            0x0059                                        /**< Company identifier for Nordic Semiconductor ASA as per www.bluetooth.org. */

#define SERIAL_NUMBER_ADDRESS           ((uint32_t) 0x10000060)     // FICR + 60
#define HUB_DEVICE_NUMBER               (*(uint16_t*) SERIAL_NUMBER_ADDRESS)

#define LOCAL_SERVICE_UUID            0x1525                                        /**< Proprietary UUID for local service. */
#define LOCAL_CHAR_UUID               0x1526                                        /**< Proprietary UUID for local characteristic. */
#define LOCAL_CHAR_CTRLPT_UUID        0x1527                                        /**< Proprietary UUID for local control point characteristic. */
#define APP_REPORT_CHAR_LEN           8                                             /**< Size of the characteristic value being notified (in bytes). */
#define APP_CTRLPT_CHAR_LEN           8                                             /**< Size of the control point characteristic value being notified (in bytes). */
static uint16_t                       m_service_handle;                             /**< Handle of local service (as provided by the BLE stack). */
static uint8_t                        m_char_value[APP_REPORT_CHAR_LEN];            /**< Value of the characteristic that will be sent as a notification to the central. */
static uint8_t                        m_char_ctrlpt_value[APP_CTRLPT_CHAR_LEN];     /**< Value of the control point characteristic that will be sent as a notification to the central. */
static ble_gatts_char_handles_t       m_char_handles;                               /**< Handles of the characteristic (as provided by the BLE stack). */
static ble_gatts_char_handles_t       m_char_ctrlpt_handles;                        /**< Handles of control point characteristic (as provided by the BLE stack). */
static bool                           m_module_initialized = false;                 /**< Value to indicate if module has been initialized. */
static uint32_t                       m_event_flags;                                /**< Event bitfield allowing the application to determine when a command has been received */
static asc_command_data_t             m_last_received_command;                      /**< The last command received over the Bluetooth channel. */

static uint32_t ach_report_char_add(const uint8_t uuid_type)
{
    // Report characteristic
    ble_gatts_char_md_t char_md;
    ble_gatts_attr_md_t cccd_md;
    ble_gatts_attr_md_t attr_md;

    ble_uuid_t          char_uuid;
    ble_gatts_attr_t    attr_char_value;


    // Controlpoint characteristic
    ble_gatts_char_md_t char_ctrlpt_md;
    ble_gatts_attr_md_t cccd_ctrlpt_md;
    ble_gatts_attr_md_t attr_ctrlpt_md;

    ble_uuid_t          char_ctrlpt_uuid;
    ble_gatts_attr_t    attr_char_ctrlpt_value;


    memset(&cccd_md, 0, sizeof(cccd_md));

    cccd_md.vloc = BLE_GATTS_VLOC_STACK;
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm);

    memset(&char_md, 0, sizeof(char_md));

    char_md.char_props.read   = 1;
    char_md.char_props.notify = 1;
    char_md.p_char_user_desc  = NULL;
    char_md.p_char_pf         = NULL;
    char_md.p_user_desc_md    = NULL;
    char_md.p_cccd_md         = &cccd_md;
    char_md.p_sccd_md         = NULL;

    char_uuid.type = uuid_type;
    char_uuid.uuid = LOCAL_CHAR_UUID;

    memset(&attr_md, 0, sizeof(attr_md));

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

    attr_md.vloc    = BLE_GATTS_VLOC_STACK;
    attr_md.rd_auth = 0;
    attr_md.wr_auth = 0;
    attr_md.vlen    = 0;

    memset(&attr_char_value, 0, sizeof(attr_char_value));
//lint --e{545}     
    memset(&m_char_value, 0, APP_REPORT_CHAR_LEN);

    attr_char_value.p_uuid    = &char_uuid;
    attr_char_value.p_attr_md = &attr_md;
    attr_char_value.init_len  = APP_REPORT_CHAR_LEN;
    attr_char_value.init_offs = 0;
    attr_char_value.max_len   = APP_REPORT_CHAR_LEN;
    attr_char_value.p_value   = m_char_value;

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

    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    memset(&cccd_ctrlpt_md, 0, sizeof(cccd_ctrlpt_md));

    cccd_ctrlpt_md.vloc = BLE_GATTS_VLOC_STACK;
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_ctrlpt_md.read_perm);
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_ctrlpt_md.write_perm);

    memset(&char_ctrlpt_md, 0, sizeof(char_ctrlpt_md));

    char_ctrlpt_md.char_props.notify   = 1;
    char_ctrlpt_md.char_props.write    = 1;
    char_ctrlpt_md.p_char_user_desc    = NULL;
    char_ctrlpt_md.p_char_pf           = NULL;
    char_ctrlpt_md.p_user_desc_md      = NULL;
    char_ctrlpt_md.p_cccd_md           = &cccd_ctrlpt_md;
    char_ctrlpt_md.p_sccd_md           = NULL;

    char_ctrlpt_uuid.type = uuid_type;
    char_ctrlpt_uuid.uuid = LOCAL_CHAR_CTRLPT_UUID;

    memset(&attr_ctrlpt_md, 0, sizeof(attr_ctrlpt_md));

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

    attr_ctrlpt_md.vloc    = BLE_GATTS_VLOC_STACK;
    attr_ctrlpt_md.rd_auth = 0;
    attr_ctrlpt_md.wr_auth = 1;
    attr_ctrlpt_md.vlen    = 0;

    memset(&attr_char_ctrlpt_value, 0, sizeof(attr_char_ctrlpt_value));
//lint --e{545}     
    memset(&m_char_ctrlpt_value, 0, APP_CTRLPT_CHAR_LEN);

    attr_char_ctrlpt_value.p_uuid    = &char_ctrlpt_uuid;
    attr_char_ctrlpt_value.p_attr_md = &attr_ctrlpt_md;
    attr_char_ctrlpt_value.init_len  = APP_CTRLPT_CHAR_LEN;
    attr_char_ctrlpt_value.init_offs = 0;
    attr_char_ctrlpt_value.max_len   = APP_CTRLPT_CHAR_LEN;
    attr_char_ctrlpt_value.p_value   = m_char_ctrlpt_value;

    err_code = sd_ble_gatts_characteristic_add(m_service_handle,
                                               &char_ctrlpt_md,
                                               &attr_char_ctrlpt_value,
                                               &m_char_ctrlpt_handles);

    return err_code;
}

uint32_t ble_achs_init(ble_achs_t * p_achs, const ble_achs_init_t * p_achs_init)
{
    VERIFY_NOT_NULL_PARAM(p_achs);
    VERIFY_NOT_NULL_PARAM(p_achs_init);

    uint32_t   err_code;
    ble_uuid_t service_uuid;

    // Invalidate any previous existing connection handle.
    p_achs->conn_handle = BLE_CONN_HANDLE_INVALID;

    service_uuid.uuid = LOCAL_SERVICE_UUID;

    err_code = sd_ble_uuid_vs_add(&m_base_uuid128, &service_uuid.type);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &service_uuid, &m_service_handle);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    err_code = ach_report_char_add(service_uuid.type);

    if (err_code == NRF_SUCCESS)
    {
        m_module_initialized = true;
    }

    return err_code;
}


static bool is_cccd_configured(uint16_t conn_handle)
{
    uint32_t          err_code;
    uint8_t           cccd_value_buf[BLE_CCCD_VALUE_LEN];
    ble_gatts_value_t value;
    bool              is_ctrlpt_notif_enabled = false;

    value.len     = BLE_CCCD_VALUE_LEN;
    value.offset  = 0;
    value.p_value = cccd_value_buf;

    err_code = sd_ble_gatts_value_get(conn_handle,
                                      m_char_ctrlpt_handles.cccd_handle,
                                      &value);

    // TODO: Error codes should be sent back to application indicating that the
    // read of CCCD did not work. No application error handler is currently
    // implemented.
    (void)err_code;

    uint16_t cccd_value = uint16_decode(cccd_value_buf);
    is_ctrlpt_notif_enabled = ((cccd_value & BLE_GATT_HVX_NOTIFICATION) != 0);

    return is_ctrlpt_notif_enabled;
}


static void on_ctrlpt_write(ble_achs_t *            p_achs,
                            ble_gatts_evt_write_t * p_evt_write)
{
    ble_gatts_rw_authorize_reply_params_t auth_reply;

    auth_reply.type                 = BLE_GATTS_AUTHORIZE_TYPE_WRITE;
    auth_reply.params.write.update  = 1;
    auth_reply.params.write.offset  = 0;
    auth_reply.params.write.len     = 0;
    auth_reply.params.write.p_data  = NULL;

    if (is_cccd_configured(p_achs->conn_handle))
    {
        auth_reply.params.write.gatt_status = BLE_GATT_STATUS_SUCCESS;
        uint32_t err_code = sd_ble_gatts_rw_authorize_reply(p_achs->conn_handle, &auth_reply);

        (void)err_code;
    }
    else
    {
        auth_reply.params.write.gatt_status = ACH_CTRLPT_NACK_CCCD_IMPROPERLY_CONFIGURED;
        uint32_t err_code = sd_ble_gatts_rw_authorize_reply(p_achs->conn_handle, &auth_reply);
        (void)err_code;
        return;
    }

    uint8_t op_code = p_evt_write->data[0];
//lint --e{415}         
    uint8_t command = p_evt_write->data[1];

    if (op_code == ACH_OP_COMMAND)
    {
        switch (command)
        {
            case ACH_CMD_REPORTING_MODE_OFF:
                {
                    //Load up the last received command structure so the application
                    //gets recent data when it asks for it
                    m_last_received_command.command = REPORTING_MODE_OFF;
                    m_last_received_command.master_id = EVERY_MASTER_ID;

                    //Set the command received flag so the application will know to ask for
                    //the most recent command.
                    asc_event_set(&m_event_flags, EVENT_ASC_COMMAND_RECEIVED);
                }
                break;

            case ACH_CMD_REPORTING_MODE_ON:
                {
                    //Load up the last received command structure so the application
                    //gets recent data when it asks for it
                    m_last_received_command.command = REPORTING_MODE_ON;
                    m_last_received_command.master_id = EVERY_MASTER_ID;

                    //Set the command received flag so the application will know to ask for
                    //the most recent command.
                    asc_event_set(&m_event_flags, EVENT_ASC_COMMAND_RECEIVED);
                }
                break;

            case ACH_CMD_REPORTING_MODE_WARNINGS:
                {
                    /* this command is not currently supported by this demo
                    //Load up the last received command structure so the application
                    //gets recent data when it asks for it
                    m_last_received_command.command = REPORTING_MODE_WARNINGS;
                    m_last_received_command.master_id = EVERY_MASTER_ID;

                    //Set the command received flag so the application will know to ask for
                    //the most recent command.
                    asc_event_set(&m_event_flags, EVENT_ASC_COMMAND_RECEIVED);
                    */
                }
                break;

            case ACH_CMD_PERI_ON:
                {
                    //Load up the last received command structure so the application
                    //gets recent data when it asks for it
                    m_last_received_command.command = TURN_ON;
//lint --e{415}                                                 
//lint --e{416}                             
                    m_last_received_command.shared_address = p_evt_write->data[2];
//lint --e{415}                                                 
//lint --e{416}                                                 
                    m_last_received_command.master_id = uint16_decode(&p_evt_write->data[4]);
                    m_last_received_command.group_number = NO_GROUPS;/**< @todo can the demo actually use groups? Double check this everywhere*/
                    //Set the command received flag so the application will know to ask for
                    //the most recent command.
                    asc_event_set(&m_event_flags, EVENT_ASC_COMMAND_RECEIVED);
                }
                break;

            case ACH_CMD_PERI_OFF:
                {
                    //Load up the last received command structure so the application
                    //gets recent data when it asks for it
                    m_last_received_command.command = TURN_OFF;
//lint --e{416}                                                 
                    m_last_received_command.shared_address = p_evt_write->data[2];
//lint --e{416}                                                 
                    m_last_received_command.master_id = uint16_decode(&p_evt_write->data[4]);
                    m_last_received_command.group_number = NO_GROUPS;

                    //Set the command received flag so the application will know to ask for
                    //the most recent command.
                    asc_event_set(&m_event_flags, EVENT_ASC_COMMAND_RECEIVED);
                }
                break;

            case ACH_CMD_GROUP_ASSIGN:
                {
                    //Load up the last received command structure so the application
                    //gets recent data when it asks for it
                    m_last_received_command.command = ASSIGN_TO_GROUP;
//lint --e{416}                                                 
                    m_last_received_command.shared_address = p_evt_write->data[2];
//lint --e{416}                                                 
                    m_last_received_command.master_id = uint16_decode(&p_evt_write->data[4]);
                    m_last_received_command.group_number = p_evt_write->data[3];

                    //Set the command received flag so the application will know to ask for
                    //the most recent command.
                    asc_event_set(&m_event_flags, EVENT_ASC_COMMAND_RECEIVED);

                }
                break;

            case ACH_CMD_GROUP_ON:
                {
                    //Load up the last received command structure so the application
                    //gets recent data when it asks for it
                    m_last_received_command.command = TURN_ON;
                    m_last_received_command.shared_address = EVERY_DEVICE_ADDRESS;
//lint --e{416}                                                 
                    m_last_received_command.master_id = uint16_decode(&p_evt_write->data[3]);
                    m_last_received_command.group_number = p_evt_write->data[2];

                    asc_event_set(&m_event_flags, EVENT_ASC_COMMAND_RECEIVED);
                }
                break;

            case ACH_CMD_GROUP_OFF:
                {
                    //Load up the last received command structure so the application
                    //gets recent data when it asks for it
                    m_last_received_command.command = TURN_OFF;
                    m_last_received_command.shared_address = EVERY_DEVICE_ADDRESS;
//lint --e{416}                                                 
                    m_last_received_command.master_id = uint16_decode(&p_evt_write->data[3]);
                    m_last_received_command.group_number = p_evt_write->data[2];

                    asc_event_set(&m_event_flags, EVENT_ASC_COMMAND_RECEIVED);
                }
                break;

            default:
                break;

        } // end switch(command)
    }
}


static void on_rw_authorize_request(ble_achs_t * p_achs, ble_gatts_evt_t * p_gatts_evt)
{
    ble_gatts_evt_rw_authorize_request_t * p_auth_req = &p_gatts_evt->params.authorize_request;

    on_ctrlpt_write(p_achs, &p_auth_req->request.write);
}


void ble_achs_on_ble_evt(ble_achs_t * p_achs, ble_evt_t * p_ble_evt)
{
    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GAP_EVT_CONNECTED:
            p_achs->conn_handle = p_ble_evt->evt.gap_evt.conn_handle;

            // If we are connected start reporting, push a fake reporting mode on command
            m_last_received_command.command = REPORTING_MODE_ON;
            m_last_received_command.master_id = EVERY_MASTER_ID;
            m_last_received_command.group_number = NO_GROUPS;
            m_last_received_command.shared_address = EVERY_DEVICE_ADDRESS;

            asc_event_set(&m_event_flags, EVENT_ASC_COMMAND_RECEIVED);
            break;

        case BLE_GAP_EVT_DISCONNECTED:
            p_achs->conn_handle = BLE_CONN_HANDLE_INVALID;
            break;

        case BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST:
            on_rw_authorize_request(p_achs, &p_ble_evt->evt.gatts_evt);
            break;

        default:
            // No implementation needed.
            break;
    }
}


uint32_t ble_achs_report_send(ble_achs_t * p_achs, ble_achs_report_t * p_report)
{
    VERIFY_MODULE_INITIALIZED();

    // Send value if connected and notifying
    if (p_achs->conn_handle != BLE_CONN_HANDLE_INVALID)
    {
        uint16_t hvx_len = APP_REPORT_CHAR_LEN;
        
        m_char_value[0] = p_report->page_num;
#ifdef TWO_BYTE_SHARED_ADDRESS
        (void)uint16_encode(p_report->shared_address, &m_char_value[1]);
        m_char_value[3] = p_report->value;
        (void)uint16_encode(p_report->master_id, &m_char_value[4]);
#else
        m_char_value[1] = p_report->shared_address;
        m_char_value[2] = p_report->value;
        (void)uint16_encode(p_report->master_id, &m_char_value[3]);
#endif  

        ble_gatts_hvx_params_t hvx_params =
        {
            .handle = m_char_handles.value_handle,
            .type   = BLE_GATT_HVX_NOTIFICATION,
            .p_len  = &hvx_len,
            .p_data = m_char_value
        };

        uint32_t err_code = sd_ble_gatts_hvx(p_achs->conn_handle, &hvx_params);
        if ((err_code == NRF_SUCCESS) && (hvx_len != APP_REPORT_CHAR_LEN))
        {
            err_code = NRF_ERROR_DATA_SIZE;
        }
//lint --e{438}                                                         
    }
    else
    {
        return NRF_ERROR_INVALID_STATE;
    }

    return NRF_SUCCESS;
}


uint32_t ble_achs_events_get(void)
{
    return m_event_flags;
}


void ble_achs_event_clear(uint32_t event)
{
    asc_event_clear(&m_event_flags, event);
}


asc_command_data_t ble_achs_get_last_command(void)
{
    return m_last_received_command;
}
