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


#include <eCxx/Platform/StreamSampler.h>


namespace eCxx {


//
// A simple data-stream sampler
//
StreamSampler::StreamSampler()
: _pin     (Application::InvalidPin)
, _cSet    (nullptr)
, _rxUserCB(nullptr)
{}


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


bool StreamSampler::begin(uint8_t pin)
{
    // Do not proceed if it has already been initialized
    if(_pin != Application::InvalidPin) return false;

    // Set pin mode and attach interrupt
    Application::setPinMode(pin, Application::PinMode::InputPullUp);

    if( !Application::attachInterruptArg(pin, &StreamSampler::gpio_isr, this, Application::InterruptMode::Change) ) {
        Application::setPinMode(pin, Application::PinMode::Input);
        return false;
    }

    // Copy the pin number and prepare for fast-digital-read
    _pin = pin;
    Application::_preparePinFDR(_pinFDR, _pin);

    // Done
    return true;
}


bool StreamSampler::begin(uint8_t pin, const TimingSpecification& ts)
{
    // Copy the timing specification
    _tSpec = ts;

    // Initialize
    return begin(pin);
}


void StreamSampler::end()
{
    // Do not proceed if it has never been initialized before
    if(_pin == Application::InvalidPin) return;

    // Detach interrupt and set pin mode
    Application::detachInterrupt(_pin);
    Application::setPinMode(_pin, Application::PinMode::Input);

    // Done
    _pin      = Application::InvalidPin;
    _cSet     = nullptr;
    _rxUserCB = nullptr;
}


bool StreamSampler::calibrate(const CalibrationSetting& cs)
{
    // Do not proceed if it has never been initialized before
    if(_pin == Application::InvalidPin) return false;

    // Wait until the line is idle
    const uint32_t start = Application::millis();
    for(;;) {
        // Check if the line is already idle
        const bool bit = Application::digitalRead(_pin);
        if( cs._activeState == ActiveState::Low &&  bit ) break;
        if( cs._activeState != ActiveState::Low && !bit ) break;
        // Check for timeout
        if( Application::millis() - start < cs._initialTimeoutMS ) {
            Application::delayMS(1);
            continue;
        }
        return false;
    }

    // Ensure atomic operation when setting the data
    {
        AtomicGuard g;

        // Set internal data
        _cSet     = &cs;
        _rxUserCB = nullptr;

        _tSpec.reset();

        // Start calibration
        _cSet->resetAllState();
    }

    // Wait until the calibration process is done
    const uint32_t startMillis = Application::millis();
          bool     timeout     = false;

    while( !cs.done() ) {
        // Delay for a while
        Application::delayMS(5);
        // Check for timeout
        if(Application::millis() - startMillis < cs._totalTimeoutMS) continue;
        timeout = true;
        break;
    }

    // Ensure atomic operation when clearing the data
    {
        AtomicGuard g;
        _cSet = nullptr;
    }

    // Check for timeout
    if(timeout) return false;

    // Check for error
    if(!cs._totalStep) return false;

    // Calculate the timing specification from the just obtained calibration data
    cs._minLTime = cs._minLTimeTot / cs._totalStep;
    cs._minHTime = cs._minHTimeTot / cs._totalStep;
    cs._maxLTime = cs._maxLTimeTot / cs._totalStep;
    cs._maxHTime = cs._maxHTimeTot / cs._totalStep;

    cs._bitCount = cs._bitCountTot / cs._totalStep;

    const uint16_t minTimeTol = ( cs._minHTime > cs._minLTime )
                              ? ( static_cast<uint32_t>(cs._minHTime - cs._minLTime) * 100 / cs._minLTime )
                              : ( static_cast<uint32_t>(cs._minLTime - cs._minHTime) * 100 / cs._minHTime );

    if(minTimeTol <= cs._tolerance) {
        cs._minLTime = (cs._minLTime + cs._minHTime) / 2;
        cs._minHTime =  cs._minLTime;
    }

    const uint16_t maxTimeTol = ( cs._maxHTime > cs._maxLTime )
                              ? ( static_cast<uint32_t>(cs._maxHTime - cs._maxLTime) * 100 / cs._maxLTime )
                              : ( static_cast<uint32_t>(cs._maxLTime - cs._maxHTime) * 100 / cs._maxHTime );

    if(maxTimeTol <= cs._tolerance) {
        cs._maxLTime = (cs._maxLTime + cs._maxHTime) / 2;
        cs._maxHTime =  cs._maxLTime;
    }

    const uint16_t unitLength = eCxx::min(cs._minLTime, cs._minHTime);

    _tSpec.endBlockTimeoutMS = cs._endBlockTimeoutMS;
    _tSpec.noiseTimeUS       = cs._noiseTimeUS;

    _tSpec.minLTimeUS0       = cs._minLTime - static_cast<uint32_t>(cs._minLTime) * cs._tolerance / 100;
    _tSpec.minLTimeUS1       = cs._minLTime + static_cast<uint32_t>(cs._minLTime) * cs._tolerance / 100;
    _tSpec.minHTimeUS0       = cs._minHTime - static_cast<uint32_t>(cs._minHTime) * cs._tolerance / 100;
    _tSpec.minHTimeUS1       = cs._minHTime + static_cast<uint32_t>(cs._minHTime) * cs._tolerance / 100;

    _tSpec.maxLTimeUS0       = cs._maxLTime - static_cast<uint32_t>(cs._maxLTime) * cs._tolerance / 100;
    _tSpec.maxLTimeUS1       = cs._maxLTime + static_cast<uint32_t>(cs._maxLTime) * cs._tolerance / 100;
    _tSpec.maxHTimeUS0       = cs._maxHTime - static_cast<uint32_t>(cs._maxHTime) * cs._tolerance / 100;
    _tSpec.maxHTimeUS1       = cs._maxHTime + static_cast<uint32_t>(cs._maxHTime) * cs._tolerance / 100;

    _tSpec.minLTimeBits      = cs._minLTime / unitLength;
    _tSpec.minHTimeBits      = cs._minHTime / unitLength;
    _tSpec.maxLTimeBits      = cs._maxLTime / unitLength;
    _tSpec.maxHTimeBits      = cs._maxHTime / unitLength;

    _tSpec.maxBitCount       = eCxx::max(cs._maxLTime, cs._maxHTime) / unitLength;
    _tSpec.maxBitCount       = roundToTheNextMultipleOf<8>(_tSpec.maxBitCount * cs._bitCount);

    // Check for error
    if( !_tSpec.maxBitCount || _tSpec.maxBitCount > Application::BSRBufferSize * 8 ) {
        _tSpec.reset();
        return false;
    }

    // Done
    return true;
}


bool StreamSampler::receive(RxCallbackF rxcb, ActiveState activeState)
{
    // Do not proceed if the receiver state and/or the given callback is not valid
    if( !valid() || !rxcb ) return false;

    // Ensure atomic operation when setting the data
    {
        AtomicGuard g;

        // Set internal data
        _rxUserCB                  = rxcb;
        _rxActiveState             = activeState;
        _rxCurBuffIdx              = 0;
        _rxBitCount[_rxCurBuffIdx] = 0;
        _rxStart                   = true;
    }

    // Done
    return true;
}


//
// Interrupt handler implementation functions
//
void StreamSampler::callUserCallback_impl(void* arg)
{
    // NOTE : This function should be automatically called in atomic mode by the system

    // Get the instance
    StreamSampler* rbs = static_cast<StreamSampler*>(arg);

    // Determine the previous Rx buffer index
    const uint8_t rxPrvBuffIdx = rbs->_rxCurBuffIdx ? 0 : 1;

    // Make sure the Rx bit count is in the multiple of 8
    const uint8_t rxBitCount = roundToTheNextMultipleOf<8>(rbs->_rxBitCount[rxPrvBuffIdx]);

    // Invert the data as needed
    if(rbs->_rxActiveState == ActiveState::Low) {
        for(uint8_t i = 0; i < rxBitCount / 8; ++i) {
            rbs->_rxBuff[rxPrvBuffIdx][i] = ~rbs->_rxBuff[rxPrvBuffIdx][i];
        }
    }

    // Call the user callback
    rbs->_rxUserCB(rbs->_rxBuff[rxPrvBuffIdx], rxBitCount);
}


void StreamSampler::calibrate_impl(bool bit, uint32_t micros)
{
    // Start
    if(_cSet->_state == CalibrationSetting::CState::Start) {
        _cSet->_state      = CalibrationSetting::CState::InProgress;
        _cSet->_lastMicros = micros;
        _cSet->_lastBit    = bit;
    }

    // In progress
    else if(_cSet->_state == CalibrationSetting::CState::InProgress) {
        // Calculate the delta microseconds
        const uint32_t deltaMicros = micros - _cSet->_lastMicros;
        // Check for possible bit transition due to noise
        if(deltaMicros < _cSet->_noiseTimeUS) return;
        // Check for possible end of block
        if(deltaMicros / 1000 > _cSet->_endBlockTimeoutMS) {
            // Increment the number of steps this calibration process has been executed
            ++_cSet->_totalStep;
            // Accumulate the time
            _cSet->_minLTimeTot += _cSet->_minLTime;
            _cSet->_minHTimeTot += _cSet->_minHTime;
            _cSet->_maxLTimeTot += _cSet->_maxLTime;
            _cSet->_maxHTimeTot += _cSet->_maxHTime;
            _cSet->_bitCountTot += _cSet->_bitCount;
            // Restart the state machine as needed or end the calibration process
            if(_cSet->_totalStep >= _cSet->_calibrateCount) {
                _cSet->_state = CalibrationSetting::CState::Done;
            }
            else {
                _cSet->resetRunningState();
            }
            return;
        }
        // If the bits are the same, skip for now (it is probably missed noise)
        if(_cSet->_lastBit == bit) return;
        // Last bit is high
        if(_cSet->_lastBit) {
            if(deltaMicros < _cSet->_minHTime) _cSet->_minHTime = deltaMicros;
            if(deltaMicros > _cSet->_maxHTime) _cSet->_maxHTime = deltaMicros;
        }
        // Last bit is low
        else {
            if(deltaMicros < _cSet->_minLTime) _cSet->_minLTime = deltaMicros;
            if(deltaMicros > _cSet->_maxLTime) _cSet->_maxLTime = deltaMicros;
        }
        // Increment the bit count
        ++_cSet->_bitCount;
        // Update the running time and bit state
        _cSet->_lastMicros = micros;
        _cSet->_lastBit    = bit;
    }
}


void StreamSampler::receive_impl(bool bit, uint32_t micros)
{
    // Start
    if(_rxStart) {
        _rxPosIdx                  = 0;
        _rxBitMask                 = 0b10000000;
        _rxBitCount[_rxCurBuffIdx] = 0;
        _rxLastMicros              = micros;
        _rxLastBit                 = bit;
        _rxStart                   = false;
    }

    // In progress
    else {
        // Calculate the delta microseconds
        const uint32_t deltaMicros = micros - _rxLastMicros;
        // Check for possible bit transition due to noise
        if(deltaMicros < _tSpec.noiseTimeUS) return;
        // Check for possible end of block
        if(
            deltaMicros / 1000         >  _tSpec.endBlockTimeoutMS       ||
            _rxBitCount[_rxCurBuffIdx] >= Application::BSRBufferSize * 8
        ) {
            // Proceed only if the received bits seem to be valid
            if(_rxBitCount[_rxCurBuffIdx] && _rxBitCount[_rxCurBuffIdx] <= _tSpec.maxBitCount) {
                // Ensure the remaining bits (if any) are set to idle state
                if(_rxBitMask & 0b01111111) {
                    while(_rxBitMask) {
                        if(_rxActiveState == ActiveState::Low) _rxBuff[_rxCurBuffIdx][_rxPosIdx] |=  _rxBitMask;
                        else                                   _rxBuff[_rxCurBuffIdx][_rxPosIdx] &= ~_rxBitMask;
                        _rxBitMask >>= 1;
                    }
                }
                // Schedule the user callback to be called by the next Application::processEvent()
                Application::scheduleAtomicUserCallback(&StreamSampler::callUserCallback_impl, this);
            }
            // Restart the state machine
            _rxCurBuffIdx = _rxCurBuffIdx ? 0 : 1;
            _rxStart      = true;
            return;
        }
        // If the bits are the same, skip for now (it is probably missed noise)
        if(_rxLastBit == bit) return;
        // Last bit is high
        if(_rxLastBit) {
            // Check if between the range of minimum high state
            if( deltaMicros > _tSpec.minHTimeUS0 && deltaMicros < _tSpec.minHTimeUS1 ) {
                for(uint8_t i = 0; i < _tSpec.minHTimeBits; ++i) {
                    _rxBuff[_rxCurBuffIdx][_rxPosIdx] |= _rxBitMask;
                    _rxBitMask >>= 1;
                    if(!_rxBitMask) {
                        ++_rxPosIdx;
                        _rxBitMask = 0b10000000;
                    }
                }
                _rxBitCount[_rxCurBuffIdx] += _tSpec.minHTimeBits;
            }
            // Check if between the range of maximum high state
            else if( deltaMicros > _tSpec.maxHTimeUS0 && deltaMicros < _tSpec.maxHTimeUS1 ) {
                for(uint8_t i = 0; i < _tSpec.maxHTimeBits; ++i) {
                    _rxBuff[_rxCurBuffIdx][_rxPosIdx] |= _rxBitMask;
                    _rxBitMask >>= 1;
                    if(!_rxBitMask) {
                        ++_rxPosIdx;
                        _rxBitMask = 0b10000000;
                    }
                }
                _rxBitCount[_rxCurBuffIdx] += _tSpec.maxHTimeBits;
            }
            // Range error (it is probably missed noise)
            else {
                return;
            }
        }
        // Last bit is low
        else {
            // Check if between the range of minimum low state
            if( deltaMicros > _tSpec.minLTimeUS0 && deltaMicros < _tSpec.minLTimeUS1 ) {
                for(uint8_t i = 0; i < _tSpec.minLTimeBits; ++i) {
                    _rxBuff[_rxCurBuffIdx][_rxPosIdx] &= ~_rxBitMask;
                    _rxBitMask >>= 1;
                    if(!_rxBitMask) {
                        ++_rxPosIdx;
                        _rxBitMask = 0b10000000;
                    }
                }
                _rxBitCount[_rxCurBuffIdx] += _tSpec.minLTimeBits;
            }
            // Check if between the range of maximum low state
            else if( deltaMicros > _tSpec.maxLTimeUS0 && deltaMicros < _tSpec.maxLTimeUS1 ) {
                for(uint8_t i = 0; i < _tSpec.maxLTimeBits; ++i) {
                    _rxBuff[_rxCurBuffIdx][_rxPosIdx] &= ~_rxBitMask;
                    _rxBitMask >>= 1;
                    if(!_rxBitMask) {
                        ++_rxPosIdx;
                        _rxBitMask = 0b10000000;
                    }
                }
                _rxBitCount[_rxCurBuffIdx] += _tSpec.maxLTimeBits;
            }
            // Range error (it is probably missed noise)
            else {
                return;
            }
        } // if _rxLastBit
        // Update the running time and bit state
        _rxLastMicros = micros;
        _rxLastBit    = bit;
    }
}


//
// Interrupt handler
//
void StreamSampler::gpio_isr(void* arg)
{
    // Get the instance
    StreamSampler* rbs = static_cast<StreamSampler*>(arg);

    // Read the current microseconds
    const uint8_t  bit    = rbs->_pinFDR();
    const uint32_t micros = Application::micros();

    // Perform calibration/receive operation
         if(rbs->_cSet     != nullptr) rbs->calibrate_impl(bit, micros);
    else if(rbs->_rxUserCB != nullptr) rbs->receive_impl  (bit, micros);
}


} // namespace eCxx
