/* Copyright (c) 2019 Arm Limited
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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.
 */

/*************************************************************************************************/
/*!
 * \file
 * \brief Link manager common implementation file.
 */
/*************************************************************************************************/
#include "ll_math.h"
#include "lmgr_api.h"
#include "lhci_api.h"
#include "wsf_assert.h"
#include "wsf_math.h"
#include <string.h>

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

/*! \brief      Link layer manager system control block (persists with resets). */
lmgrPersistCtrlBlk_t lmgrPersistCb;

/*! \brief      Link layer manager control block (clears with resets). */
lmgrCtrlBlk_t lmgrCb;

/*************************************************************************************************/
/*!
 *  \brief      Set default values.
 *
 *  \return     None.
 *
 *  Restore default values to fields that require initial state after reset. Values that
 *  survive reset are unchanged.
 */
/*************************************************************************************************/
void LmgrSetDefaults(void)
{
  /* Ensure reset shuts down resources. */
  WSF_ASSERT(lmgrCb.numConnEnabled == 0);
  WSF_ASSERT(lmgrCb.advEnabled == FALSE);
  WSF_ASSERT(lmgrCb.numExtAdvEnabled == 0);
  WSF_ASSERT(lmgrCb.numScanEnabled == 0);
  WSF_ASSERT(lmgrCb.numInitEnabled == 0);
  WSF_ASSERT(lmgrCb.numWlFilterEnabled == 0);
  WSF_ASSERT(lmgrCb.numPlFilterEnabled == 0);
  WSF_ASSERT(lmgrCb.testEnabled == FALSE);

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

  lmgrCb.features = lmgrPersistCb.featuresDefault;

  lmgrCb.opModeFlags =
    /* LL_OP_MODE_FLAG_ENA_VER_LLCP_STARTUP | */        /* disabled */
    /* LL_OP_MODE_FLAG_SLV_REQ_IMMED_ACK | */           /* disabled */
    /* LL_OP_MODE_FLAG_BYPASS_CE_GUARD | */             /* disabled */
    /* LL_OP_MODE_FLAG_MST_RETX_AFTER_RX_NACK | */      /* disabled */
    /* LL_OP_MODE_FLAG_MST_IGNORE_CP_RSP | */           /* disabled */
    /* LL_OP_MODE_FLAG_MST_UNCOND_CP_RSP | */           /* disabled */
    /* LL_OP_MODE_FLAG_REQ_SYM_PHY | */                 /* disabled */
    /* LL_OP_MODE_FLAG_ENA_FEAT_LLCP_STARTUP | */       /* disabled */
    /* LL_OP_MODE_FLAG_SLV_DELAY_LLCP_STARTUP | */      /* disabled */
    LL_OP_MODE_FLAG_ENA_SLV_LATENCY_WAKEUP |
    LL_OP_MODE_FLAG_ENA_LEN_LLCP_STARTUP |
    LL_OP_MODE_FLAG_ENA_ADV_DLY |
    LL_OP_MODE_FLAG_ENA_SCAN_BACKOFF |
    LL_OP_MODE_FLAG_ENA_WW |
    LL_OP_MODE_FLAG_ENA_SLV_LATENCY |
    LL_OP_MODE_FLAG_ENA_SLV_AUX_SCAN_RSP_ADI |
    LL_OP_MODE_FLAG_ENA_MST_CIS_NULL_PDU |
    /* LL_OP_MODE_FLAG_ENA_SLV_AUX_IND_ADVA | */        /* disabled */
    LL_OP_MODE_FLAG_ENA_ADV_CHAN_RAND |
    LL_OP_MODE_FLAG_ENA_LLCP_TIMER;

  LhciSetDefaultHciSupCmd(lmgrCb.hciSupCommands);

  lmgrCb.chanClass = LL_CHAN_DATA_ALL;
}

/*************************************************************************************************/
/*!
 *  \brief      Increment reset delay counter.
 *
 *  \return     None.
 */
/*************************************************************************************************/
void LmgrIncResetRefCount(void)
{
  lmgrCb.resetDelayRefCnt++;
}

/*************************************************************************************************/
/*!
 *  \brief      Decrement reset delay counter.
 *
 *  \return     None.
 */
/*************************************************************************************************/
void LmgrDecResetRefCount(void)
{
  WSF_ASSERT(lmgrCb.resetDelayRefCnt);
  lmgrCb.resetDelayRefCnt--;
}

/*************************************************************************************************/
/*!
 *  \brief      Increment whitelist filter enable counter.
 *
 *  \return     None.
 */
/*************************************************************************************************/
void LmgrIncWhitelistRefCount(void)
{
  lmgrCb.numWlFilterEnabled++;
}

