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


#ifndef ECXX_PLATFORM_HUB75_SEND_DATA_H
#define ECXX_PLATFORM_HUB75_SEND_DATA_H


namespace eCxx {


//
// Helper structs to send data bits to the data pin(s) for GenericHUB75Driver<T>
//
template <HUB75Config::DataPin, typename HUB75ControlPinT, uint8_t ClockDelayN0a, uint8_t ClockDelayN0b, uint8_t ClockDelayN1, typename HUB75DataPinT, uint16_t RowPixelCountN, uint8_t ColorByteSizeN>
struct HUB75SendData;


template <typename HUB75ControlPinT, uint8_t ClockDelayN0a, uint8_t ClockDelayN0b, uint8_t ClockDelayN1, typename HUB75DataPinT, uint16_t RowPixelCountN, uint8_t ColorByteSizeN>
struct HUB75SendData<HUB75Config::DataPin::R0_R1_G0_G1_B0_B1, HUB75ControlPinT, ClockDelayN0a, ClockDelayN0b, ClockDelayN1, HUB75DataPinT, RowPixelCountN, ColorByteSizeN> {
    template <bool LatchHighEndPulseEnabled>
    static inline __ECXX_ATTR_ALWAYS_INLINE void ICACHE_RAM_ATTR writeDataBits(
        const uint8_t* rowBuffer, uint8_t bcmPhase, const HUB75ControlPinT& ctlPin, const HUB75DataPinT& dataPin, uint8_t latchHighEndPulseCount
    ) noexcept
    {
        // Get the planes' pointers
        const uint8_t* plane0 = rowBuffer;
        const uint8_t* plane1 = rowBuffer + (RowPixelCountN / 2 * ColorByteSizeN);

        // Send the bits
        for(uint16_t i = 0; i < RowPixelCountN / 2; ++i) {
            // Write the data bits
            dataPin.r0.digitalWrite( *plane0++ & bcmPhase );
            dataPin.g0.digitalWrite( *plane0++ & bcmPhase );
            dataPin.b0.digitalWrite( *plane0++ & bcmPhase );
            dataPin.r1.digitalWrite( *plane1++ & bcmPhase );
            dataPin.g1.digitalWrite( *plane1++ & bcmPhase );
            dataPin.b1.digitalWrite( *plane1++ & bcmPhase );
            // Assert the latch pin as needed
            __if_constexpr(LatchHighEndPulseEnabled) {
                if( i == (RowPixelCountN / 2 - latchHighEndPulseCount) ) {
                    ctlPin.le.digitalWrite(HUB75ControlPinT::LatchH);
                    asmNOP<ClockDelayN1 >();
                }
            }
            // Pulse the clock pin
            ctlPin.pulseClock();
        }
    }

