/**
 *  \file mesh_clients.c
 *
 *
 */

/*
 *  Copyright (C) 2018. Mindtree Limited.
 *  All rights reserved.
 */

/* --------------------------------------------- Header File Inclusion */
#include "mesh_clients.h"

/* --------------------------------------------- Global Definitions */
/* Compilation Switch to have this module print trace on console */
#define MESH_CLIENT_CONSOLE_DEBUG

/* --------------------------------------------- Macros */
#ifdef MESH_CLIENT_CONSOLE_DEBUG
#define MESH_CLIENT_TRC(...) printf(__VA_ARGS__)
#else /* MESH_CLIENT_CONSOLE_DEBUG */
#define MESH_CLIENT_TRC(...)
#endif /* MESH_CLIENT_CONSOLE_DEBUG */

/* --------------------------------------------- External Global Variables */

/* --------------------------------------------- Exported Global Variables */

/* --------------------------------------------- Static Global Variables */
/**
 * This Task ID is the identifier used by the GATT APIs to reference
 * the BLE Mesh Application Task.
 * The definition and usage of this is from the bleMesh.c application
 * file.
 */
extern uint8_t bleMesh_TaskID;

/* --------------------------------------------- Functions */

#define MESH_MAX_CLI_ENV             1

/* Global variable definition */
struct mesh_cli_env_tag mesh_cli_env[MESH_MAX_CLI_ENV];

/* Mesh Provisioning Service Client Related Callbacks */
static mesh_prov_client_cb   * prov_cli_cb;

/* Mesh Proxy Service Client Related Callbacks */
static mesh_proxy_client_cb  * proxy_cli_cb;

/* Track current UUID being Discovered */
static uint16_t mesh_client_curr_dis_uuid;

/* ----------------------------------------------------------------------------
 * Function      : void mesh_client_init(void)
 * ----------------------------------------------------------------------------
 * Description   : Initialize Mesh Client environment
 * Inputs        : None
 * Outputs       : None
 * Assumptions   : None
 * ------------------------------------------------------------------------- */
void mesh_client_init(void)
{
    for (unsigned int i = 0; i < MESH_MAX_CLI_ENV; i++)
    {
        /* Reset the application manager environment */
        memset(&mesh_cli_env[i], 0, sizeof(struct mesh_cli_env_tag));
    }

    mesh_client_curr_dis_uuid = 0x0000;

    prov_cli_cb  = NULL;
    proxy_cli_cb = NULL;
}

/**
 * Routine to update the connection identifier for Mesh.
 */
void mesh_client_update_conidx (uint16_t conidx)
{
    mesh_cli_env[0].conidx = conidx;
}

/**
 * \brief Register Mesh Provisioning Client instance
 *
 * Function registers new Mesh Provisioning Client instance.
 *
 * \param [in] cb               client application callbacks
 *
 * \return None
 *
 */
void mesh_prov_client_init(mesh_prov_client_cb * cb)
{
    /* Register the upper layer provided Provisioning Client Callbacks */
    if (NULL != cb)
    {
        prov_cli_cb = cb;
    }
}

/**
 * \brief Register Mesh Proxy Client instance
 *
 * Function registers new Mesh Proxy Client instance.
 *
 * \param [in] cb               client application callbacks
 *
 * \return None
 *
 */
void mesh_proxy_client_init(mesh_proxy_client_cb * cb)
{
    /* Register the upper layer provided Provisioning Client Callbacks */
    if (NULL != cb)
    {
        proxy_cli_cb = cb;
    }
}

