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


#ifndef INCLUDE_ECXX_APPLICATION_CRTP_BASE_TPP
#error "this file cannot be included directly"
#endif
#undef INCLUDE_ECXX_APPLICATION_CRTP_BASE_TPP


#ifndef ECXX_PLATFORM_COMMON_APPLICATION_CRTP_BASE_TPP
#define ECXX_PLATFORM_COMMON_APPLICATION_CRTP_BASE_TPP


//
// Helper delay macros
#ifdef __XTENSA__
    #define ECXX_SHIFT_DELAY_S() ImplementationT::delayUS(1)
    #define ECXX_SHIFT_DELAY_L() ImplementationT::delayUS(5)
#else
    #define ECXX_SHIFT_DELAY_S() do {} while(0)
    #define ECXX_SHIFT_DELAY_L() do {} while(0)
#endif


//
//
// An application CRTP base class that contains common member types/constants/variables/functions/etc.
//
template <typename ImplementationT>
inline void Application_CRTPBase<ImplementationT>::shiftOut(uint8_t pinData, uint8_t pinClock, ActiveClockEdge clockEdge, BitOrder bitOrder, uint8_t value)
{
    // Determine the logical low (not active) and logical high (active) values for the clock pin
    const bool logicL = (clockEdge == ActiveClockEdge::Falling) ? true  : false;
    const bool logicH = (clockEdge == ActiveClockEdge::Falling) ? false : true;

    // Ensure the clock is not active
    ImplementationT::digitalWrite(pinClock, logicL);
    ECXX_SHIFT_DELAY_S();

    // Walk through the bits to write them
    for(uint8_t i = 0; i < 8; i++) {
        // Send the bit
        if(bitOrder == BitOrder::LSBFirst) {
            ImplementationT::digitalWrite(pinData, value & 0x01);
            value >>= 1;
        }
        else {
            ImplementationT::digitalWrite(pinData, value & 0x80);
            value <<= 1;
        }
        // Clock rise
        ImplementationT::digitalWrite(pinClock, logicH);
        ECXX_SHIFT_DELAY_S();
        // Clock fall
        ImplementationT::digitalWrite(pinClock, logicL);
        ECXX_SHIFT_DELAY_S();
    }
}


template <typename ImplementationT>
inline void Application_CRTPBase<ImplementationT>::shiftOut(uint8_t pinData, uint8_t pinClock, ActiveClockEdge clockEdge, uint8_t pinLatch, ActiveState pinLatchState, BitOrder bitOrder, const uint8_t* value, uint8_t count)
{
    // Determine the logical low (not active) and logical high (active) values for the latch pin
    const bool logicL = (pinLatchState == ActiveState::Low) ? true  : false;
    const bool logicH = (pinLatchState == ActiveState::Low) ? false : true;

    // Ensure the latch pin is not active
    ImplementationT::digitalWrite(pinLatch, logicL);
    ECXX_SHIFT_DELAY_S();

    // Send the bytes
    for(uint8_t i = 0; i < count; ++i) {
        shiftOut(pinData, pinClock, clockEdge, bitOrder, *value++);
    }

    // Pulse the latch pin
    ImplementationT::digitalWrite(pinLatch, logicH);
    ECXX_SHIFT_DELAY_L();
    ImplementationT::digitalWrite(pinLatch, logicL);
    ECXX_SHIFT_DELAY_L();
}


template <typename ImplementationT> template <uint8_t N>
inline void Application_CRTPBase<ImplementationT>::shiftOut(uint8_t pinData, uint8_t pinClock, ActiveClockEdge clockEdge, uint8_t pinLatch, ActiveState pinLatchState, BitOrder bitOrder, const uint8_t (&value)[N])
{ shiftOut(pinData, pinClock, clockEdge, pinLatch, pinLatchState, bitOrder, &value[0], N); }


template <typename ImplementationT>
inline uint8_t Application_CRTPBase<ImplementationT>::shiftIn(uint8_t pinData, uint8_t pinClock, ActiveClockEdge clockEdge, BitOrder bitOrder)
{
    // Determine the logical low (not active) and logical high (active) values for the clock pin
    const bool logicL = (clockEdge == ActiveClockEdge::Falling) ? true  : false;
    const bool logicH = (clockEdge == ActiveClockEdge::Falling) ? false : true;

    // Ensure the clock is not active
    ImplementationT::digitalWrite(pinClock, logicL);
    ECXX_SHIFT_DELAY_S();

    // Walk through the bits to read them
    uint8_t value = 0;

    for(uint8_t i = 0; i < 8; i++) {
        // Clock rise
        ImplementationT::digitalWrite(pinClock, logicH);
        ECXX_SHIFT_DELAY_S();
        // Receive the bit
        if(bitOrder == BitOrder::LSBFirst) {
            value |= static_cast<uint8_t>( ImplementationT::digitalRead(pinData) ) << i;
        }
        else {
            value |= static_cast<uint8_t>( ImplementationT::digitalRead(pinData) ) << (7 - i);
        }
        // Clock fall
        ImplementationT::digitalWrite(pinClock, logicL);
        ECXX_SHIFT_DELAY_S();
    }

    // Return the value
    return value;
}