    template <uint8_t RegValCountN>
    static inline void writeRegister(
        const uint8_t* regVal, const HUB75ControlPinT& ctlPin, const HUB75DataPinT& dataPin, uint8_t latchHighEndPulseCount
    ) noexcept
    {
        // Bit counter
        constexpr uint8_t  chipCount     = RowPixelCountN / 2 / RegValCountN / 8;
        constexpr uint16_t bitTotalCount = static_cast<uint16_t>(chipCount) * RegValCountN * 8;
                  uint16_t bitSendCount  = 0;

        // Deassert the latch pin
        ctlPin.le.digitalWrite(HUB75ControlPinT::LatchL);
        asmNOP<ClockDelayN0a>();

        // Loop as many as the number of chips
        for(uint16_t c = 0; c < chipCount; ++c) {
            // Loop as many as the number of register values
            for(uint8_t v = 0; v < RegValCountN; ++v) {
                // Get the current value
                uint8_t curVal = regVal[v];
                // Send 8 bits
                for(uint8_t b = 0; b < 8; ++b) {
                    // Get the MSB
                    const bool bit = (curVal & 0b10000000);
                    curVal <<= 1;
                    // Set the bits
                    dataPin.r0.digitalWrite(bit);
                    dataPin.g0.digitalWrite(bit);
                    dataPin.b0.digitalWrite(bit);
                    dataPin.r1.digitalWrite(bit);
                    dataPin.g1.digitalWrite(bit);
                    dataPin.b1.digitalWrite(bit);
                    // Assert the latch pin as needed
                    if(bitSendCount == bitTotalCount - latchHighEndPulseCount) {
                        ctlPin.le.digitalWrite(HUB75ControlPinT::LatchH);
                        asmNOP<ClockDelayN1>();
                    }
                    // Pulse the clock pin
                    ctlPin.pulseClock();
                    // Increment the bit counter
                    ++bitSendCount;
                } // for b
            } // for v
        } // for c

        // Deassert the latch pin
        ctlPin.le.digitalWrite(HUB75ControlPinT::LatchL);
        asmNOP<ClockDelayN0b>();
    }
};


template <typename HUB75ControlPinT, uint8_t ClockDelayN0a, uint8_t ClockDelayN0b, uint8_t ClockDelayN1, typename HUB75DataPinT, uint16_t RowPixelCountN, uint8_t ColorByteSizeN>
struct HUB75SendData<HUB75Config::DataPin::R0R1_G0G1_B0B1, HUB75ControlPinT, ClockDelayN0a, ClockDelayN0b, ClockDelayN1, HUB75DataPinT, RowPixelCountN, ColorByteSizeN> {
    template <bool LatchHighEndPulseEnabled>
    static inline __ECXX_ATTR_ALWAYS_INLINE void ICACHE_RAM_ATTR writeDataBits(
        const uint8_t* rowBuffer, uint8_t bcmPhase, const HUB75ControlPinT& ctlPin, const HUB75DataPinT& dataPin, uint8_t latchHighEndPulseCount
    ) noexcept
    {
        // Get the planes' pointers
        const uint8_t* plane0 = rowBuffer;
        const uint8_t* plane1 = rowBuffer + (RowPixelCountN / 2 * ColorByteSizeN);

        // Send the plane-1's bits
        for(uint16_t i = 0; i < RowPixelCountN / 2; ++i) {
            // Write the data bits
            dataPin.r01.digitalWrite( *plane1++ & bcmPhase );
            dataPin.g01.digitalWrite( *plane1++ & bcmPhase );
            dataPin.b01.digitalWrite( *plane1++ & bcmPhase );
            // Pulse the clock pin
            ctlPin.pulseClock();
        }

        // Send the plane-0's bits
        for(uint16_t i = 0; i < RowPixelCountN / 2; ++i) {
            // Write the data bits
            dataPin.r01.digitalWrite( *plane0++ & bcmPhase );
            dataPin.g01.digitalWrite( *plane0++ & bcmPhase );
            dataPin.b01.digitalWrite( *plane0++ & bcmPhase );
            // Assert the latch pin as needed
            __if_constexpr(LatchHighEndPulseEnabled) {
                if( i == (RowPixelCountN / 2 - latchHighEndPulseCount) ) {
                    ctlPin.le.digitalWrite(HUB75ControlPinT::LatchH);
                    asmNOP<ClockDelayN1 >();
                }
            }
            // Pulse the clock pin
            ctlPin.pulseClock();
        }
    }

