/*! *********************************************************************************
 * \addtogroup SHELL GAP
 * @{
 ********************************************************************************** */
/*!
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * All rights reserved.
 * \file app.c
 * This file is the source file for the GAP Shell module
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of Freescale Semiconductor, Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/************************************************************************************
 *************************************************************************************
 * Include
 *************************************************************************************
 ************************************************************************************/
/* Framework / Drivers */
#include "TimersManager.h"
#include "FunctionLib.h"
#include "fsl_os_abstraction.h"
#include "shell.h"
#include "panic.h"
#include "MemManager.h"
#include "board.h"
#include "per.h"
#include "Flash_Adapter.h"
#include "Led.h"

/* BLE Host Stack */
#include "app.h"
#include "gap_types.h"
#include "gap_interface.h"
#include "controller_interface.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
/************************************************************************************
*************************************************************************************
* Private macros
*************************************************************************************
************************************************************************************/
#define gLeRxTestTime_c                     (10) /* 10 s*/
#define gPerRxTestEvt10sTmrFlag_c           (1 << 0)
#define gPerLeTestTxCmdCompFlag_c           (1 << 1)
#define gPerLeTestRxCmdCompFlag_c           (1 << 2)
#define gPerLeTestEndCmdCompFlag_c          (1 << 3)
#define gPerLeTestResultOKFlag_c            (1 << 4)

#define PER_LE_TEST_PKT_PREAMBLE_LEN        (1)
#define PER_LE_TEST_PKT_SYNC_WORD_LEN       (4)
#define PER_LE_TEST_PKT_PDU_HDR_LEN         (1)
#define PER_LE_TEST_PKT_PDU_LENTH_LEN       (1)
#define PER_LE_TEST_PKT_PDU_CRC_LEN         (3)
#define PER_LE_TEST_PKT_ADD_LEN             (PER_LE_TEST_PKT_PREAMBLE_LEN + \
                PER_LE_TEST_PKT_SYNC_WORD_LEN + PER_LE_TEST_PKT_PDU_HDR_LEN + \
                PER_LE_TEST_PKT_PDU_LENTH_LEN + PER_LE_TEST_PKT_PDU_CRC_LEN)

#define PER_LE_TEST_MAX_RECEIVE_PKTS        (32767)
/************************************************************************************
*************************************************************************************
* Private type definitions
*************************************************************************************
************************************************************************************/

/************************************************************************************
*************************************************************************************
* Private functions prototypes
*************************************************************************************
************************************************************************************/

/************************************************************************************
*************************************************************************************
* Private memory declarations
*************************************************************************************
************************************************************************************/
static uint16_t gNumOfRecvPackets = 0;

static uint32_t gPerDataLen = 30;
static uint32_t gPerPayload = 0;
static uint32_t gTestDurationSec = 10;
static uint32_t gTxPowerLevel = 20;
static uint32_t gConnPowerLevel = 20;

static int32_t per_tx_on = FALSE;
static int32_t per_rx_on = FALSE;

static bleResult_t gTestCmdCompSts;

static perCmdExeResultCB pPerCmdExeResultCB = NULL;

static tmrTimerID_t perTestTimerId;
static osaEventId_t mPerLeTestCmdCompEvent;

/* PER data storage space on flash, address 0x0003f000, defined in linker file */
extern uint32_t PER_FLASH_DATA_BASE_ADDR[];

/************************************************************************************
*************************************************************************************
* Public memory declarations
*************************************************************************************
************************************************************************************/

/************************************************************************************
*************************************************************************************
* Public functions
*************************************************************************************
************************************************************************************/

/************************************************************************************
*************************************************************************************
* Private functions
*************************************************************************************
************************************************************************************/
/*! *********************************************************************************
* \brief        Handles advertising timer callback.
*
* \param[in]    pParam        Calback parameters.
********************************************************************************** */
/* BLE phsical speed 1M/S, that is 1us/bit */
static int32_t perGetPacketInterval(uint32_t pkt_len)
{
    uint32_t pkt_tot_bits = (pkt_len + PER_LE_TEST_PKT_ADD_LEN) << 3;

    if (pkt_tot_bits <= 376)
    {
        return 625;
    }
    else if (pkt_tot_bits <= 1000)
    {
        return 1250;
    }
    else if (pkt_tot_bits <= 1624)
    {
        return 1875;
    }
    else if (pkt_tot_bits <= 2120)
    {
        return 2500;
    }
    else
    {
        return -1;
    }
}

static uint32_t perGetTxPacketsPerSec(uint32_t pkt_len)
{
    return 1000000 / perGetPacketInterval(pkt_len);
}

