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


#ifndef ECXX_PLATFORM_SLOT_FS_H
#define ECXX_PLATFORM_SLOT_FS_H


#include "RawMemoryCard.h"


namespace eCxx {


//
// A simple slot-based file system
//     # This is a very simple slot-based file system.
//     # Each slot is a single file; chaining slots to form larger files is not supported.
//     # Only a small number of slots can be opened simultaneously (depending on the total
//       RAM of the target platform).
//     # A slot can be opened either for reading or writing, but not both simultaneously.
//     # Most other managerial and informational functions will refuse to work if any slot
//       is currently open.
//
class SlotFS : public Uncopyable {
    private:
        // See the primary descriptor section in "SlotFS-Spec.txt"
        static const uint8_t  PROGMEM MagicNumber  [16];
        static const char     PROGMEM MagicString  [34];

        static const uint16_t PROGMEM ValidSlotSize[10];

        // See the primary descriptor section in "SlotFS-Spec.txt"
        static constexpr uint16_t FSVersion          = 0x0001;

        static constexpr uint16_t SlotDescStart      = 0x0001;
        static constexpr uint16_t SlotDataStart      = 0x2000;
        static constexpr uint16_t SlotMaxCount       = SlotDataStart - SlotDescStart;

        static constexpr uint8_t  PD_ReservedSize_1  =  12; // The number of bytes for the "reserved1" field
        static constexpr uint8_t  PD_ReservedSize_2  =  50; // The number of bytes for the "reserved2" field
        static constexpr uint8_t  PD_ReservedSize_3a = 128; // The number of bytes for the "reserved3" field
        static constexpr uint8_t  PD_ReservedSize_3b = 128;

        static constexpr uint8_t  PD_UserDesc_Skip   = 128; // The number of bytes before the "userDesc" field
        static constexpr uint16_t PD_UserDesc_Len    = 128; // The number of bytes for    the "userDesc" field

        static constexpr uint32_t SlotSizeMult       = 4UL * 1024UL * 1024UL; // 4 MiB

        // See the slot descriptor section in "SlotFS-Spec.txt"
        static constexpr uint8_t SD_SlotEmpty      =  0; // Value for the "slotType" field
        static constexpr uint8_t SD_SlotOccupied   =  1; // Value for the "slotType" field

        static constexpr uint8_t SD_ReservedSize_1 =  3; // The number of bytes for the "reserved1" field
        static constexpr uint8_t SD_ReservedSize_2 = 64; // The number of bytes for the "reserved2" field

        static constexpr uint8_t SD_DataSize_Skip  =  4; // The number of bytes before the "dataSize" field

        static constexpr uint8_t SD_UserID_Skip    =  8; // The number of bytes before the "userID" field
        static constexpr uint8_t SD_UserID_Len     =  8; // The number of bytes before the "userID" field

        static constexpr uint8_t SD_UserDesc_Skip  = 16; // The number of bytes before the "userDesc" field
        static constexpr uint8_t SD_UserDesc_Len   = 48; // The number of bytes before the "userDesc" field

        struct __ECXX_ATTR_PACKED SlotDescriptor {
            uint8_t  slotType;
            uint8_t  reserved1[SD_ReservedSize_1];
            uint32_t dataSize;
            char     userID   [SD_UserID_Len    ];
            char     userDesc [SD_UserDesc_Len  ];
        };

    public:
        // Result code for memory card operations
        using RMCResultCode = RawMemoryCard::RMCResultCode;

        // Memory card sector size in bytes
        static constexpr uint16_t SectorSize = RawMemoryCard::SectorSize;

        // Byte size of user description text in SlotFS primary descriptor
        static constexpr uint8_t  FSUserDescLen = PD_UserDesc_Len;

        // Byte size of user ID and description text in SlotFS slot descriptor
        static constexpr uint8_t  SDUserIDLen   = SD_UserID_Len;
        static constexpr uint8_t  SDUserDescLen = SD_UserDesc_Len;

        // Invalid slot number and user data size
        static constexpr uint16_t InvalidSlotNumber   = 0xFFFF;
        static constexpr uint32_t InvalidSlotDataSize = 0xFFFFFFFF;

