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


#include <eCxx/Platform/OneWireSoftUART.h>


namespace eCxx {


//
// A simple one-wire software UART implementation - inline helper functions
//
inline bool OneWireSoftUART::_calcParity(uint8_t value)
{
    uint8_t p = value;
            p = p ^ (p >> 4);
            p = p ^ (p >> 2);
            p = p ^ (p >> 1);

    if(_parity == ParityEven) p = p ^ 0x01;

    return p & 0x01;
}


inline bool OneWireSoftUART::_recvOneDataFrame(uint8_t& value)
{
    // Ensure no interrupt
    AtomicGuard g;

    // Parity bit
    bool parity = false;

    // Center the sample
    Application::__delay_loop4(_rxCtrDelay);

    // Read the data bits
    Application::__delay_loop4(_rxBitDelay);
    if( _pinFDX.read() ) value |= 0b00000001;
    else                 value &= 0b11111110;

    Application::__delay_loop4(_rxBitDelay);
    if( _pinFDX.read() ) value |= 0b00000010;
    else                 value &= 0b11111101;

    Application::__delay_loop4(_rxBitDelay);
    if( _pinFDX.read() ) value |= 0b00000100;
    else                 value &= 0b11111011;

    Application::__delay_loop4(_rxBitDelay);
    if( _pinFDX.read() ) value |= 0b00001000;
    else                 value &= 0b11110111;

    Application::__delay_loop4(_rxBitDelay);
    if( _pinFDX.read() ) value |= 0b00010000;
    else                 value &= 0b11101111;

    Application::__delay_loop4(_rxBitDelay);
    if( _pinFDX.read() ) value |= 0b00100000;
    else                 value &= 0b11011111;

    Application::__delay_loop4(_rxBitDelay);
    if( _pinFDX.read() ) value |= 0b01000000;
    else                 value &= 0b10111111;

    Application::__delay_loop4(_rxBitDelay);
    if( _pinFDX.read() ) value |= 0b10000000;
    else                 value &= 0b01111111;

    // Read the parity bit as needed
    if(_parity != ParityNone) {
        Application::__delay_loop4(_rxBitDelay + 1);
        parity = _pinFDX.read();
    }

#if 0
    // Read the stop bit(s)
    Application::__delay_loop4(_rxBitDelay + 1);
    _pinFDX.read();

    if(_twoStopBits) {
        Application::__delay_loop4(_rxBitDelay + 1);
        _pinFDX.read();
    }
#endif

    // Done
    return parity;
}


inline void OneWireSoftUART::_sendOneDataFrame(uint8_t value, bool parity)
{
    // Ensure no interrupt
    AtomicGuard g;

    // Send the start bit
    _pinFDX.write(false);
    Application::__delay_loop4(_txBitDelay);

    // Send the data bits
    _pinFDX.write(value & 0b00000001);
    Application::__delay_loop4(_txBitDelay);

    _pinFDX.write(value & 0b00000010);
    Application::__delay_loop4(_txBitDelay);

    _pinFDX.write(value & 0b00000100);
    Application::__delay_loop4(_txBitDelay);

    _pinFDX.write(value & 0b00001000);
    Application::__delay_loop4(_txBitDelay);

    _pinFDX.write(value & 0b00010000);
    Application::__delay_loop4(_txBitDelay);

    _pinFDX.write(value & 0b00100000);
    Application::__delay_loop4(_txBitDelay);

    _pinFDX.write(value & 0b01000000);
    Application::__delay_loop4(_txBitDelay);

    _pinFDX.write(value & 0b10000000);
    Application::__delay_loop4(_txBitDelay);

    // Send the parity bit as needed
    if(_parity != ParityNone) {
        _pinFDX.write(parity);
        Application::__delay_loop4(_txBitDelay);
    }

    // Send the stop bit(s)
    _pinFDX.write(true);
    Application::__delay_loop4(_txBitDelay);

    if(_twoStopBits) {
        _pinFDX.write(true);
        Application::__delay_loop4(_txBitDelay);
    }
}


//
// A simple one-wire software UART implementation
//
OneWireSoftUART::OneWireSoftUART()
: _parity(InvalidParity)
{}


OneWireSoftUART::~OneWireSoftUART()
{ end(); }


bool OneWireSoftUART::begin(uint8_t pin, uint32_t baudrate, DataFormat dataFormat)
{
    // Check if it is already initialized
    if(_parity != InvalidParity) return false;

    // Prepare the pin for fast-digital-read-and-write
    Application::_preparePinFDX(_pinFDX, pin);

    // Compute the bit delays
    Application::getSoftUARTBitDelayUS(_txBitDelay, _rxBitDelay, _rxCtrDelay, baudrate);

    // Set data format
    switch(dataFormat) {
        case DataFormat::_8N1 : _parity = ParityNone; _twoStopBits = false; break;
        case DataFormat::_8N2 : _parity = ParityNone; _twoStopBits = true;  break;
        case DataFormat::_8E1 : _parity = ParityEven; _twoStopBits = false; break;
        case DataFormat::_8E2 : _parity = ParityEven; _twoStopBits = true;  break;
        case DataFormat::_8O1 : _parity = ParityOdd;  _twoStopBits = false; break;
        case DataFormat::_8O2 : _parity = ParityOdd;  _twoStopBits = true;  break;
        default  /* _8N1 */   : _parity = ParityNone; _twoStopBits = false; break;
    }

    // Done
    return true;
}


void OneWireSoftUART::end()
{
    // Check if it is not yet initialized
    if(_parity == InvalidParity) return;

    // Ensure the pin is in input mode
    _pinFDX.setReadMode();

    // Done
    _parity = InvalidParity;
}


bool OneWireSoftUART::immRead(uint8_t& value, uint32_t timeOutMS)
{
    // Check if it is not yet initialized
    if(_parity == InvalidParity) return false;

    // Set the pin to read mode
    _pinFDX.setReadMode();

    // Wait for the start bit
    const uint32_t startTime = timeOutMS ? Application::millis() : 0;

    for(;;) {
        // Break if we got a start bit
        if( !_pinFDX.read() ) break;
        // Check for timeout;
        if( timeOutMS && Application::millis() - startTime > timeOutMS ) return false;
        // Yield
        Application::yield();
    };

    // Receive the data frame
    const bool parity = _recvOneDataFrame(value);

    // Calculate and check the parity as needed
    if(_parity != ParityNone) {
        // Check for parity error
        if( _calcParity(value) != parity ) return false;
    }

    // Done
    return true;
}


bool OneWireSoftUART::immRead(uint8_t* value_, uint8_t count, uint32_t timeOutMS)
{
    // Check if it is not yet initialized
    if(_parity == InvalidParity) return false;

    // Set the pin to read mode
    _pinFDX.setReadMode();

    // Receive the data
    for(uint8_t i = 0; i < count; ++i) {
        // Get a reference to the current value
        uint8_t& value = *value_++;
        // Wait for the start bit
        const uint32_t startTime = timeOutMS ? Application::millis() : 0;
        for(;;) {
            // Break if we got a start bit
            if( !_pinFDX.read() ) break;
            // Check for timeout;
            if( timeOutMS && Application::millis() - startTime > timeOutMS ) return false;
            // Yield
            Application::yield();
        };
        // Receive the data frame
        const bool parity = _recvOneDataFrame(value);
        // Calculate and check the parity as needed
        if(_parity != ParityNone) {
            // Check for parity error
            if( _calcParity(value) != parity ) return false;
        }
    }

    // Done
    return true;
}


void OneWireSoftUART::immWrite(uint8_t value)
{
    // Check if it is not yet initialized
    if(_parity == InvalidParity) return;

    // Calculate the parity as needed
    const bool parity = (_parity != ParityNone) ? _calcParity(value) : false;

    // Set the pin to write mode
    _pinFDX.setWriteMode(true);

    // Send the data frame
    _sendOneDataFrame(value, parity);

    // Set the pin to read mode
    _pinFDX.setReadMode();
}


void OneWireSoftUART::immWrite(const uint8_t* value_, uint8_t count)
{
    // Check if it is not yet initialized
    if(_parity == InvalidParity) return;

    // Set the pin to write mode
    _pinFDX.setWriteMode(true);

    // Send the data
    for(uint8_t i = 0; i < count; ++i) {
        // Get the current value
        const uint8_t value = *value_++;
        // Calculate the parity as needed
        const bool parity = (_parity != ParityNone) ? _calcParity(value) : false;
        // Send the data frame
        _sendOneDataFrame(value, parity);
    }

    // Set the pin to read mode
    _pinFDX.setReadMode();
}


} // namespace eCxx