/* 1 bit/us */
static uint32_t perGetTotalTxPackets(uint32_t pkt_len, uint32_t duration)
{
    return duration * perGetTxPacketsPerSec(pkt_len);
}

uint32_t perGetMaxTestDuration(uint32_t pkt_len)
{
    return PER_LE_TEST_MAX_RECEIVE_PKTS / perGetTxPacketsPerSec(pkt_len);
}

static int32_t perCalPer(uint16_t recv_packets, uint32_t data_len, uint32_t *out_per_int, uint32_t *out_per_frac)
{
    uint32_t tot_pkts = perGetTotalTxPackets(data_len, gTestDurationSec);
    /* PER = (1 - Nrecv / Ntrans) * 100% */
    uint32_t per_val = 0, per_val_int = 0, per_val_frac = 0;
    
    /* If 100% received, the received packet may exceed. So abandon some packets */
    if (recv_packets > tot_pkts)
    {
        recv_packets = tot_pkts;
    }
    per_val = (tot_pkts - recv_packets) * 10000 / tot_pkts;

    /* Two frac */
    per_val_int = per_val / 100;
    per_val_frac = per_val - per_val_int * 100;

    *out_per_int = per_val_int;
    *out_per_frac = per_val_frac;
    return 0;
}

bleResult_t perRegisterCmdExeRsltCB(perCmdExeResultCB cmd_rslt_cb)
{
    if (NULL == pPerCmdExeResultCB)
    {
        pPerCmdExeResultCB = cmd_rslt_cb;
        return gBleSuccess_c;
    }
    else
    {
        return gBleUnavailable_c;
    }
}

bleResult_t perUnregisterCmdExeRsltCB(void)
{
    pPerCmdExeResultCB = NULL;

    return gBleSuccess_c;
}

bleResult_t perSetTxStatus(int32_t busy_or_not)
{
    per_tx_on = busy_or_not;
    if (per_tx_on)
    {
        TurnOffLeds();
        Led2On();
    }
    else
    {
        TurnOffLeds();
        Led1On();
    }
    return gBleSuccess_c;
}

int32_t perGetTxStatus(void)
{
    return per_tx_on;
}

bleResult_t perSetRxStatus(int32_t busy_or_not)
{
    per_rx_on = busy_or_not;
    if (per_rx_on)
    {
        TurnOffLeds();
        Led3On();
    }
    else
    {
        TurnOffLeds();
        Led1On();
    }
    return gBleSuccess_c;
}

int32_t perGetRxStatus(void)
{
    return per_rx_on;
}

bleResult_t perTestEnd(int32_t is_cal_per)
{
    bleResult_t ret;
    uint32_t rev_pkts = 0;
    osaEventFlags_t ev;
    perCalResult_t per_rslt = {PER_DATA_FLAG_DUMMY, PER_DATA_FLAG_DUMMY};

    ret = Gap_ControllerTest(gControllerTestCmdEnd_c, 0, 0, (gapControllerTestTxType_t)0);

    if (osaStatus_Success != OSA_EventWait(mPerLeTestCmdCompEvent, gPerLeTestEndCmdCompFlag_c,FALSE, osaWaitForever_c, &ev))
    {
        return gBleOsError_c;
    }

    if ((gGattDbSuccess_c == ret) && (gGattDbSuccess_c == gTestCmdCompSts))
    {
        TMR_StopTimer(perTestTimerId);
        if (TRUE == perGetTxStatus())
        {
            perSetTxStatus(FALSE);
        }
        if (TRUE == perGetRxStatus())
        {
            perSetRxStatus(FALSE);
        }
        if (1 == is_cal_per)
        {
            uint32_t per_int = 0, per_frac = 0;
            perCalPer(gNumOfRecvPackets, gPerDataLen, &per_int, &per_frac);
            per_rslt.integer_val = per_int;
            per_rslt.frac_val = per_frac;
        }
        if (pPerCmdExeResultCB)
        {
            (*pPerCmdExeResultCB)(PER_EVT_TEST_END, &per_rslt);
        }
        return gBleSuccess_c;
    }
    else
    {
        return ret;
    }
}

void perRxTestTimerCallback(void* pParam)
{
    perTestEnd(1);
}

bleResult_t perReset(void)
{
    return Gap_ControllerReset();
}

