/*
 * ll_hsusart_impl.h
 *
 *  Created on: Oct 29, 2019
 *      Author: abody
 */
#include <f4ll/packet_usart.h>

#include <cstring>

namespace f4ll {

template <typename T> static inline T round_up_to_4(T input)
{
    return (input + 3) & (((T)-1) - 3);
}

packet_usart::packet_usart(USART_TypeDef *usart, DMA_TypeDef *dma, uint32_t stream_rx, uint32_t stream_tx)
    : usart_core(usart, dma, stream_rx, stream_tx)
{
    crc_handler::instance().attach_slot(m_crc_slot);
    LL_USART_EnableIT_IDLE(usart);
    LL_USART_EnableIT_ERROR(usart);
}

void packet_usart::rx_processed(bool second)
{
    m_rx_buffers[second].busy = false;
    m_rx_buffers[second].error = false;
}

void packet_usart::set_callback(ihs_usart_callback *callback, uintptr_t callback_param)
{
    m_user_callback = callback;
    m_user_callback_param = callback_param;
}

void packet_usart::post_packet(uint8_t const *payload, uint8_t length, bool wait_for_crc_queue)
{
    uint16_t payload_length = round_up_to_4((uint16_t)length);

    build_header(m_tx_buffer.pkt, m_tx_serial_nr++, length);
    if (payload) {
        memcpy(m_tx_buffer.pkt.payload, payload, length);
    }
    m_tx_buffer.requested_length = sizeof(m_tx_buffer.pkt.header) + payload_length + sizeof(uint32_t);
    m_tx_buffer.busy = true;
    m_tx_buffer.error = false;

    crc_handler::instance().enqueue(
        m_crc_slot, 0, &m_tx_buffer.pkt, sizeof(packet_header) + payload_length, nullptr,
        reinterpret_cast<uintptr_t>(m_tx_buffer.pkt.payload + payload_length));

    while (wait_for_crc_queue && crc_handler::instance().is_queued(m_crc_slot, 0))
        ;

    setup_transmit(&m_tx_buffer.pkt, m_tx_buffer.requested_length);

    ++m_stats.sent;
}

void packet_usart::setup_receive()
{
    m_rx_buffers[m_rx_buffer_selector].requested_length = sizeof(m_rx_buffers[m_rx_buffer_selector].pkt);
    usart_core::setup_receive(&m_rx_buffers[m_rx_buffer_selector], sizeof(m_rx_buffers[m_rx_buffer_selector].pkt));
}

//////////////////////////////////////
// UsartCore pure virtual functions //
//////////////////////////////////////

void packet_usart::receiver_idle(void)
{
    uint16_t rcvdLen =
        m_rx_buffers[m_rx_buffer_selector].requested_length - LL_DMA_GetDataLength(m_rx_dma.get_dma(), m_rx_dma.get_stream());

    if (rcvdLen >= sizeof(packet_header)) {
        if (check_header(m_rx_buffers[m_rx_buffer_selector].pkt.header)) {
            if (rcvdLen >= sizeof(packet_header) + round_up_to_4((uint16_t)m_rx_buffers[m_rx_buffer_selector].pkt.header.payload_length) +
                               sizeof(uint32_t)) {
                LL_DMA_DisableStream(m_rx_dma.get_dma(), m_rx_dma.get_stream());
            } else {
                ++m_stats.premature_payload;
            }
        } else {
            m_rx_buffers[m_rx_buffer_selector].error = 1;
            LL_DMA_DisableStream(m_rx_dma.get_dma(), m_rx_dma.get_stream());
        }
    } else {
        ++m_stats.premature_hdr;
    }
}

void packet_usart::transmission_complete(void)
{
    LL_USART_DisableDirectionTx(m_usart); // enforcing an idle frame
    LL_USART_EnableDirectionTx(m_usart);
    m_tx_buffer.busy = 0;
}

void packet_usart::framing_error(void) {}

void packet_usart::overrun(void) {}

void packet_usart::rx_dma_transfer_complete(void)
{
    if (check_header(m_rx_buffers[m_rx_buffer_selector].pkt.header)) {
        crc_handler::instance().enqueue(
            m_crc_slot, 1, &m_rx_buffers[m_rx_buffer_selector].pkt,
            sizeof(packet_header) + round_up_to_4((uint16_t)m_rx_buffers[m_rx_buffer_selector].pkt.header.payload_length), this,
            m_rx_buffer_selector);
    } else {
        ++m_stats.hdr_error;
        m_rx_buffers[m_rx_buffer_selector].error = true;
    }
    switch_rx_buffers();
}

void packet_usart::rx_dma_half_transfer(void) {}

void packet_usart::rx_dma_error(dma_helper::dma_error_type reason)
{
    (void)reason;

    m_rx_buffers[m_rx_buffer_selector].error = 1;
    ++m_stats.rx_dma_error;
    switch_rx_buffers();
}

void packet_usart::tx_dma_transfer_complete(void)
{
    LL_USART_EnableIT_TC(m_usart);
    LL_DMA_DisableStream(m_tx_dma.get_dma(), m_tx_dma.get_stream());
}

void packet_usart::tx_dma_half_transfer(void) {}

void packet_usart::tx_dma_error(dma_helper::dma_error_type reason)
{
    (void)reason;

    m_tx_buffer.error = 1;
    ++m_stats.tx_dma_error;
}

///////////////////////
// Private functions //
///////////////////////

void packet_usart::build_header(packet &packet, uint8_t serial_nr, uint8_t length)
{
    uint8_t hash = STARTMARKER;

    packet.header.start_byte = STARTMARKER;
    packet.header.serial = serial_nr;
    hash ^= serial_nr;
    packet.header.payload_length = length;
    hash ^= length;
    packet.header.hash = hash;
}

bool packet_usart::check_header(packet_header &header)
{
    return header.start_byte == STARTMARKER && (header.start_byte ^ header.serial ^ header.payload_length) == header.hash;
}

void packet_usart::switch_rx_buffers(void)
{
    ++m_stats.rcvd;
    m_rx_buffer_selector = !m_rx_buffer_selector;

    if (m_rx_buffers[m_rx_buffer_selector].busy) {
        ++m_stats.overrun;
    }
    setup_receive();
}

///////////////////////////
// crc_handler::ICallback //
///////////////////////////

void packet_usart::crc_succeeded(uintptr_t callback_param, uint32_t crc, uint8_t task)
{
    (void)task;

    Buffer &buf(m_rx_buffers[static_cast<int>(callback_param)]);

    buf.busy = 1;
    if (*(uint32_t *)(buf.pkt.payload + round_up_to_4((uint16_t)buf.pkt.header.payload_length)) != crc) {
        buf.error = 1;
        buf.error_info = crc;
        ++m_stats.payload_errror;
    }
    if (m_user_callback) {
        buf.busy = !m_user_callback->packet_received(this, m_user_callback_param, buf.pkt);
    }
}

void packet_usart::crc_failed(uintptr_t callback_param, uint32_t crc, uint8_t task)
{
    (void)crc;
    (void)task;

    Buffer &buf(m_rx_buffers[static_cast<int>(callback_param)]);
    buf.busy = buf.error = true;
    buf.error_info = 0;
    ++m_stats.payload_errror;
    if (m_user_callback) {
        buf.busy = !m_user_callback->packet_received(this, m_user_callback_param, buf.pkt);
    }
}

} //	namespace f4ll
