//*****************************************************************************
//
//  appl_amdtps.c
//! @file
//
//*****************************************************************************

//*****************************************************************************
//
// Copyright (c) 2019, Ambiq Micro
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 
// 2. 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.
// 
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
// 
// Third party software included in this distribution is subject to the
// additional license terms as defined in the /docs/licenses directory.
// 
// 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.
//
// This is part of revision v2.2.0-7-g63f7c2ba1 of the AmbiqSuite Development Package.
//
//*****************************************************************************

/**
 *  \file appl_amdtps.c
 *
 *  This file contains the AMDTP application.
  *  Sample applications detailed below:
 *      a. The Sensor, as defined by the Sepcification plays the GAP Peripheral
 *         role.
 *      b. The Sensor application has following sevice records:
 *           - GAP
 *           - GATT
 *           - Battery
 *           - Device Information and
 *           - AMDTP
 *         [NOTE]: Please see gatt_db.c for more details of the record.
 *      c. appl_manage_transfer routine takes care of handling peer
 *         configuration. This handling would be needed:
 *           - When Peer Configures Measurement Transfer by writting to the
 *             Characteristic Client Configuration of AMOTA Tx.
 *           - Subsequent reconnection with bonded device that had already
 *             configured the device for transfer. Please note it is mandatory
 *             for GATT Servers to remember the configurations of bonded GATT
 *             clients.
 *         In order to ensure the above mentioned configurations are correctly
 *         handled, the routine, appl_manage_transfer, is therefore called from:
 *           - gatt_db_amotas_handler and
 *           - appl_amdtps_connect
 *         [NOTE]: If link does not have the needed secruity for the service,
 *         transfer will not be initiated.
 */



/* --------------------------------------------- Header File Inclusion */
#include "appl.h"

#include "am_mcu_apollo.h"
#include "am_bsp.h"
#include "am_util.h"

#ifdef AMDTPS

//*****************************************************************************
//
// Global variables
//
//*****************************************************************************
uint8_t rxPktBuf[AMDTP_PACKET_SIZE];
uint8_t txPktBuf[AMDTP_PACKET_SIZE];
uint8_t ackPktBuf[20];

#if defined(AMDTPS_RXONLY) || defined(AMDTPS_RX2TX)
static int totalLen = 0;
#endif

//*****************************************************************************
//
// Macro definitions
//
//*****************************************************************************
/* Control block */
static struct
{
    BOOLEAN                 txReady;              // TRUE if ready to send notifications
    AmdtpsCfg_t             cfg;                  // configurable parameters
    amdtpCb_t               core;
}
amdtpsCb;

/* --------------------------------------------- Static Global Variables */
static GATT_DB_HANDLE appl_amdtps_handle;
static ATT_ATTR_HANDLE appl_tx_hndl;
static ATT_ATTR_HANDLE appl_ack_hndl;

/* --------------------------------------------- Functions */

static bool sendDataContinuously = false;
static uint32_t counter = 0;

static void AmdtpsSendTestData(void)
{
    uint8_t data[236] = {0};
    eAmdtpStatus_t status;

    sendDataContinuously = true;
    *((uint32_t*)&(data[0])) = counter;

    status = AmdtpsSendPacket(AMDTP_PKT_TYPE_DATA, false, true, data, sizeof(data));
    if (status != AMDTP_STATUS_SUCCESS)
    {
        AMDTP_TRC("[AMDTP]: AmdtpsSendTestData() failed, status = %d\n", status);
    }
    else
    {
        counter++;
    }
}

void amdtpsDtpRecvCback(uint8_t * buf, uint16_t len)
{
    if (buf[0] == 1)
    {
        AMDTP_TRC("[AMDTP]: send test data\n");
        AmdtpsSendTestData();
    }
    else if (buf[0] == 2)
    {
        AMDTP_TRC("[AMDTP]: send test data stop\n");
        sendDataContinuously = false;
    }
}

void amdtpsDtpTransCback(eAmdtpStatus_t status)
{
#ifdef AMDTP_DEBUG_ON
    AMDTP_TRC("[AMDTP]: amdtpDtpTransCback =%d\n", status);
#endif
    if (status == AMDTP_STATUS_SUCCESS && sendDataContinuously)
    {
        AmdtpsSendTestData();
    }
}

void appl_amdtps_init(void)
{
    appl_amdtp_server_reinitialize();
}