API_RESULT mesh_client_discover_services
           (
               uint16_t conidx,
               uint8_t serv_mode
           )
{
    uint8_t svc_uuid[2];
    bStatus_t ret;

    if (BLEBRR_GATT_PROV_MODE == serv_mode)
    {
        /* UUID is Provisioning Service */
        svc_uuid[0] = (uint8_t)(UUID_MESH_PROVISIONING_SERVICE);
        svc_uuid[1] = (uint8_t)(UUID_MESH_PROVISIONING_SERVICE >> 8);
        /* EM_mem_copy(svc_uuid, MESH_PROV_SERVICE_UUID128, 16); */
        mesh_client_curr_dis_uuid = UUID_MESH_PROVISIONING_SERVICE;
    }
    else
    {
        /* UUID is Proxy Service */
        svc_uuid[0] = (uint8_t)(UUID_MESH_PROXY_SERVICE);
        svc_uuid[1] = (uint8_t)(UUID_MESH_PROXY_SERVICE >> 8);
        /* EM_mem_copy(svc_uuid, MESH_PROXY_SERVICE_UUID128, 16); */
        mesh_client_curr_dis_uuid = UUID_MESH_PROVISIONING_SERVICE;
    }

    /* Discover service by UUID */
    ret = GATT_DiscPrimaryServiceByUUID
          (
              conidx,
              svc_uuid,
              ATT_BT_UUID_SIZE,
              bleMesh_TaskID
          );

    MESH_CLIENT_TRC(
    "Discovery Primiary Service UUID 0x%04X "
    "returned with retval 0x%04X\r\n",
    mesh_client_curr_dis_uuid, ret);

    return (0 == ret) ? API_SUCCESS : API_FAILURE;
}

/* ----------------------------------------------------------------------------
 * Function      : void mesh_client_send_wwr(uint8_t conidx, uint8_t *value,
 *                                                uint16_t handle, uint8_t offset,
 *                                                uint16_t length, uint8_t type)
 * ----------------------------------------------------------------------------
 * Description   : Send a write command or request to the client device
 * Inputs        : - conidx       - Connection index
 *                 - value        - Pointer to value
 *                 - handle       - Attribute handle
 *                 - length       - Length of value
 *                 - type         - Type of write message
 * Outputs       : None
 * Assumptions   : None
 * ------------------------------------------------------------------------- */
void mesh_client_send_wwr
     (
         uint16_t  conidx,
         uint8_t   * value,
         uint16_t  length,
         uint8_t   serv_pref
     )
{
    attWriteReq_t req;
    bStatus_t     ret;

    /* Assign the Handle Based on the Service Preference for Write */
    req.handle = (BLEBRR_GATT_PROV_MODE == serv_pref) ? \
                  mesh_cli_env[0].prov_data_in_hdl :    \
                  mesh_cli_env[0].proxy_data_in_hdl;

    req.len    = length;
    req.sig    = 0x00;
    req.cmd    = 0x01; /* Write witout response */

    osal_memcpy(req.value, value, length);

    ret = GATT_WriteNoRsp(conidx, &req);

     MESH_CLIENT_TRC(
     "Writing data : len 0x%04X : handle 0x%02X : ret 0x%04X\n",
     req.len, req.handle, ret);

    return;
}

/* ----------------------------------------------------------------------------
 * Function      : void mesh_client_config_ntf
 *                      (
 *                          uint16_t  conidx,
 *                          uint8_t   serv_pref,
 *                          uint8_t   flag
 *                      )
 * ----------------------------------------------------------------------------
 * Description   : Send a write command or request to the client device
 * Inputs        : - conidx    - Connection index
 *                 - serv_pref - Provisioning or Proxy Service
 *                 - flag      - enable or disable
 * Outputs       : None
 * Assumptions   : None
 * ------------------------------------------------------------------------- */
API_RESULT mesh_client_config_ntf
           (
               uint16_t  conidx,
               uint8_t   serv_pref,
               uint8_t   flag
           )
{
    attWriteReq_t req;
    bStatus_t     ret;

    /* Assign the Handle Based on the Service Preference for Write */
    /**
     * Increment the Handle by 1 as the CCCD is present in the handle next to
     * the Value handle
     */
    req.handle   =  (BLEBRR_GATT_PROV_MODE == serv_pref) ?    \
                   mesh_cli_env[0].prov_data_out_cccd_hdl : \
                   mesh_cli_env[0].proxy_data_out_cccd_hdl;
    req.len      = 0x02;
    req.sig      = 0x00;
    req.cmd      = 0x00;

    req.value[0] = (true == flag) ? 0x01 : 0x00;
    req.value[1] = 0x00;

    /* Store CCCD Mode and State in global */
    mesh_cli_env[0].curr_notif_state = flag;
    mesh_cli_env[0].curr_notif_mode  = (BLEBRR_GATT_PROV_MODE == serv_pref) ?    \
                                       BLEBRR_GATT_PROV_MODE :                   \
                                       BLEBRR_GATT_PROXY_MODE;

    ret = GATT_WriteCharValue(mesh_cli_env[0].conidx, &req, bleMesh_TaskID);

    MESH_CLIENT_TRC
    ("Writing %d to CCCD 0x%04X for Mode 0x%02X\n",
    flag, req.handle, serv_pref);

    return (0 == ret) ? API_SUCCESS : API_FAILURE;
}