template <typename ImplementationT>
inline void Application_CRTPBase<ImplementationT>::shiftIn(uint8_t pinData, uint8_t pinClock, ActiveClockEdge clockEdge, uint8_t pinLoad, ActiveState pinLoadState, BitOrder bitOrder, uint8_t* value, uint8_t count)
{
    // Determine the logical low (not active) and logical high (active) values for the laod pin
    const bool logicL = (pinLoadState == ActiveState::Low) ? true  : false;
    const bool logicH = (pinLoadState == ActiveState::Low) ? false : true;

    // Ensure the load pin is not active
    ImplementationT::digitalWrite(pinLoad, logicL);
    ECXX_SHIFT_DELAY_S();

    // Pulse the load pin
    ImplementationT::digitalWrite(pinLoad, logicH);
    ECXX_SHIFT_DELAY_L();
    ImplementationT::digitalWrite(pinLoad, logicL);
    ECXX_SHIFT_DELAY_L();

    // Receive the bytes
    for(uint8_t i = 0; i < count; ++i) {
        *value++ = shiftIn(pinData, pinClock, clockEdge, bitOrder);
    }
}


template <typename ImplementationT> template <uint8_t N>
inline void Application_CRTPBase<ImplementationT>::shiftIn(uint8_t pinData, uint8_t pinClock, ActiveClockEdge clockEdge, uint8_t pinLoad, ActiveState pinLoadState, BitOrder bitOrder, uint8_t (&value)[N])
{ shiftIn(pinData, pinClock, clockEdge, pinLoad, pinLoadState, bitOrder, &value[0], N); }


template <typename ImplementationT> template <typename ValueT>
inline void Application_CRTPBase<ImplementationT>::shiftOutIn(
    ValueT          valueOut,
    ValueT&         valueIn,
    uint8_t         pinDataOut,
    uint8_t         pinDataIn,
    uint8_t         pinClock,
    uint8_t         pinLatchLoad,
    ActiveState     pinLatchLoadState,
    ActiveClockEdge shitOutClockEdge,
    ActiveClockEdge shitInClockEdge,
    BitOrder        bitOrder
)
{
    static_assert(eCxx::isIntegral<ValueT>::Value, "template argument substituting ValueT is not an integral");
    static_assert(eCxx::isUnsigned<ValueT>::Value, "template argument substituting ValueT is not an unsigned integral");

    // Determine the logical low (not active) and logical high (active) values for the shift-out clock pin
    const bool socLogicL = (shitOutClockEdge == ActiveClockEdge::Falling) ? true  : false;
    const bool socLogicH = (shitOutClockEdge == ActiveClockEdge::Falling) ? false : true;

    // Determine the logical low (not active) and logical high (active) values for the shift-in clock pin
    const bool sicLogicL = (shitInClockEdge == ActiveClockEdge::Falling) ? true  : false;
    const bool sicLogicH = (shitInClockEdge == ActiveClockEdge::Falling) ? false : true;

    // Determine the logical low (not active) and logical high (active) values for the latch/load pin
    const bool llLogicL = (pinLatchLoadState == ActiveState::Low) ? true  : false;
    const bool llLogicH = (pinLatchLoadState == ActiveState::Low) ? false : true;

    // Ensure the latch and load pins are not active
    ImplementationT::digitalWrite(pinLatchLoad, llLogicL);
    ECXX_SHIFT_DELAY_S();

    // Ensure the clock is not active
    ImplementationT::digitalWrite(pinClock, socLogicL);
    ECXX_SHIFT_DELAY_S();

    // Bit count and masks
    constexpr uint8_t bitCount = eCxx::bitSize<ValueT>();
    constexpr ValueT  maskLSB  = static_cast<ValueT>(0x01);
    constexpr ValueT  maskMSB  = static_cast<ValueT>(0x01) << (bitCount - 1);

    // Walk through the bits to write them
    for(uint8_t i = 0; i < bitCount; i++) {
        // Send the bit
        if(bitOrder == BitOrder::LSBFirst) {
            ImplementationT::digitalWrite(pinDataOut, valueOut & maskLSB);
            valueOut >>= 1;
        }
        else {
            ImplementationT::digitalWrite(pinDataOut, valueOut & maskMSB);
            valueOut <<= 1;
        }
        // Clock rise
        ImplementationT::digitalWrite(pinClock, socLogicH);
        ECXX_SHIFT_DELAY_S();
        // Clock fall
        if( (i == bitCount -1) && (socLogicL != sicLogicL) ) break; // Skip the last clock-fall as needed
        ImplementationT::digitalWrite(pinClock, socLogicL);
        ECXX_SHIFT_DELAY_S();
    }

    // Pulse the latch and load pins
    ImplementationT::digitalWrite(pinLatchLoad, llLogicH);
    ECXX_SHIFT_DELAY_L();
    ImplementationT::digitalWrite(pinLatchLoad, llLogicL);
    ECXX_SHIFT_DELAY_L();

    // Ensure the clock is not active
    ImplementationT::digitalWrite(pinClock, sicLogicL);
    ECXX_SHIFT_DELAY_S();

    // Walk through the bits to read them
    valueIn = 0;

    for(uint8_t i = 0; i < bitCount; i++) {
        // Clock rise
        ImplementationT::digitalWrite(pinClock, sicLogicH);
        ECXX_SHIFT_DELAY_S();
        // Receive the bit
        if(bitOrder == BitOrder::LSBFirst) {
            valueIn |= static_cast<ValueT>( ImplementationT::digitalRead(pinDataIn) ) << i;
        }
        else {
            valueIn |= static_cast<ValueT>( ImplementationT::digitalRead(pinDataIn) ) << (bitCount - 1 - i);
        }
        // Clock fall
        ImplementationT::digitalWrite(pinClock, sicLogicL);
        ECXX_SHIFT_DELAY_S();
    }
}


