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


#ifndef ECXX_PLATFORM_MAX72XX_MATRIX_DRIVER_H
#define ECXX_PLATFORM_MAX72XX_MATRIX_DRIVER_H


#include <eCxx/Platform/Common/Application.h>


namespace eCxx {


//
// A simple MAX7219/MAX7221-based LED matrix driver
//
/*
 * # In all platforms this driver is implemented using bit banging method.
 *
 * # The number of columns and rows must be in multiple of 8.
 *
 * # There is no need to call Application::setPinMode() manually because this driver does it automatically.
 */
class MAX72XXMatrixDriver : public Uncopyable {
    public:
        static constexpr uint8_t ColPerModule =  8; // There is always eight X pixels per module
        static constexpr uint8_t RowPerModule =  8; // There is always eight Y pixels per module

        static constexpr uint8_t MaxIntensity = 15; // Maximum LED intensity (brightness)
        static constexpr uint8_t DefIntensity =  7; // Default LED intensity (brightness)

    public:
        MAX72XXMatrixDriver();
        ~MAX72XXMatrixDriver();

        // NOTE : The 'rawByteBuffer' parameter can be nullptr if using packedUpdated()
        bool begin(uint8_t pinData, uint8_t pinClock, uint8_t pinLatch, uint8_t numCol, uint8_t numRow, const uint8_t* rawByteBuffer);
        void end();

        // Replace the raw byte buffer (it is needed when using double or custom buffering)
        // NOTE : The 'rawByteBuffer' parameter can be nullptr if using packedUpdated()
        void setRawByteBuffer(const uint8_t* rawByteBuffer);

        void setIntensity(uint8_t intensity);

        /*
         * Update (send new data bytes)
         *     # The raw byte buffer specified in begin() must be in 1 byte per pixel format.
         *     # Conventions on the meaning of raw byte values:
         *           + Byte  0 = LED off.
         *           + Byte >0 = LED on.
         */
        void update();

        /*
         * Update (send new data bytes)
         *     # The raw byte buffer specified in begin() must be in 1 byte per pixel format.
         *     # Conventions on the meaning of raw byte values:
         *           + Byte     0  = LED off.
         *           + Byte   255  = LED on.
         *           + Byte [1..7] = LED conditionally on.
         *           + Byte others = currently LED on (reserved for future enchantments and therefore
         *                                             they may change meaning later)
         */
        void updateExt();

        /*
         * Packed update (send new packed data bytes)
         *     # The raw byte buffer specified in begin() or the 'rawByteBuffer'/'pgmPtr' parameter must be
         *       in 8 pixels per byte format (usually pixel #0 = MSB and pixel #7 = LSB). The data must
         *       be arranged to match the display layout manually. The ColorFX2D<T> and ColorFX2DPainter<T>
         *       classes cannot be used because those classes do not support packed pixels.
         *     # Conventions on the meaning of raw bit values:
         *           + Bit 0 = LED off.
         *           + Bit 1 = LED on.
         *     # Special for the update-from-RAM variant:
         *           + If the 'rawByteBuffer' parameter is not nullptr, it will be used.
         *           + If the 'rawByteBuffer' parameter is     nullptr, the raw byte buffer specified in begin()
         *                                                              will be used.
         */
        void packedUpdated(const uint8_t* rawByteBuffer = nullptr);
        void packedUpdated(PGMCharPointer pgmPtr);
        void packedUpdated(PGMCharPointerFar pgmPtr);

    protected:
        inline const uint8_t* rawByteBuffer() const
        { return _rawByteBuffer; }

        inline uint8_t* wrawByteBuffer() const
        { return const_cast<uint8_t*>(_rawByteBuffer); }

        inline uint16_t numMod() const
        { return _numMod; }

    private:
        template <typename PointerT>
        inline void _packedUpdated(PointerT ptr);

    private:
        static constexpr uint8_t RingCntMin = 1;
        static constexpr uint8_t RingCntMax = 7;

        uint8_t              _pinData;
        uint8_t              _pinClock;
        uint8_t              _pinLatch;

        Application::_PinFDW _fdwData;
        Application::_PinFDW _fdwClock;
        Application::_PinFDW _fdwLatch;

        uint16_t             _numMod;

        const uint8_t*       _rawByteBuffer;
        uint16_t             _rawByteSizeLine;

        uint8_t              _ringCnt; // Used by updateExt()
};


} // namespace eCxx


#endif
