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


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


//
// A simple NodeMCU SPI wrapper
//
uint8_t SPI::_initCtr       = 0;
bool    SPI::_inTransaction = false;


void SPI::spiOnStatusRxCallback(void* arg, uint32_t status)
{
    // For convenience
    SPI* spi = reinterpret_cast<SPI*>(arg);

    // Skip if there is pending byte(s) that has not been read
    if(spi->_buffFlag) return;

    // Store the bytes
    spi->_buff.q   = status;
    spi->_buffFlag = HSPI_STATUS_BUFFER_SIZE;
}


void SPI::spiOnDataRxCallback(void* arg, uint8_t (&data)[HSPI_DATA_BUFFER_SIZE])
{
    // For convenience
    SPI* spi = reinterpret_cast<SPI*>(arg);

    // Skip if there is pending byte(s) that has not been read
    if(spi->_buffFlag) return;

    // Store the bytes
    for(uint32_t i = 0; i < HSPI_DATA_BUFFER_SIZE; ++i) spi->_buff.b[i] = data[i];
    spi->_buffFlag = HSPI_DATA_BUFFER_SIZE;
}


void SPI::begin(SPIMode mode)
{
    // Check if it is already initialized
    if(_initCtr) {
        ++_initCtr;
        return;
    }
    _initCtr = 1;

    // Copy the mode
    _spiMode = mode;

    // Master mode
    if(_spiMode == SPIMode::Master) {
        // Deselect the slave
        Application::setPinMode(NodeMCU::SPI_SS, Application::PinMode::Output);
        Application::digitalWrite(NodeMCU::SPI_SS, true);
        // Enable SPI in master mode
        NodeMCU::hspiMaster_begin();
    }

    // Slave mode
    else {
        _buffFlag = 0;
        NodeMCU::hspiSlave_begin(HSPI_STATUS_BUFFER_SIZE, this, &SPI::spiOnStatusRxCallback, &SPI::spiOnDataRxCallback);
    }
}


void SPI::end()
{
    // Check if it has never been initialized
    if(!_initCtr) return;

    // Do not proceed if the counter has not reached zero
    --_initCtr;
    if(_initCtr) return;

    // End transaction as needed
    if(_inTransaction) endTransaction();

    // Master mode
    if(_spiMode == SPIMode::Master) {
        NodeMCU::hspiMaster_end();
        Application::setPinMode(NodeMCU::SPI_SS, Application::PinMode::Input);
    }

    // Slave mode
    else {
        NodeMCU::hspiSlave_end();
    }
}


void SPI::beginTransaction(uint32_t frequency, BitOrder bitOrder, DataMode dataMode)
{
    // Do not proceed if it is not in master mode
    if(_spiMode != SPIMode::Master) return;

    // Check if it is actually already inside a transaction
    if(_inTransaction) return;
    _inTransaction = true;

    // Select the slave
    Application::digitalWrite(NodeMCU::SPI_SS, false);

    // Begin transaction
    NodeMCU::hspiMaster_setConfig(
        frequency,
        (bitOrder == BitOrder::MSBFirst) ? MSBFIRST : LSBFIRST,
        static_cast<uint8_t>(dataMode)
    );
}


void SPI::endTransaction()
{
    // Do not proceed if it is not in master mode
    if(_spiMode != SPIMode::Master) return;

    // Check if it actually is inside a transaction
    if(!_inTransaction) return;
    _inTransaction = false;

    // Deselect the slave
    Application::digitalWrite(NodeMCU::SPI_SS, true);
}


bool SPI::transfer(uint8_t& recv, uint8_t send) const
{
    // Do not proceed if it is not in master mode
    if(_spiMode != SPIMode::Master) return false;

    // Transfer one byte
    recv = NodeMCU::hspiMaster_transfer(send);

    // Done
    return true;
}


bool SPI::transfer(uint8_t* rxtx, uint8_t len) const
{
    // Do not proceed if it is not in master mode
    if(_spiMode != SPIMode::Master) return false;

    // Transfer bytes
    while(len) {
        *rxtx = NodeMCU::hspiMaster_transfer(*rxtx);
        ++rxtx;
        --len;
    }

    // Done
    return true;
}


