/**
 * Copyright (c) 2012 - 2018, Nordic Semiconductor ASA
 * 
 * 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, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, 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 Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 * 
 * 4. This software, with or without modification, must only be used with a
 *    Nordic Semiconductor ASA integrated circuit.
 * 
 * 5. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 * 
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA 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.
 * 
 */
/**
  @defgroup dtm_standalone main.c
  @{
  @ingroup ble_sdk_app_dtm_serial
  @brief Stand-alone DTM application for UART interface.

 */

#include <stdint.h>
#include <stdbool.h>
#include "nrf.h"
#include "ble_dtm.h"
#include "boards.h"
#include "usb_cdc.h"
#include "app_usbd_string_desc.h"
#include "app_usbd.h"
#include "app_usbd_core.h"
#include "app_usbd_string_config.h"
#include "app_usbd_serial_num.h"
#include "nrf_drv_clock.h"
#include "app_fifo.h"

#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

#ifndef USBD_POWER_DETECTION
#define USBD_POWER_DETECTION true
#endif


// @note: The BLE DTM 2-wire UART standard specifies 8 data bits, 1 stop bit, no flow control.
//        These parameters are not configurable in the BLE standard.

/**@details Maximum iterations needed in the main loop between stop bit 1st byte and start bit 2nd
 * byte. DTM standard allows 5000us delay between stop bit 1st byte and start bit 2nd byte.
 * As the time is only known when a byte is received, then the time between between stop bit 1st
 * byte and stop bit 2nd byte becomes:
 *      5000us + transmission time of 2nd byte.
 *
 * Byte transmission time is (Baud rate of 19200):
 *      10bits * 1/19200 = approx. 520 us/byte (8 data bits + start & stop bit).
 *
 * Loop time on polling UART register for received byte is defined in ble_dtm.c as:
 *   UART_POLL_CYCLE = 260 us
 *
 * The max time between two bytes thus becomes (loop time: 260us / iteration):
 *      (5000us + 520us) / 260us / iteration = 21.2 iterations.
 *
 * This is rounded down to 21.
 *
 * @note If UART bit rate is changed, this value should be recalculated as well.
 */
#define MAX_ITERATIONS_NEEDED_FOR_NEXT_BYTE ((5000 + 2 * UART_POLL_CYCLE) / UART_POLL_CYCLE)

#define MAX_TEST_DATA_BYTES     (15U)                /**< max number of test bytes to be used for tx and rx. */
#define UART_TX_BUF_SIZE 256                         /**< UART TX buffer size. */
#define UART_RX_BUF_SIZE 256                         /**< UART RX buffer size. */
static uint8_t     rx_byte;
//static bool is_rx_ready = 0;
static usb_callback_t usb_callback;
#define SERIAL_NUMBER_STRING_SIZE (16)
uint16_t g_extern_serial_number[SERIAL_NUMBER_STRING_SIZE + 1];

#define     USB_CDC_DTM_CMD_FIFO_SIZE 64
app_fifo_t  usb_cdc_dtm_cmd_fifo;
uint8_t     usb_cdc_dtm_cmd_fifo_buf[USB_CDC_DTM_CMD_FIFO_SIZE];


/**@brief Function for splitting UART command bit fields into separate command parameters for the DTM library.
 *
 * @param[in]   command   The packed UART command.
 * @return      result status from dtmlib.
 */
static uint32_t dtm_cmd_put(uint16_t command)
{
    dtm_cmd_t      command_code = (command >> 14) & 0x03;
    dtm_freq_t     freq         = (command >> 8) & 0x3F;
    uint32_t       length       = (command >> 2) & 0x3F;
    dtm_pkt_type_t payload      = command & 0x03;

    return dtm_cmd(command_code, freq, length, payload);
}

static void serial_number_string_create(void)
{
    g_extern_serial_number[0] = (uint16_t)APP_USBD_DESCRIPTOR_STRING << 8 |
                                          sizeof(g_extern_serial_number);

    uint32_t dev_id_hi = NRF_FICR->DEVICEID[1];
    uint32_t dev_id_lo = NRF_FICR->DEVICEID[0];

    uint64_t device_id = (((uint64_t)dev_id_hi) << 32) | dev_id_lo;

    for (size_t i = 1; i < SERIAL_NUMBER_STRING_SIZE + 1; ++i)
    {
        char tmp[2];
        (void)snprintf(tmp, sizeof(tmp), "%x", (unsigned int)(device_id & 0xF));
        g_extern_serial_number[i] = tmp[0];
        device_id >>= 4;
    }
}

/**@brief Function for application main entry.
 *
 * @details This function serves as an adaptation layer between a 2-wire UART interface and the
 *          dtmlib. After initialization, DTM commands submitted through the UART are forwarded to
 *          dtmlib and events (i.e. results from the command) is reported back through the UART.
 */
static void received_usb_data(uint8_t *data, uint32_t length)
{
    //TODO handle the data that is received from USB CDC in this method
    ret_code_t err_code = app_fifo_write(&usb_cdc_dtm_cmd_fifo, (uint8_t*)data, (uint32_t*)length);
    //rx_byte = *data;
    //is_rx_ready = 1;
    if(err_code != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("UART RX Buffer overflow!");
    }
    
}