        // Maximum number of slots that can be opened simultaneously
        //     Total RAM size (kiB) : <=1   2   4   6   8   12   16   20   24   >=28
        //     Maximum open slots   :   1   3   4   5   6    8   10   12   14   16
        static constexpr uint8_t MaxOpenSlots = (RAM_SIZE_KB <= 1)
                                              ? 1
                                              : ( (RAM_SIZE_KB >= 28) ? 16
                                                                      : (RAM_SIZE_KB / 2 + 2)
                                                );

        // Format strategy
        enum class FormatStrategy : uint8_t {
            Balanced         = 0,
            MaximumSlotSize  = 1,
            MaximumSlotCount = 2
        };

        // Seek mode
        enum class SeekMode : uint8_t {
            Beg = 0,
            End = 1,
            Cur = 2
        };

        // Slot handle (used for reading and writing data to a slot)
        class Slot {
            static constexpr uint16_t SlotNumberMask    = 0b0001111111111111;
            static constexpr uint16_t SlotReadWriteMask = 0b0100000000000000;

            static constexpr uint16_t SlotDirtyBit      = 0b1000000000000000;
            static constexpr uint16_t SlotReadBit       = 0b0000000000000000;
            static constexpr uint16_t SlotWriteBit      = 0b0100000000000000;

            uint16_t _slotNumber;     // Slot number with bitmask
                                      // # Bit field description:
                                      //   1111110000000000
                                      //   5432109876543210
                                      //   DM0NNNNNNNNNNNNN
                                      //      1111111111111
                                      //   [15  ] : 0 = clear     ; 1 = dirty (reopen the slot on the next read/write)
                                      //   [14  ] : 0 = read mode ; 1 = write mode
                                      //   [13  ] : reserved - always 0
                                      //   [12:0] : 0000 to 8190 (0x0000 to 0x1FFE)
                                      // # Special value InvalidSlotNumber means the slot is not open

            uint32_t _sectorStart;    // Start sector for this slot

            uint32_t _dataSizeSector; // Actual size of stored user data in number of sectors minus  the final sector
            uint16_t _dataSizeBytes;  // Actual size of stored user data in number of bytes   within the final sector

            uint32_t _curPtrSector;   // Current read/write sector pointer
            uint16_t _curPtrBytes;    // Current read/write byte   pointer within the above sector

            inline Slot();
            inline ~Slot();

            inline uint16_t slotNumber() const;

            inline bool openForAny() const;
            inline bool openForRead() const;
            inline bool openForWrite() const;

            inline bool dirty() const;
            inline void setDirty();
            inline void clearDirty();

            inline void close();

            inline void initializeRead(uint16_t slotNumber, uint16_t slotSize, uint32_t dataSize);
            inline void reinitializeRead(uint32_t startPointerBytes);

            inline void initializeWrite(uint16_t slotNumber, uint16_t slotSize, uint32_t startPointerBytes);

            friend class SlotFS;
        };

        typedef Slot* SlotHandle;

    public:
        // Match slot size and count
        static bool matchSlotSizeAndCount(
            uint16_t&      slotSize,
            uint16_t&      slotCount,
            uint32_t       sectorCount,
            FormatStrategy formatStrategy = FormatStrategy::Balanced,
            uint16_t       minSlotCount   = 0, // 0 = auto
            uint16_t       maxSlotCount   = 0  // 0 = auto
        );

    public:
        SlotFS();
        ~SlotFS();

        RMCResultCode begin();
        void end();

        // Access to the raw-mode memory card reader and writer;
        // do not use this unless you know what you are doing!
        inline RawMemoryCard& rmc();

    public:
        // User callback for format-progress
        using FormatProgressCB = eCxx::function<void(uint16_t)>;

        // Direct formatting function
        //     # This function will not check whether the specified slot size and count are correct and valid.
        //     # Please use matchSlotSizeAndCount() to find the proper combination for the attached memory card.
        //     # Customized slot size and/or slot count is possible, but not recommended.
        RMCResultCode format(PGMCharPointer userDesc, uint32_t sectorCount, uint16_t slotSize, uint16_t slotCount, FormatProgressCB progressCB = nullptr);
        RMCResultCode format(PGMCharPointerFar userDesc, uint32_t sectorCount, uint16_t slotSize, uint16_t slotCount, FormatProgressCB progressCB = nullptr);