bool SPI::readFromMaster(uint8_t (&recv)[HSPI_STATUS_BUFFER_SIZE])
{
    // Do not proceed if it is not in slave mode
    if(_spiMode != SPIMode::Slave) return false;

    // Receive bytes
    AtomicGuard g;

    if(!_buffFlag) return false; // Check if there are received bytes

    for(uint32_t i = 0; i < HSPI_STATUS_BUFFER_SIZE; ++i) recv[i] = _buff.b[i];

    _buffFlag = 0;

    return true;
}


bool SPI::writeToMaster(const uint8_t (&send)[HSPI_STATUS_BUFFER_SIZE])
{
    // Do not proceed if it is not in slave mode
    if(_spiMode != SPIMode::Slave) return false;

    // Send bytes
    AtomicGuard g;

    if(_buffFlag) return false; // Cannot send if not all the received byte(s) have been read

    for(uint32_t i = 0; i < HSPI_STATUS_BUFFER_SIZE; ++i) _buff.b[i] = send[i];

    NodeMCU::hspiSlave_sendStatus(_buff.q);

    return true;
}


bool SPI::readFromMaster(uint8_t (&recv)[HSPI_DATA_BUFFER_SIZE])
{
    // Do not proceed if it is not in slave mode
    if(_spiMode != SPIMode::Slave) return false;

    // Receive bytes
    AtomicGuard g;

    if(!_buffFlag) return false; // Check if there are received bytes

    for(uint32_t i = 0; i < HSPI_DATA_BUFFER_SIZE; ++i) recv[i] = _buff.b[i];

    _buffFlag = 0;

    return true;
}


bool SPI::writeToMaster(const uint8_t (&send)[HSPI_DATA_BUFFER_SIZE])
{
    // Do not proceed if it is not in slave mode
    if(_spiMode != SPIMode::Slave) return false;

    // Send bytes
    AtomicGuard g;

    if(_buffFlag) return false; // Cannot send if not all the received byte(s) have been read

    for(uint32_t i = 0; i < HSPI_DATA_BUFFER_SIZE; ++i) _buff.b[i] = send[i];

    NodeMCU::hspiSlave_sendData( const_cast<uint8_t(&)[HSPI_DATA_BUFFER_SIZE]>(_buff.b) );

    return true;
}


void SPI::ntmBegin(uint32_t frequency, BitOrder bitOrder, DataMode dataMode) const
{
    NodeMCU::hspiMaster_setConfig(
        frequency,
        (bitOrder == BitOrder::MSBFirst) ? MSBFIRST : LSBFIRST,
        static_cast<uint8_t>(dataMode)
    );
}


void SPI::ntmSelectSlave() const
{ Application::digitalWrite(NodeMCU::SPI_SS, false); }


void SPI::ntmDeselectSlave() const
{ Application::digitalWrite(NodeMCU::SPI_SS, true); }


uint8_t SPI::ntmRead(uint8_t dummy) const
{ return NodeMCU::hspiMaster_transfer(dummy); } // Send dummy data


uint8_t SPI::ntmReadX00() const
{ return NodeMCU::hspiMaster_transfer(0x00); } // Send dummy data


uint8_t SPI::ntmReadXFF() const
{ return NodeMCU::hspiMaster_transfer(0xFF); } // Send dummy data


void SPI::ntmWrite(uint8_t value) const
{ NodeMCU::hspiMaster_transfer(value); }


//
// A simple NodeMCU TWI-master wrapper
//
void TWIMaster::begin(uint32_t frequency)
{
    NodeMCU::stwi_begin();
    NodeMCU::stwi_setFrequency(frequency);
}


void TWIMaster::end()
{}


void TWIMaster::beginTransmission(uint8_t slaveAddress)
{ NodeMCU::stwi_beginTransmission(slaveAddress); }


bool TWIMaster::write(uint8_t value)
{ return NodeMCU::stwi_write(value) > 0; }


bool TWIMaster::write(const void* data, uint8_t len)
{ return NodeMCU::stwi_write(static_cast<const uint8_t*>(data), len) > 0; }


TWIMaster::TWIResultCode TWIMaster::endTransmission()
{ return NodeMCU::stwi_endTransmission(true); }


uint8_t TWIMaster::requestFrom(uint8_t slaveAddress, uint8_t len)
{ return NodeMCU::stwi_requestFrom(slaveAddress, len, true); }


bool TWIMaster::canRead() const
{ return NodeMCU::stwi_available(); }


bool TWIMaster::read(uint8_t& value)
{
    const int c = NodeMCU::stwi_read();
    if(c < 0) return false;

    value = c;
    return true;
}


