/**
 * Copyright (c) 2017 - 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.
 * 
 */
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>

#include "nrf.h"
#include "nrf_drv_usbd.h"
#include "nrf_drv_clock.h"
#include "nrf_drv_power.h"

#include "ble_dtm.h"
#include "app_fifo.h"

#include "app_error.h"
#include "app_util.h"
#include "app_usbd_core.h"
#include "app_usbd.h"
#include "app_usbd_string_desc.h"
#include "app_usbd_cdc_acm.h"
#include "app_usbd_serial_num.h"

#include "boards.h"
#include "bsp.h"


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


/**@file
 * @defgroup usbd_cdc_acm_example main.c
 * @{
 * @ingroup usbd_cdc_acm_example
 * @brief USBD CDC ACM example
 *
 */

#define LED_USB_RESUME      (BSP_BOARD_LED_0)
#define LED_CDC_ACM_OPEN    (BSP_BOARD_LED_1)
#define LED_CDC_ACM_RX      (BSP_BOARD_LED_2)
#define LED_CDC_ACM_TX      (BSP_BOARD_LED_3)


/**
 * @brief Enable power USB detection
 *
 * Configure if example supports USB port connection
 */
#ifndef USBD_POWER_DETECTION
#define USBD_POWER_DETECTION true
#endif


static void cdc_acm_user_ev_handler(app_usbd_class_inst_t const * p_inst,
                                    app_usbd_cdc_acm_user_event_t event);

#define CDC_ACM_COMM_INTERFACE  0
#define CDC_ACM_COMM_EPIN       NRF_DRV_USBD_EPIN2

#define CDC_ACM_DATA_INTERFACE  1
#define CDC_ACM_DATA_EPIN       NRF_DRV_USBD_EPIN1
#define CDC_ACM_DATA_EPOUT      NRF_DRV_USBD_EPOUT1


/**
 * @brief CDC_ACM class instance
 * */
APP_USBD_CDC_ACM_GLOBAL_DEF(m_app_cdc_acm,
                            cdc_acm_user_ev_handler,
                            CDC_ACM_COMM_INTERFACE,
                            CDC_ACM_DATA_INTERFACE,
                            CDC_ACM_COMM_EPIN,
                            CDC_ACM_DATA_EPIN,
                            CDC_ACM_DATA_EPOUT,
                            APP_USBD_CDC_COMM_PROTOCOL_AT_V250
);

static char m_rx_buffer[NRF_DRV_USBD_EPSIZE * 16];
uint16_t    dtm_cmd_from_uart = 0;     // Packed command containing command_code:freqency:length:payload in 2:6:6:2 bits.
//static char m_tx_buffer[NRF_DRV_USBD_EPSIZE * 16];


#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. */

#define SERIAL_NUMBER_STRING_SIZE (16)

#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];
bool got_cmd = false;

/**@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);
}



/**
 * @brief User event handler @ref app_usbd_cdc_acm_user_ev_handler_t (headphones)
 * */