uint32_t perGetConfig(perConfigType_t cfg_type)
{
    uint32_t ret_val = 0;

    switch (cfg_type)
    {
    case PER_CFG_DATA_LEN:
        ret_val = gPerDataLen;
        break;
    case PER_CFG_PAYLOAD:
        ret_val = gPerPayload;
        break;
    case PER_CFG_TEST_DURATION:
        ret_val = gTestDurationSec;
        break;
    case PER_CFG_TXDBM:
        ret_val = gTxPowerLevel;
        break;
    case PER_CFG_CONNDBM:
        ret_val = gConnPowerLevel;
        break;
    default:
        return gBleInvalidParameter_c;
        break;
    }

    return ret_val;
}

bleResult_t perConfig(perConfigType_t cfg_type, uint32_t val)
{
    uint8_t arg_cnt = 0;
    uint32_t len = 0, payload = 0, test_duration = 0, power_level = 0;;
    uint32_t cmd_valid_param = 0;
    bleResult_t ret;

    switch (cfg_type)
    {
    case PER_CFG_DATA_LEN:
        {
            if (len > 255)
            {
                return gBleInvalidParameter_c;
            }
            gPerDataLen = val;
        }
        break;
    case PER_CFG_PAYLOAD:
        {
            if (val > 2)
            {
                return gBleInvalidParameter_c;
            }
            gPerPayload = val;
        }
        break;
    case PER_CFG_TEST_DURATION:
        {
            if (test_duration > perGetMaxTestDuration(gPerDataLen))
            {
                return gBleInvalidParameter_c;
            }
            gTestDurationSec = val;
        }
        break;
    case PER_CFG_TXDBM:
        {
            if (val > 31)
            {
                return gBleInvalidParameter_c;
            }
            gTxPowerLevel = val;

            return Controller_SetTxPowerLevel(val, gAdvTxChannel_c);
        }
        break;
    case PER_CFG_CONNDBM:
        {
            if (val > 31)
            {
                return gBleInvalidParameter_c;
            }
            gConnPowerLevel = val;

            return Controller_SetTxPowerLevel(val, gConnTxChannel_c);
        }
        break;
    default:
        return gBleInvalidParameter_c;
        break;
    }

    return gBleSuccess_c;
}

bleResult_t perStartTx(uint32_t ch_num)
{
    osaEventFlags_t ev;
    bleResult_t ret;

    if (ch_num > gBleFreq2480MHz_c)
    {
        return gL2caChannelInvalid_c;
    }

    ret = Gap_ControllerTest(gControllerTestCmdStartTx_c, ch_num, gPerDataLen, (gapControllerTestTxType_t)gPerPayload);

    if (osaStatus_Success != OSA_EventWait(mPerLeTestCmdCompEvent, gPerLeTestTxCmdCompFlag_c, FALSE, osaWaitForever_c, &ev))
    {
        return gBleOsError_c;
    }

    if ((gGattDbSuccess_c == ret) && (gGattDbSuccess_c == gTestCmdCompSts))
    {
        perSetTxStatus(TRUE);
        return gBleSuccess_c;
    }
    else
    {
        return ret;
    }
}

bleResult_t perStartRx(uint32_t ch_num)
{
    bleResult_t ret;
    osaEventFlags_t ev;
    osaStatus_t     status;

    if (ch_num > gBleFreq2480MHz_c)
    {
        return gL2caChannelInvalid_c;
    }

    ret = Gap_ControllerTest(gControllerTestCmdStartRx_c, ch_num, 0, (gapControllerTestTxType_t)0);

    status = OSA_EventWait(mPerLeTestCmdCompEvent, gPerLeTestRxCmdCompFlag_c, FALSE, osaWaitForever_c, &ev);
    if (osaStatus_Success != status)
    {
        return gBleOsError_c;
    }

    if ((gGattDbSuccess_c == ret) && (gGattDbSuccess_c == gTestCmdCompSts))
    {
        perSetRxStatus(TRUE);
        /* Start a 10s timer to receive packets */
        TMR_StartLowPowerTimer(perTestTimerId,
                           gTmrLowPowerSecondTimer_c,
                           TmrSeconds(gTestDurationSec),
                           perRxTestTimerCallback, NULL);
        return gBleSuccess_c;
    }
    else
    {
        return ret;
    }
}

bleResult_t perTriggerTestCmdCompleted(gapControllerTestEvent_t *pTestEvent)
{
    switch (pTestEvent->testEventType)
    {
    case gControllerReceiverTestStarted_c:
        gTestCmdCompSts = gGattDbSuccess_c;
        if (osaStatus_Success != OSA_EventSet(mPerLeTestCmdCompEvent, gPerLeTestRxCmdCompFlag_c))
        {
            return gBleOsError_c;
        }

        break;
    case gControllerTransmitterTestStarted_c:
        gTestCmdCompSts = gGattDbSuccess_c;
        if (osaStatus_Success != OSA_EventSet(mPerLeTestCmdCompEvent, gPerLeTestTxCmdCompFlag_c))
        {
            return gBleOsError_c;
        }
        break;
    case gControllerTestEnded_c:
        gTestCmdCompSts = gGattDbSuccess_c;
        gNumOfRecvPackets = pTestEvent->receivedPackets;
        if (osaStatus_Success != OSA_EventSet(mPerLeTestCmdCompEvent, gPerLeTestEndCmdCompFlag_c))
        {
            return gBleOsError_c;
        }
        break;
    default:
        break;
    }

    return gGattDbSuccess_c;
}