bool TWIMaster::peek(uint8_t& value) const
{
    const int c = NodeMCU::stwi_peek();
    if(c < 0) return false;

    value = c;
    return true;
}


//
// A simple NodeMCU TWI-slave wrapper
//
static TWISlave::RxCallbackF __eCxx_TWISlave_onRx = nullptr;
static TWISlave::TxCallbackF __eCxx_TWISlave_onTx = nullptr;


static void __eCxx_TWISlave_onRx_internal(size_t len)
{ if(__eCxx_TWISlave_onRx) __eCxx_TWISlave_onRx(len); }


static void __eCxx_TWISlave_onTx_internal()
{ if(__eCxx_TWISlave_onTx) __eCxx_TWISlave_onTx(); }


void TWISlave::begin(uint8_t slaveAddress, RxCallbackF rxcb, TxCallbackF txcb)
{
    __eCxx_TWISlave_onRx = rxcb;
    __eCxx_TWISlave_onTx = txcb;

    NodeMCU::stwi_begin(slaveAddress, __eCxx_TWISlave_onRx_internal, __eCxx_TWISlave_onTx_internal);
}


void TWISlave::end()
{
    __eCxx_TWISlave_onRx = nullptr;
    __eCxx_TWISlave_onTx = nullptr;
}


bool TWISlave::canRead() const
{ return NodeMCU::stwi_available(); }


bool TWISlave::read(uint8_t& value)
{
    const int c = NodeMCU::stwi_read();
    if(c < 0) return false;

    value = c;
    return true;
}


bool TWISlave::peek(uint8_t& value) const
{
    const int c = NodeMCU::stwi_peek();
    if(c < 0) return false;

    value = c;
    return true;
}


TWIResultCode TWISlave::write(uint8_t value)
{
    if( NodeMCU::stwi_write(value) > 0 ) return TWIResultCode::NoError;

    return TWIResultCode::OtherError;
}


TWIResultCode TWISlave::write(const void* data, uint8_t len)
{
    if( NodeMCU::stwi_write(static_cast<const uint8_t*>(data), len) > 0 ) return TWIResultCode::NoError;

    return TWIResultCode::OtherError;
}


//
// A simple NodeMCU I2S output wrapper
//
void I2SOut::i2s_isr(void* arg)
{
    // Schedule the I2S user callback to be called by the next Application::processEvent()
    // to allow for non-RAM-residing code
    Application::_i2sCallback = reinterpret_cast<I2SOut*>(arg)->_callback;
}


I2SOut::I2SOut()
: _callback(nullptr)
{}


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


bool I2SOut::begin(uint32_t rateHz, TxCallbackF callback, uint16_t bufferLength32, bool enableBitClock, bool enableWordSelect)
{
    // End any previous initialization
    end();

    // Save the user callback
    _callback = callback;

    // Set I2S parameters
    NodeMCU::i2sSetBufferLength(bufferLength32);
    NodeMCU::i2sSetRate        (rateHz);

    // Initialize I2S
    return NodeMCU::i2sBegin(&I2SOut::i2s_isr, this, enableBitClock, enableWordSelect);
}


void I2SOut::end()
{ NodeMCU::i2sEnd(); }


bool I2SOut::resetFinishedBuffer(bool enabled, uint32_t value)
{ return NodeMCU::i2sSetResetFinishedBuffer(enabled, value); }


void I2SOut::pause()
{ NodeMCU::i2sPause(); }


void I2SOut::resume()
{ NodeMCU::i2sResume(); }


bool I2SOut::isFull()
{ return NodeMCU::i2sIsFull(); }


bool I2SOut::isEmpty()
{ return NodeMCU::i2sIsEmpty(); }


uint16_t I2SOut::available()
{ return NodeMCU::i2sAvailable(); }


bool I2SOut::writeSample(uint32_t sample, bool nonBlocking)
{ return NodeMCU::i2sWriteSample(sample, nonBlocking); }


bool I2SOut::writeSampleLR(int16_t left, int16_t right)
{ return NodeMCU::i2sWriteSampleLR(left, right); }


uint16_t I2SOut::writeBufferMono(int16_t* frames, uint16_t frameCount, bool nonBlocking)
{ return NodeMCU::i2sWriteBufferMono(frames, frameCount, nonBlocking); }


uint16_t I2SOut::writeBufferStereo(int16_t* frames, uint16_t frameCount, bool nonBlocking)
{ return NodeMCU::i2sWriteBufferStereo(frames, frameCount, nonBlocking); }
