/*! *********************************************************************************
 * \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"

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

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
/************************************************************************************
*************************************************************************************
* Private macros
*************************************************************************************
************************************************************************************/
#define mShellPerCmdsCount_c                (6)

/************************************************************************************
*************************************************************************************
* Private type definitions
*************************************************************************************
************************************************************************************/
typedef struct perCmds_tag
{
    char*       name;
    int8_t      (*cmd)(uint8_t argc, char *argv[]); 
} perCmds_t;

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

/* Shell API Functions */
static int8_t Shell_Per_Reset(uint8_t argc,  char *argv[]);
static int8_t Shell_Per_Config(uint8_t argc, char *argv[]);
static int8_t Shell_Per_Tx(uint8_t argc,     char *argv[]);
static int8_t Shell_Per_Rx(uint8_t argc,     char *argv[]);
static int8_t Shell_Per_End(uint8_t argc,    char *argv[]);
static int8_t Shell_Per_PrintNVM(uint8_t argc, char *argv[]);

/************************************************************************************
*************************************************************************************
* Private memory declarations
*************************************************************************************
************************************************************************************/
const perCmds_t mPerShellCmds[mShellPerCmdsCount_c] = 
{
    {"reset",     Shell_Per_Reset},
    {"config",    Shell_Per_Config},
    {"tx",        Shell_Per_Tx},
    {"rx",        Shell_Per_Rx},
    {"end",       Shell_Per_End},
    {"printnvm", Shell_Per_PrintNVM},
};

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

/************************************************************************************
*************************************************************************************
* Public functions
*************************************************************************************
************************************************************************************/
int8_t Shell_Per_Command(uint8_t argc, char * argv[])
{
    uint8_t i;
    
    if (argc < 2)
    {
        return CMD_RET_USAGE;
    }
    
    for (i = 0; i < mShellPerCmdsCount_c; i++)
    {
        if(!strcmp((char*)argv[1], mPerShellCmds[i].name) )
        {
            return mPerShellCmds[i].cmd(argc - 2, (char **)(&argv[2]));
        }
    }
    return CMD_RET_USAGE;
}
/************************************************************************************
*************************************************************************************
* Private functions
*************************************************************************************
************************************************************************************/
/*! *********************************************************************************
* \brief        Handles advertising timer callback.
*
* \param[in]    pParam        Calback parameters.
********************************************************************************** */
static int32_t perGetRxResultCallBack(perTestEvtType_t evt_type, const perCalResult_t *per_result)
{
    if (PER_EVT_TEST_END == evt_type)
    {
        if (PER_DATA_FLAG_DUMMY != per_result->integer_val)
        {
            shell_write("\n\r--> PER(%): ");
            shell_writeDec(per_result->integer_val);
            shell_write(".");
            shell_writeDec(per_result->frac_val);
            shell_write("%");
        }
        else
        {
            shell_write("\n\rPER(%) Result Invalid!");
        }
        SHELL_NEWLINE();
    }
    return 0;
}

static int8_t Shell_Per_Reset(uint8_t argc, char * argv[])
{
    bleResult_t ret;

    if (0 != argc)
    {
        shell_write("\n\r--> Variable length exceeds maximum!");
        SHELL_NEWLINE();
        return CMD_RET_FAILURE;
    }

    ret = Gap_ControllerReset();
    if (gGattDbSuccess_c == ret)
    {
        shell_write("\n\r--> Reset Succeed!");
        SHELL_NEWLINE();
        return CMD_RET_SUCCESS;
    }
    else
    {
        shell_write("\n\r--> Reset failed: ");
        shell_writeDec(ret);
        SHELL_NEWLINE();
        return CMD_RET_FAILURE;
    }
}

