/*************************************************************************************************/
/*!
 *  \file   medc_plx.c
 *
 *  \brief  Health/medical collector, Pulse Oximeter profile
 *
 *          $Date: 2016-06-03 10:24:08 -0700 (Fri, 03 Jun 2016) $
 *          $Revision: 7329 $
 *
 *  Copyright (c) 2016-2017 ARM Ltd., all rights reserved.
 *  ARM Ltd. confidential and proprietary.
 *
 *  IMPORTANT.  Your use of this file is governed by a Software License Agreement
 *  ("Agreement") that must be accepted in order to download or otherwise receive a
 *  copy of this file.  You may not use or copy this file for any purpose other than
 *  as described in the Agreement.  If you do not agree to all of the terms of the
 *  Agreement do not use this file and delete all copies in your possession or control;
 *  if you do not have a copy of the Agreement, you must contact ARM Ltd. prior
 *  to any use, copying or further distribution of this software.
 */
/*************************************************************************************************/

#include <string.h>
#include "wsf_types.h"
#include "wsf_msg.h"
#include "wsf_trace.h"
#include "wsf_assert.h"
#include "dm_api.h"
#include "att_api.h"
#include "app_cfg.h"
#include "app_api.h"
#include "app_db.h"
#include "app_ui.h"
#include "app_main.h"
#include "svc_ch.h"
#include "plxpc_api.h"
#include "medc_main.h"

/**************************************************************************************************
  ATT Client Discovery Data
**************************************************************************************************/

/* Start of cached health pulse oximeter service handles; begins after DIS */
#define MEDC_DISC_PLXS_START         (MEDC_DISC_DIS_START + DIS_HDL_LIST_LEN)

/* Total cached handle list length */
#define MEDC_DISC_HDL_LIST_LEN      (MEDC_DISC_PLXS_START + PLXPC_PLXS_HDL_LIST_LEN)

/*! Pointers into handle list for health pulse oximeter service handles */
static uint16_t *pMedcPlxsHdlList = &medcCb.hdlList[MEDC_DISC_PLXS_START];

/* sanity check:  make sure handle list length is <= app db handle list length */
WSF_CT_ASSERT(MEDC_DISC_HDL_LIST_LEN <= APP_DB_HDL_LIST_LEN);

/* Default MTU */
#define MEDC_PLX_DEFAULT_MTU        50

/**************************************************************************************************
  ATT Client Configuration Data
**************************************************************************************************/

/* List of characteristics to configure after service discovery */
static const attcDiscCfg_t medcCfgPlxsList[] = 
{  
  /* Read:  Features */
  {NULL, 0, PLXPC_PLXS_PLXF_HDL_IDX},

  /* Write:  Spot Check CCC descriptor  */
  {medcCccIndVal, sizeof(medcCccIndVal), PLXPC_PLXS_PLXSC_CCC_HDL_IDX},

  /* Write:  Continuous Measurement CCC descriptor  */
  {medcCccNtfVal, sizeof(medcCccNtfVal), PLXPC_PLXS_PLXC_CCC_HDL_IDX},

  /* Write:  Record Access Control Point CCC descriptor  */
  {medcCccIndVal, sizeof(medcCccIndVal), PLXPC_PLXS_RACP_CCC_HDL_IDX},
};

/* Characteristic configuration list length */
#define MEDC_CFG_PLXS_LIST_LEN   (sizeof(medcCfgPlxsList) / sizeof(attcDiscCfg_t))

/**************************************************************************************************
  Local Variables
**************************************************************************************************/

/* Control block */
static struct
{
  wsfTimer_t  racpTimer;          /*! RACP procedure timer */
  bool_t      inProgress;         /*! RACP procedure in progress */
  bool_t      restoreConnection;  /*! Indicates if a dropped connection should be restored */
} medcPlxCb;

/**************************************************************************************************
  Local Functions
**************************************************************************************************/

static void medcPlxpInit(void);
static bool_t medcPlxpDiscover(dmConnId_t connId);
static void medcPlxpConfigure(dmConnId_t connId, uint8_t status);
static void medcPlxpProcMsg(wsfMsgHdr_t *pMsg);
static void medcPlxpBtn(dmConnId_t connId, uint8_t btn);

/**************************************************************************************************
  Global Variables
**************************************************************************************************/

/*! profile interface pointer */
medcIf_t medcPlxpIf =
{
  medcPlxpInit,
  medcPlxpDiscover,
  medcPlxpConfigure,
  medcPlxpProcMsg,
  medcPlxpBtn
};

