/**
 * Copyright (c) 2017 - 2020, 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 "sdk_common.h"
#if NRF_MODULE_ENABLED(NRF_CLI_CDC_ACM)
#include "nrf_cli_cdc_acm.h"
#include "nrf_queue.h"
#include "app_error.h"
#include "nrf_assert.h"


#if APP_USBD_CONFIG_EVENT_QUEUE_ENABLE
#error "Current CLI CDC implementation supports only USB with event queue disabled (see APP_USBD_CONFIG_EVENT_QUEUE_ENABLE)"
#endif

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

/*lint -save -e26 -e40 -e64 -e123 -e505 -e651*/

/**
 * @brief CDC_ACM class instance.
 * */
APP_USBD_CDC_ACM_GLOBAL_DEF(nrf_cli_cdc_acm,
                            cdc_acm_user_ev_handler,
                            NRF_CLI_CDC_ACM_COMM_INTERFACE,
                            NRF_CLI_CDC_ACM_DATA_INTERFACE,
                            NRF_CLI_CDC_ACM_COMM_EPIN,
                            NRF_CLI_CDC_ACM_DATA_EPIN,
                            NRF_CLI_CDC_ACM_DATA_EPOUT,
                            APP_USBD_CDC_COMM_PROTOCOL_AT_V250
);

/*lint -restore*/

NRF_QUEUE_DEF(uint8_t,
              m_rx_queue,
              2*NRF_DRV_USBD_EPSIZE,
              NRF_QUEUE_MODE_OVERFLOW);

static char m_rx_buffer[NRF_DRV_USBD_EPSIZE];

static nrf_cli_cdc_acm_internal_t * mp_internal;

/**
 * @brief Set new buffer and process any data if already present
 *
 * This is internal function.
 * The result of its execution is the library waiting for the event of the new data.
 * If there is already any data that was returned from the CDC internal buffer
 * it would be processed here.
 */
static void cdc_acm_process_and_prepare_buffer(app_usbd_cdc_acm_t const * p_cdc_acm)
{
    for (;;)
    {
        if (!nrf_queue_is_empty(&m_rx_queue))
        {
            mp_internal->p_cb->handler(NRF_CLI_TRANSPORT_EVT_RX_RDY, mp_internal->p_cb->p_context);
        }
        ret_code_t ret = app_usbd_cdc_acm_read_any(&nrf_cli_cdc_acm,
                                                   m_rx_buffer,
                                                   sizeof(m_rx_buffer));
        if (ret == NRF_SUCCESS)
        {
            size_t size = app_usbd_cdc_acm_rx_size(p_cdc_acm);
            size_t qsize = nrf_queue_in(&m_rx_queue, m_rx_buffer, size);
            ASSERT(size == qsize);
            UNUSED_VARIABLE(qsize);
        }
        else if (ret == NRF_ERROR_IO_PENDING)
        {
            break;
        }
        else
        {
            APP_ERROR_CHECK(ret);
            break;
        }
    }
}

/**
 * @brief User event handler.
 * */
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);


    switch (event)
    {
        case APP_USBD_CDC_ACM_USER_EVT_PORT_OPEN:
        {
            /*Setup first transfer*/
            cdc_acm_process_and_prepare_buffer(p_cdc_acm);
            break;
        }
        case APP_USBD_CDC_ACM_USER_EVT_PORT_CLOSE:
            mp_internal->p_cb->handler(NRF_CLI_TRANSPORT_EVT_TX_RDY, mp_internal->p_cb->p_context);
            break;
        case APP_USBD_CDC_ACM_USER_EVT_TX_DONE:
            mp_internal->p_cb->handler(NRF_CLI_TRANSPORT_EVT_TX_RDY, mp_internal->p_cb->p_context);
            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);
            size_t qsize = nrf_queue_in(&m_rx_queue, m_rx_buffer, size);
            ASSERT(size == qsize);
            UNUSED_VARIABLE(qsize);

            /*Setup next transfer*/
            cdc_acm_process_and_prepare_buffer(p_cdc_acm);
            break;
        }
        default:
            break;
    }
}

static ret_code_t cli_cdc_acm_init(nrf_cli_transport_t const * p_transport,
                                   void const *                p_config,
                                   nrf_cli_transport_handler_t evt_handler,
                                   void *                      p_context)
{
    UNUSED_PARAMETER(p_config);
    nrf_cli_cdc_acm_internal_t * p_internal =
                                   CONTAINER_OF(p_transport, nrf_cli_cdc_acm_internal_t, transport);
    p_internal->p_cb->handler = evt_handler;
    p_internal->p_cb->p_context = p_context;

    mp_internal = p_internal;
    return NRF_SUCCESS;
}

static ret_code_t cli_cdc_acm_uninit(nrf_cli_transport_t const * p_transport)
{
    UNUSED_PARAMETER(p_transport);
    return NRF_SUCCESS;
}

static ret_code_t cli_cdc_acm_enable(nrf_cli_transport_t const * p_transport,
                                     bool blocking)
{
    UNUSED_PARAMETER(p_transport);
    if (blocking)
    {
        return NRF_ERROR_NOT_SUPPORTED;
    }
    else
    {
        return NRF_SUCCESS;
    }
}

static ret_code_t cli_cdc_acm_read(nrf_cli_transport_t const * p_transport,
                                   void *                      p_data,
                                   size_t                      length,
                                   size_t *                    p_cnt)
{
    ASSERT(p_cnt);
    UNUSED_PARAMETER(p_transport);
    size_t size = nrf_queue_out(&m_rx_queue, p_data, length);
    *p_cnt = size;

    return NRF_SUCCESS;
}

static ret_code_t cli_cdc_acm_write(nrf_cli_transport_t const * p_transport,
                                    void const *                p_data,
                                    size_t                      length,
                                    size_t *                    p_cnt)
{
    ASSERT(p_cnt);
    UNUSED_PARAMETER(p_transport);
    ret_code_t ret;

    ret = app_usbd_cdc_acm_write(&nrf_cli_cdc_acm, p_data, length);
    if (ret == NRF_SUCCESS || ret == NRF_ERROR_INVALID_STATE)
    {
        *p_cnt = length;
        ret = NRF_SUCCESS;
    }
    else if (ret == NRF_ERROR_BUSY)
    {
        *p_cnt = 0;
        ret = NRF_SUCCESS;
    }
    else
    {
        /* Nothing to do */
    }

    return ret;
}

const nrf_cli_transport_api_t nrf_cli_cdc_acm_transport_api = {
        .init = cli_cdc_acm_init,
        .uninit = cli_cdc_acm_uninit,
        .enable = cli_cdc_acm_enable,
        .read = cli_cdc_acm_read,
        .write = cli_cdc_acm_write,
};

#endif // NRF_MODULE_ENABLED(NRF_CLI_CDC_ACM)