/*************************************************************************************************/
/*!
 *  \file
 *
 *  \brief  Device manager extended scan module.
 *
 *  Copyright (c) 2016-2018 Arm Ltd. All Rights Reserved.
 *
 *  Copyright (c) 2019 Packetcraft, Inc.
 *  
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
/*************************************************************************************************/

#include "wsf_types.h"
#include "wsf_msg.h"
#include "wsf_assert.h"
#include "wsf_trace.h"
#include "dm_api.h"
#include "dm_scan.h"
#include "dm_conn.h"
#include "dm_dev.h"
#include "dm_main.h"

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

/* extended scan action function table */
static const dmScanAct_t dmScanAct[] =
{
  dmExtScanActStart,
  dmExtScanActStop,
  dmExtScanActTimeout
};

/* extended scan component function interface */
static const dmFcnIf_t dmScanFcnIf =
{
  dmExtScanReset,
  dmExtScanHciHandler,
  dmExtScanMsgHandler
};

/**************************************************************************************************
  Macros
**************************************************************************************************/

/* event being reported used for a legacy advertising PDU or a scan response */
#define DM_ADV_RPT_SCAN_RSP(evtType)    ((((evtType) == HCI_ADV_RPT_LEG_CONN_UNDIRECT_SCAN_RSP)  || \
                                          ((evtType) == HCI_ADV_RPT_LEG_SCAN_UNDIRECT_SCAN_RSP)) || \
                                         ((((evtType) & HCI_ADV_RPT_LEG_ADV_BIT) == 0)           && \
                                          ((evtType)  & HCI_ADV_RPT_SCAN_RSP_BIT)))

/* data status field of extended advertising report event type */
#define DM_ADV_RPT_DATA_STATUS(evtType) (((evtType) & HCI_ADV_RPT_DATA_STATUS_BITS) >> 5)