void appl_amdtps_connect(APPL_HANDLE  * appl_handle)
{
    ATT_VALUE         value;
    UINT16            cli_cnfg;

    cli_cnfg = 0;

    appl_amdtps_handle.device_id = APPL_GET_DEVICE_HANDLE((*appl_handle));
    appl_amdtps_handle.char_id = GATT_CHAR_AMDTP_TX;
    appl_amdtps_handle.service_id = GATT_SER_AMDTP_INST;

    BT_gatt_db_get_char_val_hndl(&appl_amdtps_handle, &appl_tx_hndl);
    BT_gatt_db_get_char_cli_cnfg(&appl_amdtps_handle, &value);
    BT_UNPACK_LE_2_BYTE (&cli_cnfg, value.val);


    appl_amdtps_handle.char_id = GATT_CHAR_AMDTP_ACK;
    BT_gatt_db_get_char_val_hndl(&appl_amdtps_handle, &appl_ack_hndl);
    BT_gatt_db_get_char_cli_cnfg(&appl_amdtps_handle, &value);
    BT_UNPACK_LE_2_BYTE (&cli_cnfg, value.val);

    AMDTP_TRC (
    "[APPL]: Fetched Client Configuration (0x%04X) for Device (0x%02X)\n",
    cli_cnfg, APPL_GET_DEVICE_HANDLE((*appl_handle)));
    appl_manage_trasnfer(appl_amdtps_handle, cli_cnfg);
}

void appl_manage_trasnfer(GATT_DB_HANDLE handle, UINT16 config)
{
    APPL_HANDLE    appl_handle;
    API_RESULT     retval;

    UCHAR          security, ekey_size;

    AMDTP_TRC("[AMDTP]: appl_manage_trasnfer+ \n");
    /* Get required security for service */
    /* Get required security level */
    BT_gatt_db_get_service_security (&handle, &security);
    /* Get required encryption key size */
    BT_gatt_db_get_service_enc_key_size (&handle, &ekey_size);

    /* Verify if security requirements are available with the link */
    retval = appl_smp_assert_security
             (
                 &handle.device_id,
                 security,
                 ekey_size
             );

    /* Security requirements satisfied? */
    if (API_SUCCESS != retval)
    {
        /* No. Return */
        return;
    }

    /* Security requirements satisfied, go ahead with data transfer */

    retval = appl_get_handle_from_device_handle(handle.device_id, &appl_handle);

    if (API_SUCCESS != retval)
    {
        return;
    }

    if (GATT_CLI_CNFG_NOTIFICATION == config)
    {
        amdtpsCb.txReady = true;
        amdtpsCb.core.txState = AMDTP_STATE_TX_IDLE;
        AMDTP_TRC("[AMDTP]: notify registered \n");
#if defined(AMDTPS_TXTEST)
        counter = 0;
        AmdtpsSendTestData(); //fixme
#endif
    }
    else if (GATT_CLI_CNFG_DEFAULT == config)
    {
        amdtpsCb.txReady = false;
        AMDTP_TRC("[AMDTP]: notify unregistered \n");
    }
    else
    {
        /* Incorrect Configuration */
    }
}

//*****************************************************************************
//
// Send Notification to Client
//
//*****************************************************************************
static void amdtpsSendData(uint8_t *buf, uint16_t len)
{
    ATT_HANDLE_VALUE_PAIR hndl_val_param;
    API_RESULT retval;
    APPL_HANDLE appl_handle;

#ifdef AMDTP_DEBUG_ON
    AMDTP_TRC("Sending Tx On Handle 0x%04X\n", appl_tx_hndl);
#endif

    appl_get_handle_from_device_handle (appl_amdtps_handle.device_id, &appl_handle);
    hndl_val_param.handle =  appl_tx_hndl;
    hndl_val_param.value.val = buf;
    hndl_val_param.value.len = len;

    retval = BT_att_send_hndl_val_ntf
             (
                 &APPL_GET_ATT_INSTANCE(appl_handle),
                 &hndl_val_param
              );

    if (API_SUCCESS != retval)
    {
        AMDTP_ERR( "[** ERR **]: Failed to send, reason 0x%04X", retval);
    }
}

static eAmdtpStatus_t
amdtpsSendAck(eAmdtpPktType_t type, BOOLEAN encrypted, BOOLEAN enableACK, uint8_t *buf, uint16_t len)
{
    ATT_HANDLE_VALUE_PAIR hndl_val_param;
    API_RESULT retval;
    APPL_HANDLE appl_handle;

    AmdtpBuildPkt(&amdtpsCb.core, type, encrypted, enableACK, buf, len);

#ifdef AMDTP_DEBUG_ON
    AMDTP_TRC("Sending Ack On Handle 0x%04X\n", appl_ack_hndl);
#endif
    /* send notification */
    appl_get_handle_from_device_handle (appl_amdtps_handle.device_id, &appl_handle);
    hndl_val_param.handle =  appl_ack_hndl;
    hndl_val_param.value.val = amdtpsCb.core.ackPkt.data;
    hndl_val_param.value.len = amdtpsCb.core.ackPkt.len;

    retval = BT_att_send_hndl_val_ntf
             (
                 &APPL_GET_ATT_INSTANCE(appl_handle),
                 &hndl_val_param
              );

    if (API_SUCCESS != retval)
    {
        AMDTP_ERR( "[** ERR **]: Failed to send measurement, reason 0x%04X", retval);
        return AMDTP_STATUS_TX_NOT_READY;
    }

    return AMDTP_STATUS_SUCCESS;
}