        // Standard formatting function
        bool format(
            PGMCharPointer   userDesc,
            FormatProgressCB progressCB     = nullptr,
            FormatStrategy   formatStrategy = FormatStrategy::Balanced,
            uint16_t         minSlotCount   = 0, // 0 = auto
            uint16_t         maxSlotCount   = 0  // 0 = auto
        );

        bool format(
            PGMCharPointerFar userDesc,
            FormatProgressCB  progressCB     = nullptr,
            FormatStrategy    formatStrategy = FormatStrategy::Balanced,
            uint16_t          minSlotCount   = 0, // 0 = auto
            uint16_t          maxSlotCount   = 0  // 0 = auto
        );

    public:
        // Mount and unmount
        bool mount();
        void unmount();

        // Get some information about the file system
        inline uint32_t getSectorCount() const;
        inline uint16_t getSlotSize() const;
        inline uint16_t getSlotCount() const;

        // This function will refuse to work if there is any slot open for read/write
        bool getUserDesc(char (&str)[PD_UserDesc_Len]) const;

        // Slot read functions
        SlotHandle slotReadOpen(uint16_t slotNumber);

        bool slotReadClose(SlotHandle handle);

        inline uint32_t slotReadRemainingBytesAvailable(SlotHandle handle) const;
        inline bool slotReadFlush(SlotHandle handle);
        bool slotReadSeek(SlotHandle handle, uint32_t seekPos, SeekMode mode = SeekMode::Beg);

        inline bool slotReadGet(SlotHandle handle, uint8_t& data);
        uint16_t slotReadGet(SlotHandle handle, uint8_t* data, uint16_t size);

        inline uint32_t slotReadGetCurPos(SlotHandle handle) const;

        // Slot write functions
        SlotHandle slotWriteNew(const char* userID, const char* userDesc);
        SlotHandle slotWriteNew(PGMCharPointer userID, PGMCharPointer userDesc);
        SlotHandle slotWriteNew(PGMCharPointerFar userID, PGMCharPointerFar userDesc);
        SlotHandle slotWriteNew(const char (&userID)[SDUserIDLen], const char (&userDesc)[SDUserDescLen]);
        SlotHandle slotWriteOpenTruncate(uint16_t slotNumber, uint32_t truncatePos = 0);
        inline SlotHandle slotWriteOpenAppend(uint16_t slotNumber);

        bool slotWriteClose(SlotHandle handle);

        inline uint32_t slotWriteRemainingBytesAvailable(SlotHandle handle) const;
        inline bool slotWriteFlush(SlotHandle handle);

        inline bool slotWritePut(SlotHandle handle, uint8_t data);
        uint16_t slotWritePut(SlotHandle handle, const uint8_t* data, uint16_t size);

        inline uint32_t slotWriteGetCurPos(SlotHandle handle) const;
        inline uint32_t slotWriteGetCommitedSize(SlotHandle handle) const;

        // Shortcuts for convenience
        inline bool slotFlush(SlotHandle handle);
        inline bool slotClose(SlotHandle handle);

        // Slot managerial functions
        // (these function will refuse to work if there is any slot open for read/write)
        bool freeSlot(uint16_t slotNumber);
        bool freeSlots(const uint16_t* slotNumber, uint8_t count);

        bool slotChangeUserID(uint16_t slotNumber, const char* userID);
        bool slotChangeUserID(uint16_t slotNumber, PGMCharPointer userID);
        bool slotChangeUserID(uint16_t slotNumber, PGMCharPointerFar userID);
        bool slotChangeUserID(uint16_t slotNumber, const char (&userID)[SDUserIDLen]);

        bool slotChangeUserDesc(uint16_t slotNumber, const char* userDesc);
        bool slotChangeUserDesc(uint16_t slotNumber, PGMCharPointer userDesc);
        bool slotChangeUserDesc(uint16_t slotNumber, PGMCharPointerFar userDesc);
        bool slotChangeUserDesc(uint16_t slotNumber, const char (&userDesc)[SDUserDescLen]);

        uint32_t getSlotDataSize(uint16_t slotNumber) const;
        bool getSlotUserID(uint16_t slotNumber, char (&userID)[SDUserIDLen]) const;
        bool getSlotUserDesc(uint16_t slotNumber, char (&userDesc)[SDUserDescLen]) const;