/*************************************************************************************************/
/*!
 *  \brief  Start extended scanning action function.
 *
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmExtScanActStart(dmScanMsg_t *pMsg)
{
  uint8_t i;
  uint8_t idx;
  uint8_t phyIdx;
  hciExtScanParam_t scanParam[DM_NUM_PHYS];

  if (dmScanCb.scanState == DM_SCAN_STATE_IDLE)
  {
    /* see advertising packets to be received on which PHY */
    for (i = 0, idx = 0; (i < 8) && (idx < DM_NUM_PHYS); i++)
    {
      if (pMsg->apiStart.scanPhys & (1 << i))
      {
        phyIdx = DmScanPhyToIdx(1 << i);

        /* set extended scan parameters for this PHY */
        scanParam[idx].scanType = pMsg->apiStart.scanType[idx];
        scanParam[idx].scanInterval = dmScanCb.scanInterval[phyIdx];
        scanParam[idx].scanWindow = dmScanCb.scanWindow[phyIdx];
        idx++;
      }
    }

    /* set extended scan parameters to be used on primary advertising channel */
    HciLeSetExtScanParamCmd(DmLlAddrType(dmCb.scanAddrType), dmCb.scanFiltPolicy,
                            pMsg->apiStart.scanPhys, scanParam);

    /* initialize scan result filtering */
    if (pMsg->apiStart.mode == DM_DISC_MODE_LIMITED)
    {
      dmScanCb.discFilter = DM_FLAG_LE_LIMITED_DISC;
    }
    else if (pMsg->apiStart.mode == DM_DISC_MODE_GENERAL)
    {
      dmScanCb.discFilter = DM_FLAG_LE_LIMITED_DISC | DM_FLAG_LE_GENERAL_DISC;
    }
    else
    {
      dmScanCb.discFilter = 0;
    }

    dmScanCb.filterNextScanRsp = FALSE;

    /* enable scan */
    dmScanCb.scanState = DM_SCAN_STATE_STARTING;
    HciLeExtScanEnableCmd(TRUE, pMsg->apiStart.filterDup, (pMsg->apiStart.duration / 10), pMsg->apiStart.period);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Stop extended scanning action function.
 *
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmExtScanActStop(dmScanMsg_t *pMsg)
{
  if (dmScanCb.scanState == DM_SCAN_STATE_SCANNING)
  {
    /* disable scan */
    dmScanCb.scanState = DM_SCAN_STATE_STOPPING;
    HciLeExtScanEnableCmd(FALSE, 0, 0, 0);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Handle an scan timeout.
 *
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmExtScanActTimeout(dmScanMsg_t *pMsg)
{
  /* empty */
}

/*************************************************************************************************/
/*!
 *  \brief  Handle an extended advertising report event from HCI.
 *
 *  \param  pEvent  Pointer to HCI callback event structure.
 *
 *  \return None.
 */
/*************************************************************************************************/
static void dmExtScanActHciReport(hciEvt_t *pEvent)
{
  uint8_t       *p;
  static bool_t filtered = FALSE;
  static bool_t firstFrag = TRUE;

  /* ignore if not scanning */
  if (dmScanCb.scanState == DM_SCAN_STATE_SCANNING)
  {
    /* if filtering results for limited or general discovery, and first fragment */
    if ((dmScanCb.discFilter != 0) && firstFrag)
    {
      /* if this is a scan response */
      if (DM_ADV_RPT_SCAN_RSP(pEvent->leExtAdvReport.eventType))
      {
        /* check if filtering next scan response */
        if (dmScanCb.filterNextScanRsp)
        {
          filtered = TRUE;
          dmScanCb.filterNextScanRsp = FALSE;
        }
      }
      /* else it's an advertising response */
      else
      {
        /* find flags in advertising data */
        p = DmFindAdType(DM_ADV_TYPE_FLAGS, pEvent->leExtAdvReport.len, pEvent->leExtAdvReport.pData);
        if (p == NULL)
        {
          /* flags not found */
          filtered = TRUE;
          dmScanCb.filterNextScanRsp = TRUE;
        }
        /* else flags found; check them */
        else if ((p[DM_AD_DATA_IDX] & dmScanCb.discFilter) == 0)
        {
          /* flags do not match discovery mode */
          filtered = TRUE;
          dmScanCb.filterNextScanRsp = TRUE;
        }
      }

      firstFrag = FALSE;
    }

    if (!filtered)
    {
      pEvent->hdr.event = DM_EXT_SCAN_REPORT_IND;
      (*dmCb.cback)((dmEvt_t *) pEvent);
    }

    /* if filtering results for limited or general discovery, and no more fragmented data to come */
    if ((dmScanCb.discFilter != 0) &&
        (DM_ADV_RPT_DATA_STATUS(pEvent->leExtAdvReport.eventType) != HCI_ADV_RPT_DATA_INCMPL_MORE))
    {
      /* reset our flags */
      filtered = FALSE;
      firstFrag = TRUE;
    }
  }
}

/*************************************************************************************************/
/*!
 *  \brief  DM extended scan reset function.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmExtScanReset(void)
{
  hciLeScanTimeoutEvt_t scanTimeout;

  /* if stopping scan or scanning */
  if ((dmScanCb.scanState == DM_SCAN_STATE_STOPPING) ||
      (dmScanCb.scanState == DM_SCAN_STATE_SCANNING))
  {
    /* generate HCI scan timeout event */
    scanTimeout.hdr.event = HCI_LE_SCAN_TIMEOUT_CBACK_EVT;
    scanTimeout.hdr.status = HCI_SUCCESS;

    /* handle the event */
    dmExtScanHciHandler((hciEvt_t *) &scanTimeout);
  }

  /* reset scan module */
  dmScanInit();
}

/*************************************************************************************************/
/*!
 *  \brief  DM extended scan HCI event handler.
 *
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmExtScanHciHandler(hciEvt_t *pEvent)
{
  DM_TRACE_INFO2("dmExtScanHciHandler: event: %d state: %d", pEvent->hdr.event, dmScanCb.scanState);

  if (pEvent->hdr.event == HCI_LE_EXT_ADV_REPORT_CBACK_EVT)
  {
    dmExtScanActHciReport(pEvent);
  }
  else if (pEvent->hdr.event == HCI_LE_SCAN_TIMEOUT_CBACK_EVT)
  {
    dmScanCb.scanState = DM_SCAN_STATE_IDLE;

    /* pass scanning stop event to dev priv */
    dmDevPassEvtToDevPriv(DM_DEV_PRIV_MSG_RPA_STOP, DM_EXT_SCAN_STOP_IND, 0, 0);

    /* call callback */
    pEvent->hdr.event = DM_EXT_SCAN_STOP_IND;
    (*dmCb.cback)((dmEvt_t *) pEvent);
  }
  else if (pEvent->hdr.event == HCI_LE_EXT_SCAN_ENABLE_CMD_CMPL_CBACK_EVT)
  {
    switch (dmScanCb.scanState)
    {
    case DM_SCAN_STATE_STARTING:
      pEvent->hdr.event = DM_EXT_SCAN_START_IND;
      dmScanCb.scanState = pEvent->hdr.status == HCI_SUCCESS? DM_SCAN_STATE_SCANNING : DM_SCAN_STATE_IDLE;
      break;

    case DM_SCAN_STATE_STOPPING:
      pEvent->hdr.event = DM_EXT_SCAN_STOP_IND;
      dmScanCb.scanState = pEvent->hdr.status == HCI_SUCCESS? DM_SCAN_STATE_IDLE : DM_SCAN_STATE_SCANNING;
      break;

    default:
      /* Should never happen */
      WSF_ASSERT(0);
      break;
    }

    /* pass scanning start/stop event to dev priv */
    dmDevPassEvtToDevPriv((pEvent->hdr.event == DM_EXT_SCAN_START_IND) ? \
                           DM_DEV_PRIV_MSG_RPA_START : DM_DEV_PRIV_MSG_RPA_STOP, pEvent->hdr.event,
                           0, 0);

    /* call callback */
    (*dmCb.cback)((dmEvt_t *)pEvent);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  DM scan event handler.
 *
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmExtScanMsgHandler(wsfMsgHdr_t *pMsg)
{
  /* execute action function */
  (*dmScanAct[DM_MSG_MASK(pMsg->event)])((dmScanMsg_t *)pMsg);
}

/*************************************************************************************************/
/*!
 *  \brief  Initialize DM extended scanning.
 *
 *  \return None.
 */
/*************************************************************************************************/
void DmExtScanInit(void)
{
  WsfTaskLock();

  /* set function interface table */
  dmFcnIfTbl[DM_ID_SCAN] = (dmFcnIf_t *) &dmScanFcnIf;

  /* initialize scan/sync modules */
  dmScanInit();
  dmSyncInit();

  WsfTaskUnlock();
}

/*************************************************************************************************/
/*!
 *  \brief  Whether DM scanning is in extended mode.
 *
 *  \return TRUE if DM scanning is in extended mode. FALSE, otherwise.
 */
/*************************************************************************************************/
bool_t DmScanModeExt(void)
{
  return (dmFcnIfTbl[DM_ID_SCAN] == (dmFcnIf_t *) &dmScanFcnIf) ? TRUE : FALSE;
}