/************************************************************************************
*  Updates the CRC based on the received data to process.
*  Updates the global CRC value. This was determined to be optimal from a resource
*  consumption POV.
*
*  Input parameters:
*  - None
*  Return:
*  - None
************************************************************************************/
static uint16_t perCrcCompute(uint8_t *pData, uint16_t lenData, uint16_t crcValueOld)
{
    uint8_t i;

    while(lenData--)
    {
        crcValueOld ^= (uint16_t)((uint16_t)*pData++ << 8);
        for( i = 0; i < 8; ++i )
        {
            if( crcValueOld & 0x8000 )
            {
                crcValueOld = (crcValueOld << 1) ^ 0x1021U;
            }
            else
            {
                crcValueOld = crcValueOld << 1;
            }
        }
    }
    return crcValueOld;
}

bleResult_t perStoreFlashData(PER_Data_Store_t *per_data)
{
    uint16_t computed_crc;

    /* Computed CRC */
    computed_crc = perCrcCompute((uint8_t *)per_data->per_data, sizeof(per_data->per_data), 0);
    per_data->data_crc = computed_crc;
    per_data->data_flag = PER_DATA_FLAG_VALID;
    /* RX end, save data to flash */
    if (kStatus_FLASH_Success != NV_FlashEraseSector((uint32_t)PER_FLASH_DATA_BASE_ADDR, FSL_FEATURE_FLASH_PFLASH_BLOCK_SECTOR_SIZE))
    {
        return gBleUnavailable_c;
    }

    if (kStatus_FLASH_Success != NV_FlashProgramUnaligned((uint32_t)PER_FLASH_DATA_BASE_ADDR, sizeof(PER_Data_Store_t), (uint8_t *)per_data))
    {
        return gBleUnavailable_c;
    }

    return gBleSuccess_c;
}

bleResult_t perReadFlashData(PER_Data_Store_t *per_data)
{
    uint16_t computed_crc;
    PER_Data_Store_t *nvm_per_data = (PER_Data_Store_t *)PER_FLASH_DATA_BASE_ADDR;

    FLib_MemSet(per_data, (uint8_t)PER_DATA_FLAG_DUMMY, sizeof(PER_Data_Store_t));
    if (PER_DATA_FLAG_VALID == nvm_per_data->data_flag)
    {
        FLib_MemCpy((void *)per_data, (void *)&nvm_per_data, sizeof(PER_Data_Store_t));
        computed_crc = perCrcCompute((uint8_t *)&per_data->per_data, sizeof(per_data->per_data), 0);
        if (computed_crc != per_data->data_crc)
        {
            /* Reset, Record from start */
            per_data->rec_index = 0;
        }
        return gBleSuccess_c;
    }
    else
    {
        per_data->rec_index = 0;
    }
    return gBleUnavailable_c;
}

const PER_Data_Store_t *perGetFlashDataPtr(void)
{
    PER_Data_Store_t *nvm_per_data = (PER_Data_Store_t *)PER_FLASH_DATA_BASE_ADDR;
    if (PER_DATA_FLAG_VALID == nvm_per_data->data_flag)
    {
        return nvm_per_data;
    }
    else
    {
        return NULL;
    }
}

bleResult_t perTestInit(void)
{
    mPerLeTestCmdCompEvent = OSA_EventCreate(TRUE);
    if( NULL == mPerLeTestCmdCompEvent )
    {
        panic(0,0,0,0);
        return gBleOsError_c;
    }

    /* Allocate aplication timer */
    perTestTimerId = TMR_AllocateTimer();
    if (gTmrInvalidTimerID_c == perTestTimerId)
    {
        shell_write("\n\r--> Allocate Timer failed!");
        return gBleUnavailable_c;
    }

    StopLed1Flashing();
    StopLed2Flashing();
    StopLed3Flashing();
    StopLed4Flashing();
    TurnOffLeds();
    Led1On();

    return gBleSuccess_c;
}

/*! *********************************************************************************
 * @}
 ********************************************************************************** */