        // Slot informational functions
        // (these function will refuse to work if there is any slot open for read/write)
        uint16_t numberOfEmptySlots() const;
        uint16_t numberOfOccupiedSlots() const;

        bool listOccupiedSlotNumberBegin() const;
        uint16_t listOccupiedSlotNumberGet() const;
        bool listOccupiedSlotNumberEnd() const;

        uint16_t findFirstSlotNumberByUserID(const char* userID, uint16_t slotNumberStart = 0) const;
        uint16_t findFirstSlotNumberByUserID(PGMCharPointer userID, uint16_t slotNumberStart = 0) const;
        uint16_t findFirstSlotNumberByUserID(PGMCharPointerFar userID, uint16_t slotNumberStart = 0) const;
        uint16_t findFirstSlotNumberByUserID(const char (&userID)[SDUserIDLen], uint16_t slotNumberStart = 0) const;

    private:
        // Raw-mode memory card reader and writer
        RawMemoryCard _rmc;

        // See the primary descriptor section in "SlotFS-Spec.txt"
        uint32_t _sectorCount;
        uint16_t _slotSize;
        uint16_t _slotCount;

        // Each slot can be opened either for reading or writing, but not both simultaneously
        Slot       _slot[MaxOpenSlots];
        SlotHandle _activeOpenSlot;

        // State variable for streaming slot informational function
        mutable uint16_t _ssifCurSlotDesc; // idle -> 0 ; in progress -> SlotDescStart to (SlotDescStart + _slotCount - 1)

    private:
        // Formatting functions
        template <typename PGMPointerT>
        RMCResultCode _format(PGMPointerT userDesc, uint32_t sectorCount, uint16_t slotSize, uint16_t slotCount, FormatProgressCB progressCB = nullptr);

        template <typename PGMPointerT>
        inline bool _format(
            PGMPointerT      userDesc,
            FormatProgressCB progressCB,
            FormatStrategy   formatStrategy,
            uint16_t         minSlotCount,
            uint16_t         maxSlotCount
        );

        // Slot write function
        template <typename PGMPointerT>
        inline SlotHandle _slotWriteNew(PGMPointerT userID, PGMPointerT userDesc);

        // Slot managerial functions
        template <typename PGMPointerT>
        inline bool _slotChangeUserID(uint16_t slotNumber, PGMPointerT userID);

        template <typename PGMPointerT>
        inline bool _slotChangeUserDesc(uint16_t slotNumber, PGMPointerT userDesc);

        // Slot informational function
        template <typename PGMPointerT>
        inline uint16_t _findFirstSlotNumberByUserID(PGMPointerT userID, uint16_t slotNumberStart) const;

    private:
        bool _slotReadFlush(SlotHandle handle, bool noChangeActiveOpenSlot);
        bool _slotWriteFlush(SlotHandle handle, bool noChangeActiveOpenSlot);

        // Update the corresponding slot descriptor and handle state
        // (must only be called after the completion of a streaming write operation)
        bool _slotWriteFlush_impl(SlotHandle handle);

        // Clear slot descriptors (hence mark the slots as empty)
        RMCResultCode _clearSlotDescriptors(uint16_t slotCount, FormatProgressCB progressCB);

        // Match slot count and size
        static uint16_t _matchSlotCount(uint16_t& slotSize, uint32_t scaledSectorCount, uint16_t minimumSlotCount, uint16_t maximumSlotCount, bool revSearch);

        // Slot helper functions
        inline bool _anyOpenSlot() const;
        inline bool _slotAlreadyOpen(uint16_t slotNumber) const;
        inline bool _changeActiveOpenSlotTo(SlotHandle handle);
        inline SlotHandle _findAvailableSlotHandle();

        // Helper functions to write data using streaming write
        inline void _writeZeroes(uint8_t count);
        inline void _writeUInt16_LE(uint16_t value);
        inline void _writeUInt32_LE(uint32_t value);

        template <typename PGMPointerT>
        inline void _writeBytesFromNullTerminatedPGMPtr(PGMPointerT pgmPtr, uint8_t maxCount);

        // Helper functions to check data using streaming read
        inline bool _checkZeroes(uint8_t count);
        inline bool _checkUInt16_LE(uint16_t value);
        inline bool _checkUInt32_LE(uint32_t value);

        template <typename PGMPointerT>
        inline bool _checkBytesWithPGMPtr(PGMPointerT pgmPtr, uint8_t count);