void mesh_client_process_gattMsg
     (
         gattMsgEvent_t *pMsg,
         uint8_t        t_id
     )
{
    bStatus_t ret;
    uint16_t  serv_start_hndl, serv_end_hndl;

    MESH_CLIENT_TRC("Processing GATT Messages:\n");
    MESH_CLIENT_TRC("pMsg->method: 0x%X\n",pMsg->method);
    MESH_CLIENT_TRC("pMsg->hdr.status: 0x%X\n",pMsg->hdr.status);

    /* Cache the Task ID */
    bleMesh_TaskID = t_id;

    /* Process the GATT server message */
    switch ( pMsg->method )
    {
        case ATT_EXCHANGE_MTU_RSP:
            break;

        case ATT_FIND_BY_TYPE_VALUE_RSP:
            {
                if (bleProcedureComplete == pMsg->hdr.status)
                {
                    if (UUID_MESH_PROVISIONING_SERVICE == mesh_client_curr_dis_uuid)
                    {
                        serv_start_hndl = mesh_cli_env[0].prov_start_hdl;
                        serv_end_hndl   = mesh_cli_env[0].prov_end_hdl;
                    }
                    else if (UUID_MESH_PROXY_SERVICE == mesh_client_curr_dis_uuid)
                    {
                        serv_start_hndl = mesh_cli_env[0].proxy_start_hdl;
                        serv_end_hndl   = mesh_cli_env[0].proxy_end_hdl;
                    }
                    else
                    {
                        MESH_CLIENT_TRC( "Mesh Services not Present\r\n");
                    }

                    /* Start Charactertistic Discovery */
                    ret = GATT_DiscAllChars
                          (
                              mesh_cli_env[0].conidx,
                              serv_start_hndl,
                              serv_end_hndl,
                              bleMesh_TaskID
                          );

                    MESH_CLIENT_TRC(
                    "Disc Characteristics btw Handles 0x%04X :: 0x%04X"
                    "returned with retval 0x%04X\r\n",
                    serv_start_hndl, serv_end_hndl, ret);
                }
                else
                {
                    if (pMsg->msg.findByTypeValueRsp.numInfo > 0)
                    {
                        MESH_CLIENT_TRC(
                        "\nServices found :%d\n",
                        pMsg->msg.findByTypeValueRsp.numInfo);

                        MESH_CLIENT_TRC(
                        "Service Handles are 0x%04X :: 0x%04X",
                        pMsg->msg.findByTypeValueRsp.handlesInfo[0].handle,
                        pMsg->msg.findByTypeValueRsp.handlesInfo[0].grpEndHandle);
                        if (mesh_client_curr_dis_uuid == UUID_MESH_PROVISIONING_SERVICE)
                        {
                            mesh_cli_env[0].prov_start_hdl   = pMsg->msg.findByTypeValueRsp.handlesInfo[0].handle;
                            mesh_cli_env[0].prov_end_hdl     = pMsg->msg.findByTypeValueRsp.handlesInfo[0].grpEndHandle;
                            MESH_CLIENT_TRC(
                            "\r\n Prov Service Start Handle is 0x%04X\r\n",
                            mesh_cli_env[0].prov_start_hdl);
                            MESH_CLIENT_TRC(
                            "\r\n Prov Service End Handle is 0x%04X\r\n",
                            mesh_cli_env[0].prov_end_hdl);

                        }
                        else if (mesh_client_curr_dis_uuid == UUID_MESH_PROXY_SERVICE)
                        {
                            mesh_cli_env[0].proxy_start_hdl   = pMsg->msg.findByTypeValueRsp.handlesInfo[0].handle;
                            mesh_cli_env[0].proxy_end_hdl     = pMsg->msg.findByTypeValueRsp.handlesInfo[0].grpEndHandle;

                            MESH_CLIENT_TRC(
                            "\r\nProxy Service Start Handle is 0x%04X\r\n",
                            mesh_cli_env[0].proxy_start_hdl);
                            MESH_CLIENT_TRC(
                            "\r\nProxy Service End Handle is 0x%04X\r\n",
                            mesh_cli_env[0].proxy_end_hdl);
                        }
                    }
                    else
                    {
                        MESH_CLIENT_TRC("\n No Services Found!\n");

                        /**
                         * TODO: Provide Discovery completion Callback.
                         */
                    }
                }
            }
            break;

        case ATT_READ_BY_TYPE_RSP:
            {
                if (bleProcedureComplete == pMsg->hdr.status)
                {
                    uint16_t t_char_start_hndl;

                    if (UUID_MESH_PROVISIONING_SERVICE == mesh_client_curr_dis_uuid)
                    {
                        t_char_start_hndl = mesh_cli_env[0].prov_data_out_hdl;
                        serv_end_hndl     = mesh_cli_env[0].prov_end_hdl;
                    }
                    else if (UUID_MESH_PROXY_SERVICE == mesh_client_curr_dis_uuid)
                    {
                        t_char_start_hndl = mesh_cli_env[0].proxy_data_out_hdl;
                        serv_end_hndl   = mesh_cli_env[0].proxy_end_hdl;
                    }

                    ret = GATT_DiscAllCharDescs
                          (
                              mesh_cli_env[0].conidx,
                              t_char_start_hndl,
                              serv_end_hndl,
                              bleMesh_TaskID
                          );

                    MESH_CLIENT_TRC(
                    "Disc Char Desc btw Handles 0x%04X :: 0x%04X"
                    "returned with retval 0x%04X\r\n",
                    t_char_start_hndl, serv_end_hndl, ret);
                }
                else
                {
                    // Pointer to the pair list data in the GATT response.
                    uint8_t   *pCharPairList;
                    // Will store the start and end handles of the current pair.
                    uint16_t  charStartHandle;
                    // Stores to the UUID of the current pair.
                    uint16_t  charUuid;
                    // Stores what pair the loop is currently processing.
                    uint8_t   currentCharIndex;

                    // Set the pair pointer to the first pair.
                    pCharPairList = pMsg->msg.readByTypeRsp.dataList;

                    // Iterate through all three pairs found.
                    for(currentCharIndex = 0; currentCharIndex < pMsg->msg.readByTypeRsp.numPairs ; currentCharIndex++)
                    {
                      /* uint16_t* p_chars_hdl = pservie->chars_hdl; */

                      // Extract the starting handle, ending handle, and UUID of the current characteristic.
                      charStartHandle = BUILD_UINT16(pCharPairList[3], pCharPairList[4]);
                      charUuid        = BUILD_UINT16(pCharPairList[5], pCharPairList[6]);
                      MESH_CLIENT_TRC(
                      "Chars found handle is %d, uuid is %x\n",
                      charStartHandle, charUuid);

                      switch (charUuid)
                      {
                          /* "Mesh Provisioning Data IN" */
                          case UUID_MESH_PROVISIONING_DATA_IN:
                              mesh_cli_env[0].prov_data_in_hdl = charStartHandle;
                              break;
                          /* "Mesh Provisioning Data OUT" */
                          case UUID_MESH_PROVISIONING_DATA_OUT:
                              mesh_cli_env[0].prov_data_out_hdl = charStartHandle;
                              mesh_cli_env[0].prov_data_out_cccd_hdl = charStartHandle + 1;
                              break;
                          /* "Mesh Proxy Data IN" */
                          case UUID_MESH_PROXY_DATA_IN:
                              mesh_cli_env[0].proxy_data_in_hdl = charStartHandle;
                              break;
                          /* "Mesh Proxy Data OUT" */
                          case UUID_MESH_PROXY_DATA_OUT:
                              mesh_cli_env[0].proxy_data_out_hdl = charStartHandle;
                              mesh_cli_env[0].proxy_data_out_cccd_hdl = charStartHandle + 1;
                              break;
                      }

                      // Increment the pair pointer to the next pair.
                      pCharPairList += 5 + 2;
                    }
                }
            }
            break;

        case ATT_FIND_INFO_RSP:
            {
                if (bleProcedureComplete == pMsg->hdr.status)
                {
                    /**
                     * TODO: Provide Discovery completion Callback.
                     */
                }
                else
                {
                    if ( (pMsg->msg.findInfoRsp.numInfo > 0) &&
                      (pMsg->msg.findInfoRsp.format == ATT_HANDLE_BT_UUID_TYPE) )
                    {
                        // This will keep track of the current pair being processed.
                        uint8_t currentPair;

                        // Iterate through the pair list.
                        for(currentPair = 0; currentPair < pMsg->msg.findInfoRsp.numInfo; currentPair++)
                        {
                            // Check if the pair is a CCCD.
                            uint16_t uuid = BUILD_UINT16(pMsg->msg.findInfoRsp.info.btPair[currentPair].uuid[0], pMsg->msg.findInfoRsp.info.btPair[currentPair].uuid[1]);
                            if (uuid == GATT_CLIENT_CHAR_CFG_UUID)
                            {
                                MESH_CLIENT_TRC(
                                "CCCD is found at handle 0x%X for Mode %d\n",
                                pMsg->msg.findInfoRsp.info.btPair[currentPair].handle, blebrr_gatt_mode_get());
                                if (BLEBRR_GATT_PROV_MODE == blebrr_gatt_mode_get())
                                {
                                    mesh_cli_env[0].prov_data_out_cccd_hdl = pMsg->msg.findInfoRsp.info.btPair[currentPair].handle;
                                }
                                else
                                {
                                    mesh_cli_env[0].proxy_data_out_cccd_hdl = pMsg->msg.findInfoRsp.info.btPair[currentPair].handle;
                                }
                            }
                        }
                     }
                }
            }
            break;

        case ATT_HANDLE_VALUE_NOTI:
            {
                uint16_t notifHandle = pMsg->msg.handleValueNoti.handle;

                if (pMsg->msg.handleValueNoti.len > 0)
                {
                    /* Check the Attribute Handle:
                     * - If Provisioning Data Out Handle: then call the Prov Callback
                     * - If Proxy Data Out Handle: the ncall the Proxy Callback
                     */
                    if (mesh_cli_env[0].prov_data_out_hdl == notifHandle)
                    {
                        if (NULL != prov_cli_cb)
                        {
                            prov_cli_cb->mesh_prov_data_out_notif
                            (
                                mesh_cli_env[0].conidx,
                                pMsg->msg.handleValueNoti.len,
                                pMsg->msg.handleValueNoti.value
                            );
                        }
                    }
                    else if (mesh_cli_env[0].proxy_data_out_hdl == notifHandle)
                    {
                        if (NULL != proxy_cli_cb)
                        {
                            proxy_cli_cb->mesh_proxy_data_out_notif
                            (
                                mesh_cli_env[0].conidx,
                                pMsg->msg.handleValueNoti.len,
                                pMsg->msg.handleValueNoti.value
                            );
                        }
                    }
                }
            }
            break;

        case ATT_WRITE_RSP:
            {
                if (BLEBRR_GATT_PROV_MODE == mesh_cli_env[0].curr_notif_mode)
                {
                    /* Call the Prov CCCD ntf complete Callback */
                    if (NULL != prov_cli_cb)
                    {
                        prov_cli_cb->mesh_prov_ntf_status
                        (
                            mesh_cli_env[0].conidx,
                            mesh_cli_env[0].curr_notif_state,
                            0x00
                        );
                    }
                }
                else if (BLEBRR_GATT_PROXY_MODE == mesh_cli_env[0].curr_notif_mode)
                {
                    /* Call the Proxy CCCD ntf complete Callback */
                    if (NULL != proxy_cli_cb)
                    {
                        proxy_cli_cb->mesh_proxy_ntf_status
                        (
                            mesh_cli_env[0].conidx,
                            mesh_cli_env[0].curr_notif_state,
                            0x00
                        );
                    }
                }
                else
                {
                    /* DO Nothing ! */
                }
            }
            break;

        case ATT_ERROR_RSP:
            {
                attErrorRsp_t* perr = &(pMsg->msg.errorRsp);
                MESH_CLIENT_TRC(
                "\nATT_ERROR_RSP 0x%x, 0x%x, 0x%x\n",
                perr->errCode, perr->handle, perr->reqOpcode);
            }
            break;

        default:
            break;
    }

    (void)ret;
}