template <typename ImplementationT>
inline uint32_t Application_CRTPBase<ImplementationT>::measurePulse(uint8_t pin, ActiveState pulseAS, uint32_t timeoutUS)
{
    // Prepare for fast-digital-read
    typename ImplementationT::_PinFDR pinFDR;

    ImplementationT::_preparePinFDR(pinFDR, pin);

    // Determine the idle state (value)
    const bool idleState = (pulseAS == ActiveState::Low) ? true : false;

    // Save the start micros()
    uint32_t start = ImplementationT::micros();

    // Wait until the line is idle
    while(pinFDR() != idleState) {
        if(ImplementationT::micros() - start > timeoutUS) return 0;
        ImplementationT::yield();
    }

    // Wait until the line is no longer idle
    while(pinFDR() == idleState) {
        if(ImplementationT::micros() - start > timeoutUS) return 0;
        ImplementationT::yield();
    }

    // Save the start micros()
    start = ImplementationT::micros();

    // Wait until the line is idle again
    while(pinFDR() != idleState) {
        if(ImplementationT::micros() - start > timeoutUS) return 0;
        ImplementationT::yield();
    }

    // Return the pulse length
    return ImplementationT::micros() - start;
}


//
// Protected member functions of the Application_CRTPBase<> class
//
template <typename ImplementationT>
inline void Application_CRTPBase<ImplementationT>::callScheduledAtomicUserCallback() noexcept
{
    for(uint8_t i = 0; i < ImplementationT::MaxScheduledUserCallback; ++i) {
        // Check if the current slot contains a user callback
        if(!ImplementationT::_schedUserCB[i]) continue;
        // The current slot contains a user callback, call it
        {
            // Ensure atomic operation (no interrupt) when calling the user callback
            AtomicGuard g;
            // Call the user calback
            (*ImplementationT::_schedUserCB[i])(ImplementationT::_schedUserCBArg[i]);
            // Clear the slot
            ImplementationT::_schedUserCB[i] = nullptr;
        }
        // Yield
        ImplementationT::yield();
    }
}


template <typename ImplementationT>
inline uint32_t Application_CRTPBase<ImplementationT>::blinkActivityIndicatorLEDAndGetMillis() noexcept
{
    static uint32_t prvMillis = 0;
           uint32_t curMillis = ImplementationT::millis();

    if(curMillis - prvMillis < ActivityIndicatorLEDBlinkDelay) return curMillis;
    prvMillis = curMillis;

    static bool blState = false;
    blState = !blState;
    ImplementationT::setActivityIndicatorLED(blState);

    return curMillis;
}


#endif
