/**
 * Copyright (c) 2016 - 2017, 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_config.h"
#include "uart_async.h"
#include "uart_drv.h"
#include "app_error.h"
#include "nrf_balloc.h"
#include "nrf_drv_timer.h"
#include "nrf_drv_ppi.h"
#include "nrf_uart.h"
#include "nrf_queue.h"
#define NRF_LOG_LEVEL 2
#define NRF_LOG_MODULE_NAME uart_async
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();

static uart_async_evt_handler m_evt_handler;
#define POOL_SZ 3UL
#define MAX_CHUNK_SZ 255UL

NRF_BALLOC_DEF(m_rx_pool, MAX_CHUNK_SZ, POOL_SZ);

NRF_QUEUE_DEF(uint8_t *, m_rxdata_queue, 3, NRF_QUEUE_MODE_NO_OVERFLOW);

static nrf_drv_timer_t m_timer = NRF_DRV_TIMER_INSTANCE(2);

#if CONCAT_3(TIMER, 3,_ENABLED) == 0
#error "Timer instance not enabled"
#endif

typedef enum
{
    PPI_CH_RXRDY_CLEAR,
    PPI_CH_COMPARE_SHUTDOWN,
    PPI_CH_MAX
} uart_async_ppi_channel_t;

static nrf_ppi_channel_t m_ppi_channels[PPI_CH_MAX];
static int32_t m_alloc_cnt;
static uint32_t m_rx_count;
static uint32_t m_sub_rx_count;
static uint8_t * mp_curr_rx_buf;
static uint32_t m_rx_free_cnt;
static uint32_t m_rx_chunk_size;

#define PPI_CH_SETUP(_ch, _evt, _tsk, _fork)                               \
    ret = nrf_drv_ppi_channel_assign(m_ppi_channels[_ch], _evt, _tsk);     \
    if (ret != NRF_SUCCESS)                                                \
    {                                                                      \
        return ret;                                                        \
    }                                                                      \
    if (_fork)                                                             \
    {                                                                      \
        ret = nrf_drv_ppi_channel_fork_assign(m_ppi_channels[_ch], _fork); \
        if (ret != NRF_SUCCESS)                                            \
        {                                                                  \
            return ret;                                                    \
        }                                                                  \
    }

static void uart_evt_handler(uart_drv_evt_t * p_evt)
{
    ret_code_t ret;
    switch (p_evt->type)
    {
    case UART_DRV_EVT_TX_DONE:
    {
        NRF_LOG_INFO("Tx done");
        uart_async_evt_t evt = {
            .type = UART_ASYNC_EVT_TX_DONE,
            .data.rxtx.p_data = p_evt->data.rxtx.p_data,
            .data.rxtx.length = p_evt->data.rxtx.length,
        };
        m_evt_handler(&evt);
        break;
    }
    case UART_DRV_EVT_RX_BUF_REQ:
    {
        uint8_t * p_data = nrf_balloc_alloc(&m_rx_pool);
        if (p_data)
        {
            ret = nrf_queue_push(&m_rxdata_queue, &p_data);
            if (ret != NRF_SUCCESS)
            {
                NRF_LOG_ERROR("buf queue full");
                APP_ERROR_CHECK_BOOL(false);
            }

            m_alloc_cnt++;
            uart_drv_rx_buf_rsp(p_data, m_rx_chunk_size);
        }
        else
        {
            NRF_LOG_ERROR("Failed to allocate buffer.");
            APP_ERROR_CHECK_BOOL(false);
        }
        break;
    }
    case UART_DRV_EVT_RX_DATA:
    {

        uint32_t rx_amount = p_evt->data.rxtx.length - m_sub_rx_count;
        if (rx_amount)
        {
            m_rx_count += rx_amount;
            uart_async_evt_t evt = {
                .type = UART_ASYNC_EVT_RX_DATA,
                .data.rxtx.p_data = &p_evt->data.rxtx.p_data[m_sub_rx_count],
                .data.rxtx.length = rx_amount,
            };
            NRF_LOG_INFO("rx data addr:%x, idx:%d amount:%d",p_evt->data.rxtx.p_data,  m_sub_rx_count, rx_amount);
            //NRF_LOG_HEXDUMP_INFO(&p_evt->data.rxtx.p_data[m_sub_rx_count], rx_amount);
            m_sub_rx_count = 0;

            if(p_evt->data.rxtx.p_data != mp_curr_rx_buf)
            {
                NRF_LOG_ERROR("address mismatch");
            }

            ret = nrf_queue_pop(&m_rxdata_queue, &mp_curr_rx_buf);
            if (ret != NRF_SUCCESS)
            {
                NRF_LOG_ERROR("rx_buf queue empty");
                APP_ERROR_CHECK_BOOL(false);
            }

            m_evt_handler(&evt);
        }
        else
        {
            NRF_LOG_ERROR("rx zero length: %x", p_evt->data.rxtx.p_data);
            //zero length packet is freed immediately and not forwarded to the application.
            APP_ERROR_CHECK_BOOL(false);
//            uart_async_rx_free(p_evt->data.rxtx.p_data, p_evt->data.rxtx.length);
        }
        break;
    }
    default:
        APP_ERROR_CHECK_BOOL(false);
        break;
    }
}

static void tmr_evt_handler(nrf_timer_event_t event_type, void * p_context)
{
    uint32_t capt_rx_count = NRF_TIMER3->CC[2];

    if (capt_rx_count > m_rx_count)
    {
        uint32_t rx_amount = capt_rx_count - m_rx_count;
        uart_async_evt_t evt = {
            .type = UART_ASYNC_EVT_RX_DATA,
            .data.rxtx.p_data = &mp_curr_rx_buf[m_sub_rx_count],
            .data.rxtx.length = rx_amount,
        };
        NRF_LOG_INFO("tmr handler length: %d idx:%d", rx_amount,m_sub_rx_count);
        //NRF_LOG_HEXDUMP_INFO(&mp_curr_rx_buf[m_sub_rx_count], rx_amount);
        m_sub_rx_count += rx_amount;
        m_rx_count = capt_rx_count;
        m_evt_handler(&evt);
    }
    //Dummy timer handler
}

ret_code_t uart_async_init(uart_async_config_t const * p_config, uart_async_evt_handler evt_handler)
{
    ret_code_t ret;

    m_evt_handler  = evt_handler;
    m_rx_count     = 0;
    mp_curr_rx_buf = NULL;
    m_rx_free_cnt  = 0;
    m_sub_rx_count = 0;
    m_alloc_cnt    = 0;

    nrf_drv_timer_config_t tmr_config = NRF_DRV_TIMER_DEFAULT_CONFIG;
    tmr_config.frequency = NRF_TIMER_FREQ_1MHz;
    ret = nrf_drv_timer_init(&m_timer, &tmr_config, tmr_evt_handler);
    if (ret != NRF_SUCCESS)
    {
        return ret;
    }
    nrf_drv_timer_compare(&m_timer, NRF_TIMER_CC_CHANNEL0, p_config->timeout_us, true);

    (void)nrf_drv_ppi_init();

    uint32_t i;
    for (i = 0; i < PPI_CH_MAX; i++)
    {
        ret = nrf_drv_ppi_channel_alloc(&m_ppi_channels[i]);
        if (ret != NRF_SUCCESS)
        {
            //we don't free already allocated channels, system is wrongly configured.
            return ret;
        }
    }

    PPI_CH_SETUP(m_ppi_channels[PPI_CH_RXRDY_CLEAR], 
                 nrf_uart_event_address_get(NRF_UART0, NRF_UART_EVENT_RXDRDY),
                 nrf_drv_timer_task_address_get(&m_timer, NRF_TIMER_TASK_START),
                 nrf_drv_timer_task_address_get(&m_timer, NRF_TIMER_TASK_CLEAR));

    PPI_CH_SETUP(m_ppi_channels[PPI_CH_COMPARE_SHUTDOWN],
                nrf_drv_timer_compare_event_address_get(&m_timer, 0),
                nrf_drv_timer_task_address_get(&m_timer, NRF_TIMER_TASK_SHUTDOWN),
                (uint32_t)&NRF_TIMER3->TASKS_CAPTURE[2]);


    uart_drv_config_t uart_config = {
            .tx_pin        = p_config->tx_pin,
            .rx_pin        = p_config->rx_pin,
            .startrx_evt   = nrf_uarte_event_address_get(NRF_UARTE0, NRF_UARTE_EVENT_ENDRX),
            .endrx_evt     = 0,
            .rxstarted_tsk = 0,
            .rxdone_tsk    = 0,
            .irq_priority  = 7,
            .baudrate      = (nrf_uarte_baudrate_t)p_config->baudrate
    };

    ret = uart_drv_init(&uart_config, uart_evt_handler);
    if (ret != NRF_SUCCESS)
    {
        return ret;
    }

    ret = nrf_balloc_init(&m_rx_pool);
    if (ret != NRF_SUCCESS)
    {
        return ret;
    }

    nrf_queue_reset(&m_rxdata_queue);

    return ret;
}

void uart_async_uninit(void)
{
    nrf_drv_ppi_channel_disable(m_ppi_channels[PPI_CH_RXRDY_CLEAR]);
    uart_drv_uninit();
    nrf_drv_timer_disable(&m_timer);
    nrf_drv_timer_uninit(&m_timer);

    uint32_t i;
    ret_code_t ret;
    for (i = 0; i < PPI_CH_MAX; i++)
    {
        ret = nrf_drv_ppi_channel_disable(m_ppi_channels[i]);
        if (ret != NRF_SUCCESS)
        {
            continue;
        }
        ret = nrf_drv_ppi_channel_free(m_ppi_channels[i]);
        if (ret != NRF_SUCCESS)
        {
            continue;
        }
    }
}

void uart_async_enable(uint32_t chunk_size)
{
    ASSERT(chunk_size <= MAX_CHUNK_SZ);
    uint8_t * p_data;
    p_data = nrf_balloc_alloc(&m_rx_pool);
    m_alloc_cnt++;
    if (p_data == NULL)
    {
        APP_ERROR_CHECK_BOOL(false);
    }
    nrf_drv_timer_clear(&m_timer);
    nrf_drv_ppi_channel_enable(m_ppi_channels[PPI_CH_RXRDY_CLEAR]);
    nrf_drv_ppi_channel_enable(m_ppi_channels[PPI_CH_COMPARE_SHUTDOWN]);

    mp_curr_rx_buf = p_data;
    m_rx_chunk_size = chunk_size;
    uart_drv_rx_start(p_data, chunk_size, false);
}

ret_code_t uart_async_tx(uint8_t * p_data, uint16_t length)
{
    return uart_drv_tx(p_data, length);
}

void uart_async_rx_free(uint8_t * p_data, uint16_t length)
{
    
    m_rx_free_cnt += length;
    if (m_rx_free_cnt == m_rx_chunk_size)
    {
        p_data -= (m_rx_free_cnt - length);
        m_rx_free_cnt = 0;
        nrf_balloc_free(&m_rx_pool, p_data);

        m_alloc_cnt--;
        if (m_alloc_cnt<0)
        {
            NRF_LOG_ERROR("more free than alloc.");
            APP_ERROR_CHECK_BOOL(false);
        }
        NRF_LOG_INFO("Freeing full buffer %x, %d, alloc_cnt:%d",p_data, length, m_alloc_cnt);
    }
    else if (m_rx_free_cnt > m_rx_chunk_size)
    {
        NRF_LOG_ERROR("Unexpected rx free input parameter")
        APP_ERROR_CHECK_BOOL(false);
    }
    else
    {
        NRF_LOG_INFO("Freeing partial buffer: %x, length:%d", p_data, length)
    }

}