/**************************************************************************************************
  External Variables
**************************************************************************************************/

/*! MEDC application master configuration */
extern const appMasterCfg_t medcMasterCfg;

/*! App connection control block */
extern appConnCb_t appConnCb[DM_CONN_MAX];

/*************************************************************************************************/
/*!
 *  \fn     medcPlxsValueUpdate
 *        
 *  \brief  Process a received ATT read response, notification, or indication.
 *
 *  \param  pMsg    Pointer to ATT callback event message.
 *
 *  \return None.
 */
/*************************************************************************************************/
static void medcPlxsValueUpdate(attEvt_t *pMsg)
{
  if (pMsg->hdr.status == ATT_SUCCESS)
  {
    /* determine which profile the handle belongs to; start with most likely */
    
    /* pulse oximeter */
    if (PlxpcPlxsValueUpdate(pMedcPlxsHdlList, pMsg) == ATT_SUCCESS)    
    {
      return;
    }

    /* device information */
    if (DisValueUpdate(pMedcDisHdlList, pMsg) == ATT_SUCCESS)
    {
      return;
    }

    /* GATT */
    if (GattValueUpdate(pMedcGattHdlList, pMsg) == ATT_SUCCESS)    
    {
      return;
    }
  }
} 

/*************************************************************************************************/
/*!
 *  \fn     medcPlxpProcMsg
 *        
 *  \brief  Process messages from the event handler.
 *
 *  \param  pMsg    Pointer to message.
 *
 *  \return None.
 */
/*************************************************************************************************/
static void medcPlxpProcMsg(wsfMsgHdr_t *pMsg)
{
  appConnCb_t *pCb;

  switch(pMsg->event)
  {
    case  DM_CONN_OPEN_IND:
      AttcMtuReq((dmConnId_t) pMsg->param, MEDC_PLX_DEFAULT_MTU);
      break;
   
    case DM_CONN_CLOSE_IND:
      /* if procedure in progress stop procedure timer */
      if (medcPlxCb.inProgress)
      {
        medcPlxCb.inProgress = FALSE;
        WsfTimerStop(&medcPlxCb.racpTimer);
      }

      if (medcPlxCb.restoreConnection == TRUE)
      {
        medcCb.autoConnect = TRUE;
        AppScanStart(medcMasterCfg.discMode, medcMasterCfg.scanType,
                       medcMasterCfg.scanDuration);
      }
      break;
      
    case DM_SEC_ENCRYPT_IND:
      if (medcPlxCb.restoreConnection == TRUE)
      {
        AppDiscConfigure((dmConnId_t) pMsg->param, HCI_SUCCESS, MEDC_CFG_PLXS_LIST_LEN,
                         (attcDiscCfg_t *) medcCfgPlxsList,
                         PLXPC_PLXS_HDL_LIST_LEN, pMedcPlxsHdlList);

        medcPlxCb.restoreConnection = FALSE;
      }
      break;

    case DM_SEC_ENCRYPT_FAIL_IND:
      if (medcPlxCb.restoreConnection == TRUE)
      {
        /* look up app connection control block from DM connection ID */
        pCb = &appConnCb[pMsg->param - 1];
      
        pCb->initiatingSec = FALSE;

        /* if database record handle valid */
        if (pCb->dbHdl != APP_DB_HDL_NONE)
        {     
          AppDbDeleteRecord(pCb->dbHdl);
          pCb->dbHdl = APP_DB_HDL_NONE;
        }

        AppMasterSecurityReq((dmConnId_t) pMsg->param);
      }
      break;

    case ATTC_READ_RSP:
    case ATTC_HANDLE_VALUE_NTF:
      medcPlxsValueUpdate((attEvt_t *) pMsg);
      break;

    case ATTC_WRITE_RSP:
      /* if write to RACP was successful, start procedure timer */
      if ((((attEvt_t *) pMsg)->hdr.status == ATT_SUCCESS) &&
          (((attEvt_t *) pMsg)->handle == pMedcPlxsHdlList[PLXPC_PLXS_RACP_HDL_IDX]))
      {
        medcPlxCb.inProgress = TRUE;
        medcPlxCb.racpTimer.msg.param = pMsg->param;    /* conn ID */
        WsfTimerStartSec(&medcPlxCb.racpTimer, ATT_MAX_TRANS_TIMEOUT);
      }
      break;
    
    case ATTC_HANDLE_VALUE_IND:
      /* if procedure in progress stop procedure timer */
      if (medcPlxCb.inProgress)
      {
        medcPlxCb.inProgress = FALSE;
        WsfTimerStop(&medcPlxCb.racpTimer);
      }

      medcPlxsValueUpdate((attEvt_t *) pMsg);
      break;

    case MEDC_TIMER_IND:
      /* if procedure in progress then close connection */
      if (medcPlxCb.inProgress && pMsg->param != DM_CONN_ID_NONE)
      {
        medcPlxCb.inProgress = FALSE;

        /* if configured to disconnect upon ATT transaction timeout */
        if (pAppCfg->disconnect)
        {
          AppConnClose((dmConnId_t)pMsg->param);
        }
      }
      break;

    default:
      break;
  }
}