static int8_t Shell_Per_Config(uint8_t argc, char * argv[])
{
    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;

    if ((0 != argc) && (2 != argc))
    {
        shell_write("\n\r--> Variable length invalid!");
        SHELL_NEWLINE();
        return CMD_RET_FAILURE;
    }

    if (0 == argc)
    {
        shell_write("\n\r--> Data Len: ");
        shell_writeDec(perGetConfig(PER_CFG_DATA_LEN));
        shell_write(" Bytes");
        shell_write("\n\r--> Payload type: ");
        shell_writeDec(perGetConfig(PER_CFG_PAYLOAD));
        shell_write("\n\r--> Test duration: ");
        shell_writeDec(perGetConfig(PER_CFG_TEST_DURATION));
        shell_write(" Seconds");
        shell_write("\n\r--> ADV TX Power level: ");
        shell_writeDec(perGetConfig(PER_CFG_TXDBM));
        shell_write("\n\r--> Connection TX Power level: ");
        shell_writeDec(perGetConfig(PER_CFG_CONNDBM));
        SHELL_NEWLINE();
        return CMD_RET_SUCCESS;
    }
    else
    {
        for (arg_cnt = 0; arg_cnt < argc; arg_cnt += 2)
        {
            if (!strcmp((char*)argv[arg_cnt], "-len") && ((arg_cnt + 1) < argc))
            {
                len = strtoul(argv[arg_cnt + 1], NULL, 10);
                if (gBleSuccess_c != perConfig(PER_CFG_DATA_LEN, len))
                {
                    shell_write("\n\r--> Invalid data length! The value can be 0 - 255!");
                    SHELL_NEWLINE();
                    return CMD_RET_FAILURE;
                }
                shell_write("\n\r--> Set new data length DONE!");
                SHELL_NEWLINE();
                return CMD_RET_SUCCESS;
            }
    
            if (!strcmp((char*)argv[arg_cnt], "-payload") && ((arg_cnt + 1) < argc))
            {
                payload = strtoul(argv[arg_cnt + 1], NULL, 10);
                if (gBleSuccess_c != perConfig(PER_CFG_PAYLOAD, payload))
                {
                    shell_write("\n\r--> Invalid payload type! The value can be 0 - 2!");
                    SHELL_NEWLINE();
                    return CMD_RET_FAILURE;
                }
                shell_write("\n\r--> Set new payload type DONE!");
                SHELL_NEWLINE();
                return CMD_RET_SUCCESS;
            }

            if (!strcmp((char*)argv[arg_cnt], "-time") && ((arg_cnt + 1) < argc))
            {
                test_duration = strtoul(argv[arg_cnt + 1], NULL, 10);
                if (gBleSuccess_c != perConfig(PER_CFG_TEST_DURATION, test_duration))
                {
                    shell_write("\n\r--> Test duration exceed maximum allowed time! The duration should be in ");
                    shell_writeDec(perGetMaxTestDuration(perGetConfig(PER_CFG_DATA_LEN)));
                    shell_write(" Seconds!");
                    SHELL_NEWLINE();
                    return CMD_RET_FAILURE;
                }
                shell_write("\n\r--> Set test duration DONE!");
                SHELL_NEWLINE();
                return CMD_RET_SUCCESS;
            }

            if (!strcmp((char*)argv[arg_cnt], "-txdbm") && ((arg_cnt + 1) < argc))
            {
                power_level = strtoul(argv[arg_cnt + 1], NULL, 10);
                if (power_level > 31)
                {
                    shell_write("\n\r--> Invalid power level! The value can be 0 - 31!");
                    SHELL_NEWLINE();
                    return CMD_RET_FAILURE;
                }

                if (gBleSuccess_c != perConfig(PER_CFG_TXDBM, power_level))
                {
                    shell_write("\n\r--> Set Tx Power Level for ADV failed!");
                    SHELL_NEWLINE();
                    return CMD_RET_FAILURE;
                }

                shell_write("\n\r--> Set ADV Tx power level DONE!");
                SHELL_NEWLINE();
                return CMD_RET_SUCCESS;
            }
            
            if (!strcmp((char*)argv[arg_cnt], "-conndbm") && ((arg_cnt + 1) < argc))
            {
                power_level = strtoul(argv[arg_cnt + 1], NULL, 10);
                if (power_level > 31)
                {
                    shell_write("\n\r--> Invalid power level! The value can be 0 - 31!");
                    SHELL_NEWLINE();
                    return CMD_RET_FAILURE;
                }

                if (gBleSuccess_c != perConfig(PER_CFG_CONNDBM, power_level))
                {
                    shell_write("\n\r--> Set Tx Power Level for Connection failed!");
                    SHELL_NEWLINE();
                    return CMD_RET_FAILURE;
                }
                shell_write("\n\r--> Set Connection Tx power level DONE!");
                SHELL_NEWLINE();
                return CMD_RET_SUCCESS;
            }
        }
    }
    shell_write("\n\r--> Invalid parameters!");
    SHELL_NEWLINE();
    return CMD_RET_FAILURE;
}

