/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */


#ifndef INCLUDE_ECXX_PLATFORM_AVR_UART_CPP
#error "this file cannot be compiled directly"
#endif
#undef INCLUDE_ECXX_PLATFORM_AVR_UART_CPP


namespace eCxx {


//
// A simple AVR UART wrapper
//
void UART::begin(uint32_t baudrate, DataFormat dataFormat)
{
    // Set the baudrate
    changeBaudrate(baudrate);

    // Set the transmission format to asynchronous, no/odd/even parity,
    // one/two stop bit, 8-bit character size
#if defined(URSEL) && !defined(UMSEL1) && !defined(UMSEL0)
    // URSEL  UMSEL  UPM1   UPM0   USBS   UCSZ1  UCSZ0  UCPOl
    *_UCSRC = _BV(URSEL) | static_cast<uint8_t>(dataFormat);
#else
    // UMSEL1 UMSEL0 UPM1   UPM0   USBS   UCSZ1  UCSZ0  UCPOl
    // 0      0      0      0      0      1      1      0
    *_UCSRC = static_cast<uint8_t>(dataFormat);
#endif

    // Configure interrupts
    *_UCSRB |=  _BV(RXCIE0); // 'Rx Complete'
    *_UCSRB &= ~_BV(UDRIE0); // 'Data Register Empty'

    // Enable the UART
    *_UCSRB |= _BV(RXEN0);
    *_UCSRB |= _BV(TXEN0);

    // Reset flag
    _txAtLeastOnce = false;
}


void UART::end()
{
    // Ensure all outgoing data are transmitted
    flush();

    // Disable the UART
    *_UCSRB &= ~_BV(RXEN0);
    *_UCSRB &= ~_BV(TXEN0);

    // Disable interrupts
    *_UCSRB &= ~_BV(RXCIE0); // 'Rx Complete'
    *_UCSRB &= ~_BV(UDRIE0); // 'Data Register Empty'

    // Clear the Rx and Tx buffers
    _rxBuff.clear();
    _txBuff.clear();
}


bool UART::changeBaudrate(uint32_t baudrate)
{
    // Get the CPU frequency
    constexpr uint32_t cpuFreq = Application::getCPUFrequency();

    // Try using 2X mode first
    uint16_t baudSetting = ( cpuFreq + 4UL * (baudrate) ) / ( 8UL * (baudrate) ) - 1UL;

    // If the result is larger than 4095, fallback to 1X mode (because UBBR is a 12-bit register)
    const bool use2XMode = (baudSetting <= 4095);

    if(!use2XMode) baudSetting = ( cpuFreq + 8UL * (baudrate) ) / ( 16UL * (baudrate) ) - 1UL;

    // Set the baudrate
    if(use2XMode) *_UCSRA |= _BV(U2X0);

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        *_UBRRH = baudSetting >> 8;
        *_UBRRL = baudSetting;
    }

    // Done
    return true;
}


bool UART::canRead() const
{
    bool r;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        r = !_rxBuff.empty();
    }

    return r;
}


bool UART::read(uint8_t& value)
{
    bool r;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        r = _rxBuff.get(value);
    }

    return r;
}


bool UART::peek(uint8_t& value) const
{
    bool r;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        r = _rxBuff.peek(value);
    }

    return r;
}


bool UART::canWrite() const
{
    bool r;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        r = !_txBuff.full();
    }

    return r;
}


bool UART::write(uint8_t value)
{
    // Set flag
    _txAtLeastOnce = true;

    // If the Tx buffer and data register is empty, simply send the byte directly
    if( _txBuff.empty() && ( *_UCSRA & _BV(UDRE0) ) ) {
        ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
        {
            // Send the data
            *_UDR = value;
            // Clear the TXC bit to ensure flush() will not return until the byte(s) are really sent
            // (eCxx does not support 'Multi-processor Communication Mode', hence MPCM0 is never set)
            *_UCSRA |= _BV(TXC0);
            // Disable the 'Data Register Empty' interrupt
            *_UCSRB |= _BV(UDRIE0);
        }
        return true;
    }

    // If the output buffer is full, wait for the interrupt handler to empty it
    while(_txBuff.full()) {
        // If interrupt is disabled, send the data manually
        if( !( SREG & _BV(SREG_I) ) || !( *_UCSRB & _BV(UDRIE0) ) ) {
            // Ensure the data register is empty before sending
            if( *_UCSRA & _BV(UDRE0) ) udrEmpty_isr();
        }
    }

    // Store the data and enable the 'Data Register Empty' interrupt
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        _txBuff.put(value);
        *_UCSRB |= _BV(UDRIE0);
    }

    // Done
    return true;
}


void UART::flush()
{
    // No need to flush if no bytes have been written
    if(!_txAtLeastOnce) return;

    // Disable the 'Data Register Empty' interrupt
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        *_UCSRB &= ~_BV(UDRIE0);
    }

    // Call the interrupt handler to manually send the data
    while( !_txBuff.empty() ) {
        Application::yield();
        if( *_UCSRA & _BV(UDRE0) ) udrEmpty_isr(); // Ensure the data register is empty before sending
    }

    // Wait until the last byte is fully sent
    Application::yield();
    while( !( *_UCSRA & _BV(TXC0) ) );
}


} // namespace eCxx