//*****************************************************************************
//
// Timer Expiration handler
//
//*****************************************************************************
void amdtps_timeout_timer_expired(void *data, UINT16 datalen)
{
    API_RESULT retval = API_SUCCESS;
    uint8_t ack[1];
    ack[0] = amdtpsCb.core.txPktSn;
    AMDTP_TRC("amdtps tx timeout, txPktSn = %d\n", amdtpsCb.core.txPktSn);
    AmdtpSendControl(&amdtpsCb.core, AMDTP_CONTROL_RESEND_REQ, ack, 1);
    // fire a timer for receiving an AMDTP_STATUS_RESEND_REPLY ACK
    if (BT_TIMER_HANDLE_INIT_VAL != amdtpsCb.core.timeoutTimer)
    {
        retval = BT_stop_timer (amdtpsCb.core.timeoutTimer);
        AMDTP_TRC (
        "[AMDTP]: Stopping Timer with result 0x%04X, timer handle %p\n",
        retval, amdtpsCb.core.timeoutTimer);
        amdtpsCb.core.timeoutTimer = BT_TIMER_HANDLE_INIT_VAL;
    }

    retval = BT_start_timer
         (
             &amdtpsCb.core.timeoutTimer,
             amdtpsCb.core.txTimeout,
             amdtps_timeout_timer_expired,
             NULL,
             0
         );
    AMDTP_TRC (
    "[AMDTP]: Started Timer with result 0x%04X, timer handle %p\n",
    retval, amdtpsCb.core.timeoutTimer);
}

void amdtpsHandleValueCnf(
               APPL_HANDLE    * appl_handle,
               UCHAR         * event_data,
               UINT16        event_datalen
               )
{
    ATT_ATTR_HANDLE  attr_handle;

    BT_UNPACK_LE_2_BYTE(&attr_handle, event_data);

#ifdef AMDTP_DEBUG_ON
    AMDTP_TRC("appl_handle 0x%x attr_handle = 0x%x\n", *appl_handle, attr_handle);
#endif

#if !defined(AMDTPS_RXONLY) && !defined(AMDTPS_RX2TX)
    if ( attr_handle == appl_tx_hndl )
    {
        amdtpsCb.txReady = true;
        // process next data
        AmdtpSendPacketHandler(&amdtpsCb.core);
#ifdef AMDTPS_TXTEST
            // fixme when last packet, continue to send next one.
            if (amdtpsCb.core.txState == AMDTP_STATE_WAITING_ACK)
            {
                uint8_t temp[3];
                temp[0] = AMDTP_STATUS_SUCCESS;
                AmdtpPacketHandler(&amdtpsCb.core, AMDTP_PKT_TYPE_ACK, 3, temp);
            }
#endif
    }
#endif
}

void amdtps_mtu_update(APPL_HANDLE    * appl_handle, UINT16 t_mtu)
{
    UINT16 mtu = 0;

    BT_att_access_mtu(&APPL_GET_ATT_INSTANCE(*appl_handle),
        &mtu);

    AMDTP_TRC("appl_handle 0x%x t_mtu = %d %d\n", *appl_handle, t_mtu, mtu);
}

void appl_amdtp_server_reinitialize(void)
{
    API_RESULT retval = API_SUCCESS;

    memset(&amdtpsCb, 0, sizeof(amdtpsCb));
    amdtpsCb.txReady = false;
    amdtpsCb.core.txState = AMDTP_STATE_INIT;
    amdtpsCb.core.rxState = AMDTP_STATE_RX_IDLE;

    amdtpsCb.core.lastRxPktSn = 0;
    amdtpsCb.core.txPktSn = 0;

    resetPkt(&amdtpsCb.core.rxPkt);
    amdtpsCb.core.rxPkt.data = rxPktBuf;

    resetPkt(&amdtpsCb.core.txPkt);
    amdtpsCb.core.txPkt.data = txPktBuf;

    resetPkt(&amdtpsCb.core.ackPkt);
    amdtpsCb.core.ackPkt.data = ackPktBuf;

    amdtpsCb.core.recvCback = amdtpsDtpRecvCback;
    amdtpsCb.core.transCback = amdtpsDtpTransCback;

    amdtpsCb.core.data_sender_func = amdtpsSendData;
    amdtpsCb.core.ack_sender_func = amdtpsSendAck;

    amdtpsCb.core.txTimeout = TX_TIMEOUT_DEFAULT;

    if (BT_TIMER_HANDLE_INIT_VAL != amdtpsCb.core.timeoutTimer)
    {
        retval = BT_stop_timer (amdtpsCb.core.timeoutTimer);
        AMDTP_TRC (
        "[AMDTP]: Stopping Timer with result 0x%04X, timer handle %p\n",
        retval, amdtpsCb.core.timeoutTimer);
        amdtpsCb.core.timeoutTimer = BT_TIMER_HANDLE_INIT_VAL;
    }

#if defined(AMDTPS_RXONLY) || defined(AMDTPS_RX2TX)
    AMDTP_TRC("*** RECEIVED TOTAL %d ***\n", totalLen);
    totalLen = 0;
#endif
}