/*************************************************************************************************/
/*!
 *  \fn     medcPlxpInit
 *        
 *  \brief  Profile initialization function.
 *
 *  \return None.
 */
/*************************************************************************************************/
static void medcPlxpInit(void)
{
  /* set handle list length */
  medcCb.hdlListLen = MEDC_DISC_HDL_LIST_LEN;
  
  /* set autoconnect UUID */
  medcCb.autoUuid[0] = ATT_UUID_PULSE_OXIMITER_SERVICE;

  /* initialize timer */
  medcPlxCb.racpTimer.handlerId = medcCb.handlerId;
  medcPlxCb.racpTimer.msg.event = MEDC_TIMER_IND;
}

/*************************************************************************************************/
/*!
 *  \fn     medcPlxpDiscover
 *        
 *  \brief  Discover service for profile.
 *
 *  \param  connId    Connection identifier.
 *
 *  \return TRUE - finished discovering services. FALSE - more services are to be discovered.
 */
/*************************************************************************************************/
static bool_t medcPlxpDiscover(dmConnId_t connId)
{
  /* discover health pulse oximeter service */
  PlxpcPlxsDiscover(connId, pMedcPlxsHdlList);

  return TRUE;
}

/*************************************************************************************************/
/*!
 *  \fn     medcPlxpConfigure
 *        
 *  \brief  Configure service for profile.
 *
 *  \param  connId    Connection identifier.
 *  \param  status    APP_DISC_CFG_START or APP_DISC_CFG_CONN_START.
 *
 *  \return None.
 */
/*************************************************************************************************/
static void medcPlxpConfigure(dmConnId_t connId, uint8_t status)
{
  /* configure health pulse oximeter service */
  AppDiscConfigure(connId, status, MEDC_CFG_PLXS_LIST_LEN,
                   (attcDiscCfg_t *) medcCfgPlxsList,
                   PLXPC_PLXS_HDL_LIST_LEN, pMedcPlxsHdlList);
}

/*************************************************************************************************/
/*!
 *  \fn     medcPlxpBtn
 *        
 *  \brief  Handle a button press.
 *
 *  \param  connId    Connection identifier.
 *  \param  btn       Button press.
 *
 *  \return None.
 */
/*************************************************************************************************/
static void medcPlxpBtn(dmConnId_t connId, uint8_t btn)
{
  /* button actions when connected */
  if (connId != DM_CONN_ID_NONE)
  {
    switch (btn)
    {
      case APP_UI_BTN_1_MED:
        medcPlxCb.restoreConnection = TRUE;
        AppConnClose(connId);
        break;

      case APP_UI_BTN_2_SHORT:
        PlxpcPlxsRacpSend(connId, pMedcPlxsHdlList[PLXPC_PLXS_RACP_HDL_IDX], CH_RACP_OPCODE_DELETE, CH_RACP_OPERATOR_ALL);
        break;

      case APP_UI_BTN_2_MED:
        PlxpcPlxsRacpSend(connId, pMedcPlxsHdlList[PLXPC_PLXS_RACP_HDL_IDX], CH_RACP_OPCODE_REPORT, CH_RACP_OPERATOR_ALL);
        break;
      
      case APP_UI_BTN_2_LONG:
        PlxpcPlxsRacpSend(connId, pMedcPlxsHdlList[PLXPC_PLXS_RACP_HDL_IDX], CH_RACP_OPCODE_REPORT_NUM, CH_RACP_OPERATOR_ALL);
        break;
      
      case APP_UI_BTN_2_EX_LONG:
        PlxpcPlxsRacpSend(connId, pMedcPlxsHdlList[PLXPC_PLXS_RACP_HDL_IDX], CH_RACP_OPCODE_ABORT, CH_RACP_OPERATOR_ALL);
        break;
    }
  }
}