    template <uint8_t RegValCountN>
    static inline void writeRegister(
        const uint8_t* regVal, const HUB75ControlPinT& ctlPin, const HUB75DataPinT& dataPin, uint8_t latchHighEndPulseCount
    ) noexcept
    {
        // Bit counter
        constexpr uint8_t  chainingMF    = 2;
        constexpr uint8_t  chipCount     = RowPixelCountN / 2 / RegValCountN / 8;
        constexpr uint16_t bitTotalCount = static_cast<uint16_t>(chipCount) * RegValCountN * 8 * chainingMF;
                  uint16_t bitSendCount  = 0;

        // Deassert the latch pin
        ctlPin.le.digitalWrite(HUB75ControlPinT::LatchL);
        asmNOP<ClockDelayN0a>();

        // Loop as many as the number of chips
        for(uint16_t c = 0; c < chipCount; ++c) {
            // Send everything multiple times because of the chaining
            for(uint8_t p = 0; p < chainingMF; ++p) {
                // Loop as many as the number of register values
                for(uint8_t v = 0; v < RegValCountN; ++v) {
                    // Get the current value
                    uint8_t curVal = regVal[v];
                    // Send 8 bits
                    for(uint8_t b = 0; b < 8; ++b) {
                        // Get the MSB
                        const bool bit = (curVal & 0b10000000);
                        curVal <<= 1;
                        // Set the bits
                        dataPin.r01.digitalWrite(bit);
                        dataPin.g01.digitalWrite(bit);
                        dataPin.b01.digitalWrite(bit);
                        // Assert the latch pin as needed
                        if(bitSendCount == bitTotalCount - latchHighEndPulseCount) {
                            ctlPin.le.digitalWrite(HUB75ControlPinT::LatchH);
                            asmNOP<ClockDelayN1>();
                        }
                        // Pulse the clock pin
                        ctlPin.pulseClock();
                        // Increment the bit counter
                        ++bitSendCount;
                    } // for b
                } // for v
            } // for p
        } // for c

        // Deassert the latch pin
        ctlPin.le.digitalWrite(HUB75ControlPinT::LatchL);
        asmNOP<ClockDelayN0b>();
    }
};


template <typename HUB75ControlPinT, uint8_t ClockDelayN0a, uint8_t ClockDelayN0b, uint8_t ClockDelayN1, typename HUB75DataPinT, uint16_t RowPixelCountN, uint8_t ColorByteSizeN>
struct HUB75SendData<HUB75Config::DataPin::R0G0B0_R1G1B1, HUB75ControlPinT, ClockDelayN0a, ClockDelayN0b, ClockDelayN1, HUB75DataPinT, RowPixelCountN, ColorByteSizeN> {
    template <bool LatchHighEndPulseEnabled>
    static inline __ECXX_ATTR_ALWAYS_INLINE void ICACHE_RAM_ATTR writeDataBits(
        const uint8_t* rowBuffer, uint8_t bcmPhase, const HUB75ControlPinT& ctlPin, const HUB75DataPinT& dataPin, uint8_t latchHighEndPulseCount
    ) noexcept
    {
        // Get the planes' pointers
        const uint8_t* plane0s = rowBuffer;
        const uint8_t* plane1s = rowBuffer + (RowPixelCountN / 2 * ColorByteSizeN);

        const uint8_t* plane0;
        const uint8_t* plane1;

        // Send the blue-channel's bits
        plane0 = plane0s + 2;
        plane1 = plane1s + 2;

        for(uint16_t i = 0; i < RowPixelCountN / 2; ++i) {
            // Write the data bits
            dataPin.rgb0.digitalWrite( *plane0 & bcmPhase ); plane0 += 3;
            dataPin.rgb1.digitalWrite( *plane1 & bcmPhase ); plane1 += 3;
            // Pulse the clock pin
            ctlPin.pulseClock();
        }

        // Send the green-channel's bits
        plane0 = plane0s + 1;
        plane1 = plane1s + 1;

        for(uint16_t i = 0; i < RowPixelCountN / 2; ++i) {
            // Write the data bits
            dataPin.rgb0.digitalWrite( *plane0 & bcmPhase ); plane0 += 3;
            dataPin.rgb1.digitalWrite( *plane1 & bcmPhase ); plane1 += 3;
            // Pulse the clock pin
            ctlPin.pulseClock();
        }

        // Send the red-channel's bits
        plane0 = plane0s + 0;
        plane1 = plane1s + 0;

        for(uint16_t i = 0; i < RowPixelCountN / 2; ++i) {
            // Write the data bits
            dataPin.rgb0.digitalWrite( *plane0 & bcmPhase ); plane0 += 3;
            dataPin.rgb1.digitalWrite( *plane1 & bcmPhase ); plane1 += 3;
            // Assert the latch pin as needed
            __if_constexpr(LatchHighEndPulseEnabled) {
                if( i == (RowPixelCountN / 2 - latchHighEndPulseCount) ) {
                    ctlPin.le.digitalWrite(HUB75ControlPinT::LatchH);
                    asmNOP<ClockDelayN1 >();
                }
            }
            // Pulse the clock pin
            ctlPin.pulseClock();
        }
    }