API_RESULT appl_amdtps_write_cback
           (
                GATT_DB_HANDLE  * handle,
                ATT_VALUE       * value
           )
{
    API_RESULT retval = API_SUCCESS;
    eAmdtpStatus_t status = AMDTP_STATUS_UNKNOWN_ERROR;
    amdtpPacket_t *pkt = NULL;

#if 0
    uint16_t i = 0;
    AMDTP_TRC("============= data arrived start ===============\n");
    for (i = 0; i < value->len; i++)
    {
        AMDTP_TRC("%x\t", value->val[i]);
    }
    AMDTP_TRC("============= data arrived end ===============\n");
#endif

    if (GATT_CHAR_AMDTP_RX == handle->char_id)
    {
#if defined(AMDTPS_RX2TX)
        amdtpsSendData(value->val, value->len);
#endif
#if defined(AMDTPS_RXONLY) || defined(AMDTPS_RX2TX)
        totalLen += value->len;
        AMDTP_TRC("received data len %d, total %d\n", value->len, totalLen);
        return API_SUCCESS;
#else /* RXONLY && RX2TX */
        status = AmdtpReceivePkt(&amdtpsCb.core, &amdtpsCb.core.rxPkt, value->len, value->val);
#endif
    }
    else if (GATT_CHAR_AMDTP_ACK == handle->char_id)
    {
        status = AmdtpReceivePkt(&amdtpsCb.core, &amdtpsCb.core.ackPkt, value->len, value->val);
    }

    if (status == AMDTP_STATUS_RECEIVE_DONE)
    {
        if (GATT_CHAR_AMDTP_RX == handle->char_id)
        {
            pkt = &amdtpsCb.core.rxPkt;
        }
        else if (GATT_CHAR_AMDTP_ACK == handle->char_id)
        {
            pkt = &amdtpsCb.core.ackPkt;
        }

        AmdtpPacketHandler(&amdtpsCb.core, (eAmdtpPktType_t)pkt->header.pktType, pkt->len - AMDTP_CRC_SIZE_IN_PKT, pkt->data);
    }

    return retval;
}

//*****************************************************************************
//
//! @brief Send data to Client via notification
//!
//! @param type - packet type
//! @param encrypted - is packet encrypted
//! @param enableACK - does client need to response
//! @param buf - data
//! @param len - data length
//!
//! @return status
//
//*****************************************************************************
eAmdtpStatus_t
AmdtpsSendPacket(eAmdtpPktType_t type, BOOLEAN encrypted, BOOLEAN enableACK, uint8_t *buf, uint16_t len)
{
    //
    // Check if ready to send notification
    //
    if ( !amdtpsCb.txReady )
    {
        //set in callback amdtpsHandleValueCnf
        AMDTP_TRC("data sending failed, not ready for notification.\n", NULL);
        return AMDTP_STATUS_TX_NOT_READY;
    }

    //
    // Check if the service is idle to send
    //
    if ( amdtpsCb.core.txState != AMDTP_STATE_TX_IDLE )
    {
        AMDTP_TRC("data sending failed, tx state = %d\n", amdtpsCb.core.txState);
        return AMDTP_STATUS_BUSY;
    }

    //
    // Check if data length is valid
    //
    if ( len > AMDTP_MAX_PAYLOAD_SIZE )
    {
        AMDTP_TRC("data sending failed, exceed maximum payload, len = %d.\n", len);
        return AMDTP_STATUS_INVALID_PKT_LENGTH;
    }

    AmdtpBuildPkt(&amdtpsCb.core, type, encrypted, enableACK, buf, len);

    // send packet
    AmdtpSendPacketHandler(&amdtpsCb.core);

    return AMDTP_STATUS_SUCCESS;
}

#endif /* AMDTPS */