/*************************************************************************************************/
/*!
 *  \brief      Decrement whitelist filter enable counter.
 *
 *  \return     None.
 */
/*************************************************************************************************/
void LmgrDecWhitelistRefCount(void)
{
  WSF_ASSERT(lmgrCb.numWlFilterEnabled);
  lmgrCb.numWlFilterEnabled--;
}

/*************************************************************************************************/
/*!
 *  \brief      Increment periodiclist filter enable counter.
 *
 *  \return     None.
 */
/*************************************************************************************************/
void LmgrIncPeriodiclistRefCount(void)
{
  lmgrCb.numPlFilterEnabled++;
}

/*************************************************************************************************/
/*!
 *  \brief      Decrement periodiclist filter enable counter.
 *
 *  \return     None.
 */
/*************************************************************************************************/
void LmgrDecPeriodiclistRefCount(void)
{
  WSF_ASSERT(lmgrCb.numPlFilterEnabled);
  lmgrCb.numPlFilterEnabled--;
}

/*************************************************************************************************/
/*!
 *  \brief  Check that that a given address type is available.
 *
 *  \param  ownAddrType     Own address type.
 *
 *  \return TRUE if value is valid, FALSE otherwise.
 */
/*************************************************************************************************/
bool_t LmgrIsAddressTypeAvailable(uint8_t ownAddrType)
{
  bool_t isAddrTypeValid = TRUE;

  switch (ownAddrType)
  {
    case LL_ADDR_RANDOM:
    case LL_ADDR_RANDOM_IDENTITY:
      if (!lmgrCb.bdAddrRndValid)
      {
        /* Random address not available. */
        isAddrTypeValid = FALSE;
      }
      break;
    case LL_ADDR_PUBLIC:
    case LL_ADDR_PUBLIC_IDENTITY:
    case LL_ADDR_ANONYMOUS:
      /* Addresses are always available. */
      break;
    default:
      /* Invalid type. */
      isAddrTypeValid = FALSE;
  }

  return isAddrTypeValid;
}

/*************************************************************************************************/
/*!
 *  \brief      Check that extended commands have not been issued.
 *
 *  \return     TRUE if allowed, FALSE otherwise.
 */
/*************************************************************************************************/
bool_t LmgrIsLegacyCommandAllowed(void)
{
  if (lmgrCb.useExtCmds)
  {
    return FALSE;
  }

  lmgrCb.useLegacyCmds = TRUE;
  return TRUE;
}

/*************************************************************************************************/
/*!
 *  \brief      Check that legacy commands have not been issued.
 *
 *  \return     TRUE if allowed, FALSE otherwise.
 */
/*************************************************************************************************/
bool_t LmgrIsExtCommandAllowed(void)
{
  if (lmgrCb.useLegacyCmds)
  {
    return FALSE;
  }

  lmgrCb.useExtCmds = TRUE;
  return TRUE;
}

/*************************************************************************************************/
/*!
 *  \brief  Build channel remapping table.
 *
 *  \param  pChanParam  Channel parameters.
 *
 *  \return None.
 */
/*************************************************************************************************/
void LmgrBuildRemapTable(lmgrChanParam_t *pChanParam)
{
  unsigned int chanIdx;
  unsigned int numUsedChan = 0;

  for (chanIdx = 0; chanIdx < sizeof(pChanParam->chanRemapTbl); chanIdx++)
  {
    if (pChanParam->chanMask & (UINT64_C(1) << chanIdx))
    {
      pChanParam->chanRemapTbl[numUsedChan++] = chanIdx;
    }
  }

  WSF_ASSERT(numUsedChan);          /* must have at least one channel */

  pChanParam->numUsedChan = numUsedChan;
}

/*************************************************************************************************/
/*!
 *  \brief  Calculate permutation.
 *
 *  \param  v       Input value.
 *
 *  \return Result of permutation calculation.
 */
/*************************************************************************************************/
static inline uint32_t lmgrCalcPerm(uint32_t v)
{
  uint32_t mask;

  /*          abcd efgh ijkl mnop */
  mask = 0x0F0F0F0F;
  v    = ((v << 8) & mask) | (v & ~mask);

  /*     efgh abcd mnop ijkl xxxx */
  mask = mask ^ (mask << 2);
  v    = ((v << 4) & mask) | (v & ~mask);

  /*  gh efcd abop mnkl ijxx xxxx */
  mask = mask ^ (mask << 1);
  v    = ((v << 2) & mask) | (v & ~mask);

  /* hgf edcb apon mlkj ixxx xxxx */
  return v >> 7;
}

/*************************************************************************************************/
/*!
 *  \brief  Calculate MAM (Multiply, Add, Modulo).
 *
 *  \param  a       Input value a.
 *  \param  b       Input value b.
 *
 *  \return Result of MAM.
 */