    template <uint8_t RegValCountN>
    static inline void writeRegister(
        const uint8_t* regVal, const HUB75ControlPinT& ctlPin, const HUB75DataPinT& dataPin, uint8_t latchHighEndPulseCount
    ) noexcept
    {
        // Bit counter
        constexpr uint8_t  chainingMF    = 3;
        constexpr uint8_t  chipCount     = RowPixelCountN / 2 / RegValCountN / 8;
        constexpr uint16_t bitTotalCount = static_cast<uint16_t>(chipCount) * RegValCountN * 8 * chainingMF;
                  uint16_t bitSendCount  = 0;

        // Deassert the latch pin
        ctlPin.le.digitalWrite(HUB75ControlPinT::LatchL);
        asmNOP<ClockDelayN0a>();

        // Loop as many as the number of chips
        for(uint16_t c = 0; c < chipCount; ++c) {
            // Send everything multiple times because of the chaining
            for(uint8_t p = 0; p < chainingMF; ++p) {
                // Loop as many as the number of register values
                for(uint8_t v = 0; v < RegValCountN; ++v) {
                    // Get the current value
                    uint8_t curVal = regVal[v];
                    // Send 8 bits
                    for(uint8_t b = 0; b < 8; ++b) {
                        // Get the MSB
                        const bool bit = (curVal & 0b10000000);
                        curVal <<= 1;
                        // Set the bits
                        dataPin.rgb0.digitalWrite(bit);
                        dataPin.rgb1.digitalWrite(bit);
                        // Assert the latch pin as needed
                        if(bitSendCount == bitTotalCount - latchHighEndPulseCount) {
                            ctlPin.le.digitalWrite(HUB75ControlPinT::LatchH);
                            asmNOP<ClockDelayN1>();
                        }
                        // Pulse the clock pin
                        ctlPin.pulseClock();
                        // Increment the bit counter
                        ++bitSendCount;
                    } // for b
                } // for v
            } // for p
        } // for c

        // Deassert the latch pin
        ctlPin.le.digitalWrite(HUB75ControlPinT::LatchL);
        asmNOP<ClockDelayN0b>();
    }
};


template <typename HUB75ControlPinT, uint8_t ClockDelayN0a, uint8_t ClockDelayN0b, uint8_t ClockDelayN1, typename HUB75DataPinT, uint16_t RowPixelCountN, uint8_t ColorByteSizeN>
struct HUB75SendData<HUB75Config::DataPin::R0G0B0R1G1B1, HUB75ControlPinT, ClockDelayN0a, ClockDelayN0b, ClockDelayN1, HUB75DataPinT, RowPixelCountN, ColorByteSizeN> {
    template <bool LatchHighEndPulseEnabled>
    static inline __ECXX_ATTR_ALWAYS_INLINE void ICACHE_RAM_ATTR writeDataBits(
        const uint8_t* rowBuffer, uint8_t bcmPhase, const HUB75ControlPinT& ctlPin, const HUB75DataPinT& dataPin, uint8_t latchHighEndPulseCount
    ) noexcept
    {
        // Get the planes' pointers
        const uint8_t* plane0s = rowBuffer;
        const uint8_t* plane1s = rowBuffer + (RowPixelCountN / 2 * ColorByteSizeN);

        const uint8_t* plane0;
        const uint8_t* plane1;

        // Send the plane-1's blue-channel's bits
        plane1 = plane1s + 2;

        for(uint16_t i = 0; i < RowPixelCountN / 2; ++i) {
            // Write the data bits
            dataPin.rgb01.digitalWrite( *plane1 & bcmPhase ); plane1 += 3;
            // Pulse the clock pin
            ctlPin.pulseClock();
        }

        // Send the plane-1's green-channel's bits
        plane1 = plane1s + 1;

        for(uint16_t i = 0; i < RowPixelCountN / 2; ++i) {
            // Write the data bits
            dataPin.rgb01.digitalWrite( *plane1 & bcmPhase ); plane1 += 3;
            // Pulse the clock pin
            ctlPin.pulseClock();
        }

        // Send the plane-1's red-channel's bits
        plane1 = plane1s + 0;

        for(uint16_t i = 0; i < RowPixelCountN / 2; ++i) {
            // Write the data bits
            dataPin.rgb01.digitalWrite( *plane1 & bcmPhase ); plane1 += 3;
            // Pulse the clock pin
            ctlPin.pulseClock();
        }

        // Send the plane-0's blue-channel's bits
        plane0 = plane0s + 2;

        for(uint16_t i = 0; i < RowPixelCountN / 2; ++i) {
            // Write the data bits
            dataPin.rgb01.digitalWrite( *plane0 & bcmPhase ); plane0 += 3;
            // Pulse the clock pin
            ctlPin.pulseClock();
        }

        // Send the plane-0's green-channel's bits
        plane0 = plane0s + 1;

        for(uint16_t i = 0; i < RowPixelCountN / 2; ++i) {
            // Write the data bits
            dataPin.rgb01.digitalWrite( *plane0 & bcmPhase ); plane0 += 3;
            // Pulse the clock pin
            ctlPin.pulseClock();
        }

        // Send the plane-0's red-channel's bits
        plane0 = plane0s + 0;

        for(uint16_t i = 0; i < RowPixelCountN / 2; ++i) {
            // Write the data bits
            dataPin.rgb01.digitalWrite( *plane0 & bcmPhase ); plane0 += 3;
            // Assert the latch pin as needed
            __if_constexpr(LatchHighEndPulseEnabled) {
                if( i == (RowPixelCountN / 2 - latchHighEndPulseCount) ) {
                    ctlPin.le.digitalWrite(HUB75ControlPinT::LatchH);
                    asmNOP<ClockDelayN1 >();
                }
            }
            // Pulse the clock pin
            ctlPin.pulseClock();
        }
    }