        // Helper functions to read/skip data using streaming read
        inline uint16_t _readUInt16_LE();
        inline uint32_t _readUInt32_LE();
        inline void _skipBytes(uint8_t count);
};


//
// Private inline member functions of SlotFS::Slot class
//
inline SlotFS::Slot::Slot()
: _slotNumber(InvalidSlotNumber)
{}


inline SlotFS::Slot::~Slot()
{}


inline uint16_t SlotFS::Slot::slotNumber() const
{ return _slotNumber & SlotNumberMask;  }


inline bool SlotFS::Slot::openForAny() const
{ return _slotNumber != InvalidSlotNumber;  }


inline bool SlotFS::Slot::openForRead() const
{ return openForAny() && (_slotNumber & SlotReadWriteMask) == SlotReadBit; }


inline bool SlotFS::Slot::openForWrite() const
{ return openForAny() && (_slotNumber & SlotReadWriteMask) == SlotWriteBit; }


inline bool SlotFS::Slot::dirty() const
{ return openForAny() && (_slotNumber & SlotDirtyBit); }


inline void SlotFS::Slot::setDirty()
{ _slotNumber |= SlotDirtyBit; }


inline void SlotFS::Slot::clearDirty()
{ _slotNumber &= ~SlotDirtyBit; }


inline void SlotFS::Slot::close()
{ _slotNumber = InvalidSlotNumber; }


inline void SlotFS::Slot::initializeRead(uint16_t slotNumber, uint16_t slotSize, uint32_t dataSize)
{
    // Initialize the slot number and set the dirty and read bits
    _slotNumber = slotNumber | SlotDirtyBit | SlotReadBit;

    // Initialize the sector start
    _sectorStart = static_cast<uint32_t>(SlotDataStart)
                 + static_cast<uint32_t>(slotNumber   ) * SlotSizeMult / SectorSize * slotSize;

    // Read starting from byte #0
    _dataSizeSector = dataSize / SectorSize;
    _dataSizeBytes  = dataSize - _dataSizeSector * SectorSize;

    _curPtrSector   = 0;
    _curPtrBytes    = 0;
}


inline void SlotFS::Slot::reinitializeRead(uint32_t startPointerBytes)
{
    // Set the dirty bit
    _slotNumber |= SlotDirtyBit;

    // Read starting from byte #startPointerBytes
    _curPtrSector = startPointerBytes / SectorSize;
    _curPtrBytes  = startPointerBytes - _curPtrSector * SectorSize;
}


inline void SlotFS::Slot::initializeWrite(uint16_t slotNumber, uint16_t slotSize, uint32_t startPointerBytes)
{
    // Initialize the slot number and set the dirty and write bits
    _slotNumber = slotNumber | SlotDirtyBit | SlotWriteBit;

    // Initialize the sector start
    _sectorStart = static_cast<uint32_t>(SlotDataStart)
                 + static_cast<uint32_t>(slotNumber   ) * SlotSizeMult / SectorSize * slotSize;

    // Write starting from byte #0
    if(!startPointerBytes) {
        _dataSizeSector = 0;
        _dataSizeBytes  = 0;
        _curPtrSector   = 0;
        _curPtrBytes    = 0;
        return;
    }

    // Write starting from byte > #0
    _dataSizeSector = startPointerBytes / SectorSize;
    _dataSizeBytes  = startPointerBytes - _dataSizeSector * SectorSize;

    _curPtrSector   = _dataSizeSector;
    _curPtrBytes    = _dataSizeBytes;
}


//
// Public inline member functions of SlotFS class
//
inline RawMemoryCard& SlotFS::rmc()
{ return _rmc;}


inline uint32_t SlotFS::getSectorCount() const
{ return _sectorCount; }


inline uint16_t SlotFS::getSlotSize() const
{ return _slotSize; }


inline uint16_t SlotFS::getSlotCount() const
{ return _slotCount; }


inline uint32_t SlotFS::slotReadRemainingBytesAvailable(SlotHandle handle) const
{
    // Simply exit if the slot is not open for reading
    if( !handle->openForRead() ) return 0;

    // Calculate the actual size of stored user data in bytes
    const uint32_t dataSizeStored = handle->_dataSizeSector * SectorSize + handle->_dataSizeBytes;

    // Calculate the actual size of read user data in bytes
    const uint32_t dataSizeRead = handle->_curPtrSector * SectorSize + handle->_curPtrBytes;

    // Return the remaining bytes available for reading
    return dataSizeStored - dataSizeRead;
}


inline bool SlotFS::slotReadFlush(SlotHandle handle)
{
    // Simply exit if the slot is not open for reading
    if( !handle->openForRead() ) return false;

    // Flush the slot
    return _slotReadFlush(handle, false);
}


inline bool SlotFS::slotReadGet(SlotHandle handle, uint8_t& data)
{ return slotReadGet(handle, &data, 1) == 1; }


inline uint32_t SlotFS::slotReadGetCurPos(SlotHandle handle) const
{
    // Simply exit if the slot is not open for reading
    if( !handle->openForRead() ) return 0;

    // Return the actual size of stored user data in bytes that has been read
    return  handle->_curPtrSector * SectorSize + handle->_curPtrBytes;
}


inline SlotFS::SlotHandle SlotFS::slotWriteOpenAppend(uint16_t slotNumber)
{
    // Open and truncate at beyond the end of slot (hence appending the new data at the end of the slot)
    return slotWriteOpenTruncate(slotNumber, InvalidSlotDataSize);
}


inline uint32_t SlotFS::slotWriteRemainingBytesAvailable(SlotHandle handle) const
{
    // Simply exit if the slot is not open for writing
    if( !handle->openForWrite() ) return 0;

    // Calculate the actual size of stored user data in bytes
    const uint32_t dataSize = handle->_curPtrSector * SectorSize + handle->_curPtrBytes;

    // Return the remaining bytes available for writing
    return static_cast<uint32_t>(_slotSize) * SlotSizeMult - dataSize;
}


inline bool SlotFS::slotWriteFlush(SlotHandle handle)
{
    // Simply exit if the slot is not open for writing
    if( !handle->openForWrite() ) return false;

    // Flush the slot
    return _slotWriteFlush(handle, false);
}


inline bool SlotFS::slotWritePut(SlotHandle handle, uint8_t data)
{ return slotWritePut(handle, &data, 1) == 1; }


inline uint32_t SlotFS::slotWriteGetCurPos(SlotHandle handle) const
{
    // Simply exit if the slot is not open for writing
    if( !handle->openForWrite() ) return 0;

    // Return the actual size of stored user data in bytes that has been written
    return  handle->_curPtrSector * SectorSize + handle->_curPtrBytes;
}


inline uint32_t SlotFS::slotWriteGetCommitedSize(SlotHandle handle) const
{
    // Simply exit if the slot is not open for writing
    if( !handle->openForWrite() ) return 0;

    // Return the actual size of stored user data in bytes that has been flushed to the memory card
    return  handle->_dataSizeSector * SectorSize + handle->_dataSizeBytes;
}


inline bool SlotFS::slotFlush(SlotHandle handle)
{
    if( handle->openForRead () ) return slotReadFlush (handle);
    if( handle->openForWrite() ) return slotWriteFlush(handle);
    return false;
}


inline bool SlotFS::slotClose(SlotHandle handle)
{
    if( handle->openForRead () ) return slotReadClose (handle);
    if( handle->openForWrite() ) return slotWriteClose(handle);
    return false;
}


//
// Private inline member functions of SlotFS class
//
inline bool SlotFS::_anyOpenSlot() const
{
    for(uint8_t i = 0; i < MaxOpenSlots; ++i) {
        if( _slot[i].openForAny() ) return true;
    }

    return false;
}


inline bool SlotFS::_slotAlreadyOpen(uint16_t slotNumber) const
{
    for(uint8_t i = 0; i < MaxOpenSlots; ++i) {
        if( _slot[i].slotNumber() == slotNumber && _slot[i].openForAny() ) return true;
    }

    return false;
}


inline bool SlotFS::_changeActiveOpenSlotTo(SlotHandle handle)
{
    // Flush the previous active open slot as needed
    if(_activeOpenSlot) {
        // Read flush
        if( _activeOpenSlot->openForRead() ) {
            if( !_slotReadFlush(_activeOpenSlot, true) ) return false;
        }
        // Write flush
        else if( _activeOpenSlot->openForWrite() ) {
            if( !_slotWriteFlush(_activeOpenSlot, true) ) return false;
        }
        // Set the dirty bit
        _activeOpenSlot->setDirty();
    }

    // Change the active open slot
    _activeOpenSlot = handle;

    // Done
    return true;
}


inline SlotFS::SlotHandle SlotFS::_findAvailableSlotHandle()
{
    for(uint8_t i = 0; i < MaxOpenSlots; ++i) {
        if( !_slot[i].openForAny() ) return &_slot[i];
    }

    return nullptr;
}


inline void SlotFS::_writeZeroes(uint8_t count)
{
    const uint8_t ch = 0;

    while(count--) _rmc.streamingWritePut(&ch, 1);
}


inline void SlotFS::_writeUInt16_LE(uint16_t value)
{
    const uint8_t num16[2] = {
        static_cast<uint8_t>(value & 0x00FF),
        static_cast<uint8_t>(value >> 8    ),
    };

    _rmc.streamingWritePut(num16, 2);
}


inline void SlotFS::_writeUInt32_LE(uint32_t value)
{
    const uint8_t num32[4] = {
        static_cast<uint8_t>(  value        & 0x000000FF ),
        static_cast<uint8_t>( (value >>  8) & 0x000000FF ),
        static_cast<uint8_t>( (value >> 16) & 0x000000FF ),
        static_cast<uint8_t>(  value >> 24               )
    };

    _rmc.streamingWritePut(num32, 4);
}


template <typename PGMPointerT>
inline void SlotFS::_writeBytesFromNullTerminatedPGMPtr(PGMPointerT pgmPtr, uint8_t maxCount)
{
    while(maxCount--) {
        const uint8_t ch = PGMReadCharPtrInc<uint8_t, PGMPointerT>::_(pgmPtr);
        if(!ch) break;
        _rmc.streamingWritePut(&ch, 1);
    }
}


inline bool SlotFS::_checkZeroes(uint8_t count)
{
    uint8_t ch;

    while(count--) {
        _rmc.streamingReadGet(&ch, 1);
        if(ch) return false;
    }

    return true;
}


inline bool SlotFS::_checkUInt16_LE(uint16_t ref)
{
    uint8_t num16[2];

    _rmc.streamingReadGet(num16, 2);

    return ( num16[0] == static_cast<uint8_t>(ref & 0x00FF) ) &&
           ( num16[1] == static_cast<uint8_t>(ref >> 8    ) );
}


inline bool SlotFS::_checkUInt32_LE(uint32_t ref)
{
    uint8_t num32[4];

    _rmc.streamingReadGet(num32, 4);

    return ( num32[0] == static_cast<uint8_t>(  ref        & 0x000000FF) ) &&
           ( num32[1] == static_cast<uint8_t>( (ref >>  8) & 0x000000FF) ) &&
           ( num32[2] == static_cast<uint8_t>( (ref >> 16) & 0x000000FF) ) &&
           ( num32[3] == static_cast<uint8_t>(  ref >> 24              ) );
}


template <typename PGMPointerT>
inline bool SlotFS::_checkBytesWithPGMPtr(PGMPointerT refPGMPtr, uint8_t count)
{
    uint8_t ch;

    while(count--) {
        _rmc.streamingReadGet(&ch, 1);
        if( ch != PGMReadCharPtrInc<uint8_t, PGMPointerT>::_(refPGMPtr) ) return false;
    }

    return true;
}


inline uint16_t SlotFS::_readUInt16_LE()
{
    uint8_t num16[2];

    _rmc.streamingReadGet(num16, 2);

    return ( static_cast<uint16_t>( num16[0] )      ) |
           ( static_cast<uint16_t>( num16[1] ) << 8 );
}


inline uint32_t SlotFS::_readUInt32_LE()
{
    uint8_t num32[4];

    _rmc.streamingReadGet(num32, 4);

    return ( static_cast<uint32_t>( num32[0] )       ) |
           ( static_cast<uint32_t>( num32[1] ) <<  8 ) |
           ( static_cast<uint32_t>( num32[2] ) << 16 ) |
           ( static_cast<uint32_t>( num32[3] ) << 24 );
}


inline void SlotFS::_skipBytes(uint8_t count)
{
    uint8_t ch;

    while(count--) _rmc.streamingReadGet(&ch, 1);
}


} // namespace eCxx


#endif