static void cdc_acm_user_ev_handler(app_usbd_class_inst_t const * p_inst,
                                    app_usbd_cdc_acm_user_event_t event)
{
    app_usbd_cdc_acm_t const * p_cdc_acm = app_usbd_cdc_acm_class_get(p_inst);
    //UNUSED_PARAMETER(p_inst);
    
    switch (event)
    {
        case APP_USBD_CDC_ACM_USER_EVT_PORT_OPEN:
        {   

            NRF_LOG_INFO("Port open!"); 
            bsp_board_led_on(1);
            app_usbd_cdc_acm_read(&m_app_cdc_acm,
                                   m_rx_buffer,
                                   2);
            break;
        }
        case APP_USBD_CDC_ACM_USER_EVT_PORT_CLOSE:
            NRF_LOG_INFO("Port closed!");
            app_fifo_flush(&usb_cdc_dtm_cmd_fifo);
            bsp_board_led_off(1);
            break;

        case APP_USBD_CDC_ACM_USER_EVT_TX_DONE:
            NRF_LOG_INFO("Tx done!");
            bsp_board_led_invert(LED_CDC_ACM_TX);
            break;

        case APP_USBD_CDC_ACM_USER_EVT_RX_DONE:
        {
            /*Get amount of data transfered*/
            size_t size = app_usbd_cdc_acm_rx_size(p_cdc_acm);
            for(int i = 0; i < size; i++)
            {
                NRF_LOG_INFO("RX(%i): char: %.2X", i, m_rx_buffer[i]);
            }

            
            dtm_cmd_from_uart = 0;
            dtm_cmd_from_uart = ((dtm_cmd_t)m_rx_buffer[0]) << 8;
            dtm_cmd_from_uart |= (dtm_cmd_t)m_rx_buffer[1];
            got_cmd = true;
            
            /*Setup next transfer*/
             app_usbd_cdc_acm_read(&m_app_cdc_acm,
                                    m_rx_buffer,
                                    2);

            //ASSERT(ret == NRF_SUCCESS); /*Should not happen*/
            bsp_board_led_invert(LED_CDC_ACM_RX);
            break;
        }

        default:
            NRF_LOG_INFO("Default!");
            break;
    }
}

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    dtm_error_code;
    dtm_event_t result;                    // Result of a DTM operation.
    
    bsp_board_init(BSP_INIT_LEDS);
    ret_code_t ret;

    static const app_usbd_config_t usbd_config = {
        .ev_state_proc = usbd_user_ev_handler
    };

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

    NRF_LOG_DEFAULT_BACKENDS_INIT(); 

    ret = nrf_drv_clock_init();
    APP_ERROR_CHECK(ret);
    
    nrf_drv_clock_lfclk_request(NULL);

    while(!nrf_drv_clock_lfclk_is_running())
    {
        /* Just waiting */
    }

    app_usbd_serial_num_generate();

    ret = app_usbd_init(&usbd_config);
    APP_ERROR_CHECK(ret);
    NRF_LOG_INFO("USBD CDC ACM example started.");

    app_usbd_class_inst_t const * class_cdc_acm = app_usbd_cdc_acm_class_inst_get(&m_app_cdc_acm);
    ret = app_usbd_class_append(class_cdc_acm);
    APP_ERROR_CHECK(ret);

    if (USBD_POWER_DETECTION)
    {
        ret = app_usbd_power_events_enable();
        APP_ERROR_CHECK(ret);
    }
    else
    {
        NRF_LOG_INFO("No USB power detection enabled\r\nStarting USB now");

        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.
        UNUSED_RETURN_VALUE(dtm_wait());

        //Channel 0, tx test (Used for test purpose only)
        /* dtm_cmd_put(0b0000000000000000);
        dtm_cmd_put(0b0000001000000100);
        dtm_cmd_put(0b0000000100001100);
        dtm_cmd_put(0b1000000011111100);*/

        //Channel 19, tx test (Used for test purpose only)
        /*dtm_cmd_put(0b0000000000000000);
        dtm_cmd_put(0b0000001000000100);
        dtm_cmd_put(0b0000000100001100);
        dtm_cmd_put(0b1001001111111100);*/

        //Channel 39, tx test (Used for test purpose only)
        /*dtm_cmd_put(0b0000000000000000); 
        dtm_cmd_put(0b0000001000000100);
        dtm_cmd_put(0b0000000100001100);
        dtm_cmd_put(0b1010011111111100);*/

        if(got_cmd)
        {
            uint32_t err = dtm_cmd_put(dtm_cmd_from_uart);

            if ( err != DTM_SUCCESS)
            {
                NRF_LOG_INFO("dtm error! err_code = %i",err);
                // 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.
            } 
            else
            {
                got_cmd = false;
            }
            

            // 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))
            {
                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 = app_usbd_cdc_acm_write(&m_app_cdc_acm, result_buf, 2);
            }
        }
        UNUSED_RETURN_VALUE(NRF_LOG_PROCESS());
        __WFE();
    }
}

/** @} */