    template <uint8_t RegValCountN>
    static inline void writeRegister(
        const uint8_t* regVal, const HUB75ControlPinT& ctlPin, const HUB75DataPinT& dataPin, uint8_t latchHighEndPulseCount
    ) noexcept
    {
        // Bit counter
        constexpr uint8_t  chainingMF    = 6;
        constexpr uint8_t  chipCount     = RowPixelCountN / 2 / RegValCountN / 8;
        constexpr uint16_t bitTotalCount = static_cast<uint16_t>(chipCount) * RegValCountN * 8 * chainingMF;
                  uint16_t bitSendCount  = 0;

        // Deassert the latch pin
        ctlPin.le.digitalWrite(HUB75ControlPinT::LatchL);
        asmNOP<ClockDelayN0a>();

        // Loop as many as the number of chips
        for(uint16_t c = 0; c < chipCount; ++c) {
            // Send everything multiple times because of the chaining
            for(uint8_t p = 0; p < chainingMF; ++p) {
                // Loop as many as the number of register values
                for(uint8_t v = 0; v < RegValCountN; ++v) {
                    // Get the current value
                    uint8_t curVal = regVal[v];
                    // Send 8 bits
                    for(uint8_t b = 0; b < 8; ++b) {
                        // Get the MSB
                        const bool bit = (curVal & 0b10000000);
                        curVal <<= 1;
                        // Set the bits
                        dataPin.rgb01.digitalWrite(bit);
                        // Assert the latch pin as needed
                        if(bitSendCount == bitTotalCount - latchHighEndPulseCount) {
                            ctlPin.le.digitalWrite(HUB75ControlPinT::LatchH);
                            asmNOP<ClockDelayN1>();
                        }
                        // Pulse the clock pin
                        ctlPin.pulseClock();
                        // Increment the bit counter
                        ++bitSendCount;
                    } // for b
                } // for v
            } // for p
        } // for c

        // Deassert the latch pin
        ctlPin.le.digitalWrite(HUB75ControlPinT::LatchL);
        asmNOP<ClockDelayN0b>();
    }
};


} // namespace eCxx


#endif