static int8_t Shell_Per_Tx(uint8_t argc, char * argv[])
{
    uint8_t arg_cnt = 0;
    uint32_t channel_num = 0;
    uint32_t cmd_valid_param = 0;
    osaEventFlags_t ev;
    bleResult_t ret;

    if (2 != argc)
    {
        shell_write("\n\r--> Variable length exceeds maximum!");
        SHELL_NEWLINE();
        return CMD_RET_FAILURE;
    }

    if (!strcmp((char*)argv[arg_cnt], "-ch") && ((arg_cnt + 1) < argc))
    {
        channel_num = strtoul(argv[arg_cnt + 1], NULL, 10);
        if (channel_num > gBleFreq2480MHz_c)
        {
            shell_write("\n\r--> Invalid channel number! The value can be 0 - 39!");
            SHELL_NEWLINE();
            return CMD_RET_FAILURE;
        }
        ++cmd_valid_param;
    }

    if (cmd_valid_param < 1)
    {
        shell_write("\n\r--> Invalid parameters!");
        SHELL_NEWLINE();
        return CMD_RET_FAILURE;
    }

    if (TRUE == perGetRxStatus())
    {
        shell_write("\n\r--> RF is busy in RX!");
        return CMD_RET_FAILURE;
    }

    if (TRUE == perGetTxStatus())
    {
        shell_write("\n\r--> RF is busy in TX!");
        return CMD_RET_FAILURE;
    }

    perUnregisterCmdExeRsltCB();
    perRegisterCmdExeRsltCB(perGetRxResultCallBack);
    shell_write("\n\r--> Data Len: ");
    shell_writeDec(perGetConfig(PER_CFG_DATA_LEN));
    shell_write(" Bytes");
    shell_write("\n\r--> Payload type: ");
    shell_writeDec(perGetConfig(PER_CFG_PAYLOAD));
    shell_write("\n\r--> ADV TX Power level: ");
    shell_writeDec(perGetConfig(PER_CFG_TXDBM));
    shell_write("\n\r--> Connection TX Power level: ");
    shell_writeDec(perGetConfig(PER_CFG_CONNDBM));
    ret = perStartTx(channel_num);
    
    if (gGattDbSuccess_c == ret)
    {
        shell_write("\n\r--> Start Transmitting! You should use 'per end' command to end it");
        SHELL_NEWLINE();
        return CMD_RET_SUCCESS;
    }
    else
    {
        shell_write("\n\r--> Transmitting start failed:");
        shell_writeDec(ret);
        SHELL_NEWLINE();
        return CMD_RET_FAILURE;
    }
}