/*************************************************************************************************/
static inline uint16_t lmgrCalcMAM(uint16_t a, uint16_t b)
{
  /* (17 x a + b) mod 2^16 */
  return ((17 * a) + b) & 0xFFFFF;
}

/*************************************************************************************************/
/*!
 *  \brief  Compute next channel index, algorithm #2.
 *
 *  \param  pChanParam    Channel parameters.
 *  \param  eventCounter  Event Counter.
 *  \param  numSkip       Number of skip.
 *  \param  calSubEvt     TRUE if calculation of subevent is needed.
 *
 *  \return Next data channel index.
 */
/*************************************************************************************************/
uint8_t LmgrSelectNextChannel(lmgrChanParam_t *pChanParam, uint16_t eventCounter, uint16_t numSkip, bool_t calSubEvt)
{
  unsigned int prn;

  /* Ignore parameter since eventCounter is adjusted by client. */
  (void)numSkip;

  /* Pseudo random number */

  prn = eventCounter ^ pChanParam->chIdentifier;
  prn = lmgrCalcPerm(prn);
  prn = lmgrCalcMAM(prn, pChanParam->chIdentifier);
  prn = lmgrCalcPerm(prn);
  prn = lmgrCalcMAM(prn, pChanParam->chIdentifier);
  prn = lmgrCalcPerm(prn);
  prn = lmgrCalcMAM(prn, pChanParam->chIdentifier);

  pChanParam->prnLast = prn;      /* Used by subevent only. */

  unsigned int prn_e = prn ^ pChanParam->chIdentifier;

  /* unmappedChannel */

  uint16_t unmapChan = LL_MATH_MOD_37(prn_e & 0xFFFF);

  pChanParam->lastChanIdx = unmapChan;

  /* remappingIndex */

  if (!((UINT64_C(1) << unmapChan) & pChanParam->chanMask))
  {
    /* remappingIndex = (N * prn_e) / 2^16 */
    uint8_t remapIdx = (pChanParam->numUsedChan * prn_e) >> 16;
    if (calSubEvt)
    {
      pChanParam->subEvtIdx = remapIdx;
    }
    return pChanParam->chanRemapTbl[remapIdx];
  }
  else
  {
    if (calSubEvt)
    {
      for (unsigned int chanIdx = 0; chanIdx < sizeof(pChanParam->chanRemapTbl); chanIdx++)
      {
        if (pChanParam->chanRemapTbl[chanIdx] == unmapChan)
        {
          pChanParam->subEvtIdx = chanIdx;
          break;
        }
      }
    }
  }

  return unmapChan;
}

/*************************************************************************************************/
/*!
 *  \brief  Compute next subevent channel index, algorithm #2.
 *
 *  \param  pChanParam    Channel parameters.
 *
 *  \return Next data channel index.
 */
/*************************************************************************************************/
uint8_t LmgrSelectNextSubEvtChannel(lmgrChanParam_t *pChanParam)
{
  uint16_t prn = pChanParam->prnLast;
  uint8_t subEvtIdx = pChanParam->subEvtIdx;
  uint8_t d;

  /* Subevent pseudo random number generator. */
  prn = lmgrCalcPerm(prn);
  prn = lmgrCalcMAM(prn, pChanParam->chIdentifier);   /* prn is prnSubEvent_lu*/

  pChanParam->prnLast = prn;

  prn = prn ^ pChanParam->chIdentifier;               /* prn is prnSubEvent_se */

  /* Subevent mapped to used channel index. */
  /* d = max(1, max(min(3, N-5), min(11, (N-10)/2))) */
  d = WSF_MAX(1, WSF_MAX(WSF_MIN(3, (pChanParam->numUsedChan - 5)), WSF_MIN(11, ((pChanParam->numUsedChan - 10) >> 1))));

  /* subEventIndex = (indexofLAstUsedChannel + d + (prnSubEvent_se * (N - 2 * d +1)/ 2 ^16)) mod N */
  uint16_t temp = subEvtIdx + d + ((prn * (pChanParam->numUsedChan - (d << 1) + 1)) >> 16);
  subEvtIdx = temp - (temp / pChanParam->numUsedChan) * pChanParam->numUsedChan;

  pChanParam->subEvtIdx = subEvtIdx;                  /* Update subEvtIdx */

  return pChanParam->chanRemapTbl[subEvtIdx];
}

/*************************************************************************************************/
/*!
 *  \brief  Lmgr Read HCI Supported cmd
 *
 *  \return Supported command bitmask table.
 */
/*************************************************************************************************/
uint8_t * LmgrReadHciSupCmd(void)
{
  return lmgrCb.hciSupCommands;
}