static void usbd_user_ev_handler(app_usbd_event_type_t event)
{
    switch (event)
    {
        case APP_USBD_EVT_DRV_SUSPEND:
            break;

        case APP_USBD_EVT_DRV_RESUME:
            break;

        case APP_USBD_EVT_STARTED:
            break;

        case APP_USBD_EVT_STOPPED:
            app_usbd_disable();
            bsp_board_leds_off();
            break;

        case APP_USBD_EVT_POWER_DETECTED:
            NRF_LOG_INFO("USB power detected");
            //bsp_board_led_on(1);
            if (!nrf_drv_usbd_is_enabled())
            {
                app_usbd_enable();
            }
            break;

        case APP_USBD_EVT_POWER_REMOVED:
            bsp_board_led_off(0);
            NRF_LOG_INFO("USB power removed");
            app_usbd_stop();
            break;

        case APP_USBD_EVT_POWER_READY:
            NRF_LOG_INFO("USB ready");
            bsp_board_led_on(0);
            app_usbd_start();
            break;

        default:
            break;
    }
}
int main(void)
{
    uint32_t    current_time;
    uint32_t    dtm_error_code;
    uint32_t    msb_time          = 0;     // Time when MSB of the DTM command was read. Used to catch stray bytes from "misbehaving" testers.
    bool        is_msb_read       = false; // True when MSB of the DTM command has been read and the application is waiting for LSB.
    uint16_t    dtm_cmd_from_uart = 0;     // Packed command containing command_code:freqency:length:payload in 2:6:6:2 bits.
    //uint8_t     rx_byte;                   // Last byte read from UART.
    dtm_event_t result;                    // Result of a DTM operation.
    
    bsp_board_init(BSP_INIT_LEDS);
    ret_code_t ret;

    ret = NRF_LOG_INIT(NULL);
    APP_ERROR_CHECK(ret);
    NRF_LOG_DEFAULT_BACKENDS_INIT(); 

    ret = nrf_drv_clock_init();
    APP_ERROR_CHECK(ret);
    //uart_init();
    usb_callback.receive_from_cdc = received_usb_data;

    static const app_usbd_config_t usbd_config = {
        .ev_state_proc = usbd_user_ev_handler,
    };
    
    serial_number_string_create();
    ret = app_usbd_init(&usbd_config);
    
    APP_ERROR_CHECK(ret);
    ret = usb_cdc__init(usb_callback);
    APP_ERROR_CHECK(ret);
    if (USBD_POWER_DETECTION)
    {
        ret = app_usbd_power_events_enable();
        APP_ERROR_CHECK(ret);
    }
    else
    {
        app_usbd_enable();
        app_usbd_start();
    }  
    app_fifo_init(&usb_cdc_dtm_cmd_fifo, usb_cdc_dtm_cmd_fifo_buf, USB_CDC_DTM_CMD_FIFO_SIZE);
    dtm_error_code = dtm_init();

    NRF_LOG_INFO("Starting!");
    if (dtm_error_code != DTM_SUCCESS)
    {
        // If DTM cannot be correctly initialized, then we just return.
        return -1;
    }
    for (;;)
    {
        while (app_usbd_event_queue_process());
        
        // Will return every timeout, 625 us.
        current_time = dtm_wait();
        while(app_fifo_get(&usb_cdc_dtm_cmd_fifo, &rx_byte) == NRF_SUCCESS)
        {
            
            if (!is_msb_read)
            {
                
                // This is first byte of two-byte command.
                is_msb_read       = true;
                dtm_cmd_from_uart = ((dtm_cmd_t)rx_byte) << 8;
                msb_time          = current_time;
                NRF_LOG_INFO("Wait for second byte command!");
                // Go back and wait for 2nd byte of command word.
                continue;
            }

            // This is the second byte read; combine it with the first and process command
            if (current_time > (msb_time + MAX_ITERATIONS_NEEDED_FOR_NEXT_BYTE))
            {
                // More than ~5mS after msb: Drop old byte, take the new byte as MSB.
                // The variable is_msb_read will remains true.
                // Go back and wait for 2nd byte of the command word.
                dtm_cmd_from_uart = ((dtm_cmd_t)rx_byte) << 8;
                msb_time          = current_time;
                continue;
            }
             
            // 2-byte UART command received.
            is_msb_read        = false;
            dtm_cmd_from_uart |= (dtm_cmd_t)rx_byte;

            if (dtm_cmd_put(dtm_cmd_from_uart) != DTM_SUCCESS)
            {
                // Extended error handling may be put here.
                // Default behavior is to return the event on the UART (see below);
                // the event report will reflect any lack of success.
            }

            // Retrieve result of the operation. This implementation will busy-loop
            // for the duration of the byte transmissions on the UART.
            if (dtm_event_get(&result))
            {
            /*// Report command status on the UART.
            // Transmit MSB of the result.
            while (usb_cdc__write((uint8_t*)((result >> 8) & 0xFF),sizeof(((result >> 8) & 0xFF))));
            // Transmit LSB of the result.
            while (usb_cdc__write((uint8_t*)(result & 0xFF),sizeof((result & 0xFF))));
            */
                uint8_t result_buf[2];
                // Report command status on the UART.
                // Transmit MSB of the result.
                result_buf[0] = (result >> 8) & 0xFF;
                // Transmit LSB of the result.
                result_buf[1] = result & 0xFF;
                
                ret = usb_cdc__write(result_buf, 2);

                if(ret != NRF_SUCCESS)
                {
                    NRF_LOG_ERROR("USBD CDC write error: %i", ret);
                }     
            }
        }
        UNUSED_RETURN_VALUE(NRF_LOG_PROCESS());
        __WFE();
    }
}

/// @}