static int8_t Shell_Per_Rx(uint8_t argc, char * argv[])
{
    uint8_t arg_cnt = 0;
    uint32_t channel_num = 0;
    uint32_t cmd_valid_param = 0;
    bleResult_t ret;
    osaEventFlags_t ev;
    osaStatus_t     status;

    if (2 != argc)
    {
        shell_write("\n\r--> Variable length invalid!");
        SHELL_NEWLINE();
        return CMD_RET_FAILURE;
    }

    if(!strcmp((char*)argv[arg_cnt], "-ch"))
    {
        channel_num = strtoul(argv[1], NULL, 10);
        if (channel_num > gBleFreq2480MHz_c)
        {
            shell_write("\n\r--> Invalid channel number! The value can be 0 - 39!");
            SHELL_NEWLINE();
            return CMD_RET_FAILURE;
        }
        ++cmd_valid_param;
    }

    if (cmd_valid_param < 1)
    {
        shell_write("\n\r--> Invalid parameters!");
        SHELL_NEWLINE();
        return CMD_RET_FAILURE;
    }

    if (TRUE == perGetRxStatus())
    {
        shell_write("\n\r--> RF is busy in RX!");
        return CMD_RET_FAILURE;
    }

    if (TRUE == perGetTxStatus())
    {
        shell_write("\n\r--> RF is busy in TX!");
        return CMD_RET_FAILURE;
    }

    perUnregisterCmdExeRsltCB();
    perRegisterCmdExeRsltCB(perGetRxResultCallBack);
    ret = perStartRx(channel_num);

    if (gGattDbSuccess_c == ret)
    {
        shell_write("\n\r--> Start receiving for ");
        shell_writeDec(perGetConfig(PER_CFG_TEST_DURATION));
        shell_write(" Seconds! Please make sure Tx is ON before Rx!");
        shell_write("\n\r--> Receiving ......");
        return CMD_RET_SUCCESS;
    }
    else
    {
        shell_write("\n\r--> Receiving start failed: ");
        shell_writeDec(ret);
        SHELL_NEWLINE();
        return CMD_RET_FAILURE;
    }
}

static int8_t Shell_Per_PrintNVM(uint8_t argc, char *argv[])
{
    bleResult_t ret;
    const PER_Data_Store_t *pPerData;
    int32_t rec_cnt = 0, data_index = 0;

    if (argc > 1)
    {
        shell_write("\n\r--> Variable length exceeds maximum!");
        SHELL_NEWLINE();
        return CMD_RET_FAILURE;
    }

    pPerData = perGetFlashDataPtr();
    if (NULL == pPerData)
    {
        shell_write("\n\r--> Test data on flash is not valid!");
        return CMD_RET_FAILURE;
    }

    if (0 == pPerData->rec_index)
    {
        shell_write("\n\r--> No test data found!");
        return CMD_RET_FAILURE;
    }
    shell_write("\n\r--> PER Data:");
    for (rec_cnt = 0; rec_cnt < pPerData->rec_index; ++rec_cnt)
    {
        shell_write("\n\r--> Test");
        shell_writeDec(rec_cnt);
        shell_write(":");
        shell_write("\n\rIndex            PER(%)");
        for (data_index = 0; data_index < PER_TEST_LOOP_COUNT; ++data_index)
        {
            const perCalResult_t *per_result = &(pPerData->per_data[rec_cnt][data_index]);
            shell_write("\n\r");
            shell_writeDec(data_index);
            shell_write("                ");
            shell_writeDec(per_result->integer_val);
            shell_write(".");
            shell_writeDec(per_result->frac_val);
            shell_write("%");
        }
        SHELL_NEWLINE();
    }
    
    return CMD_RET_SUCCESS;
}

static int8_t Shell_Per_End(uint8_t argc, char *argv[])
{
    bleResult_t ret;
    uint32_t rev_pkts = 0;
    uint32_t cmd_valid_param = 0;
    osaEventFlags_t ev;

    if (argc > 1)
    {
        shell_write("\n\r--> Variable length exceeds maximum!");
        SHELL_NEWLINE();
        return CMD_RET_FAILURE;
    }

    ret = perTestEnd(0);
    if (gBleSuccess_c ==  ret)
    {
        shell_write("\n\r--> Le Test Ended!");
        SHELL_NEWLINE();
        return CMD_RET_SUCCESS;
    }
    else
    {
        shell_write("\n\r--> End testing failed: ");
        shell_writeDec(ret);
        SHELL_NEWLINE();
        return CMD_RET_FAILURE;
    }
}

bleResult_t Shell_Per_Init(void)
{
    bleResult_t ret;

    ret = perTestInit();
    if (gBleUnavailable_c == ret)
    {
        shell_write("\n\r--> Allocate Timer failed!");
    }

    return ret;
}

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