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


#include <eCxx/eCxx_Internal.h>
#include <eCxx/Platform/SlotFS.h>


namespace eCxx {


//
// A simple slot-based file system
//
const uint8_t  PROGMEM SlotFS::MagicNumber  [16] = { 0xA1, 0x3B, 0x3D, 0xFC, 0x64, 0x71, 0x5E, 0x8B, 0xAE, 0x28, 0x9E, 0x9F, 0x75, 0xF2, 0x96, 0x50  };
const char     PROGMEM SlotFS::MagicString  [34] = { 'e', 'C', 'x', 'x', ' ', 'S', 'l', 'o', 't', 'F', 'S', ' ', 'F', 'i', 'l', 'e', ' ', 'S', 'y', 's', 't', 'e', 'm', ' ', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r' };

const uint16_t PROGMEM SlotFS::ValidSlotSize[10] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 };


bool SlotFS::matchSlotSizeAndCount(
    uint16_t&      slotSize,
    uint16_t&      slotCount,
    uint32_t       sectorCount,
    FormatStrategy formatStrategy,
    uint16_t       minSlotCount,
    uint16_t       maxSlotCount
)
{
    // Set the result to zero frst
    slotSize  = 0; // Slot size in a multiple of 4 MiB
    slotCount = 0; // The number of slots available for use in the memory card

    // Reduce by the number of sectors that will be used for primary and slot descriptors
    sectorCount -= SlotDataStart;

    // Calculate the scaled sector count
    const uint32_t scaledSectorCount = sectorCount / 4 / 1024 * SectorSize / 1024;

    // Accumulator used for FormatStrategy::Balanced
    uint16_t slotSizeAcc = 0;

    // Calculate the slot count and size for FormatStrategy::MaximumSlotSize or FormatStrategy::Balanced
    if(formatStrategy != FormatStrategy::MaximumSlotCount) {
        const uint16_t minSC = minSlotCount
                             ? minSlotCount
                             : (scaledSectorCount >  32) ?   4 : ( (scaledSectorCount >  16) ?  2 :  1 );
        const uint16_t maxSC = maxSlotCount
                             ? maxSlotCount
                             : (scaledSectorCount > 256) ? 128 : ( (scaledSectorCount > 128) ? 64 : 32 );
        slotCount = _matchSlotCount(slotSize, scaledSectorCount, minSC, maxSC, true);
        if(!slotCount) slotCount = _matchSlotCount(slotSize, scaledSectorCount, 1, 8192, true);
        if(!slotCount) return false;
        slotSizeAcc += slotSize;
    }

    // Calculate the slot count and size for FormatStrategy::MaximumSlotCount or FormatStrategy::Balanced
    if(formatStrategy != FormatStrategy::MaximumSlotSize ) {
        const uint16_t minSC = minSlotCount
                             ? minSlotCount
                             : (scaledSectorCount >  32) ?   32 : ( (scaledSectorCount >  16) ?  16 :   8 );
        const uint16_t maxSC = maxSlotCount
                             ? maxSlotCount
                             : (scaledSectorCount > 256) ? 1024 : ( (scaledSectorCount > 128) ? 512 : 256 );
        slotCount = _matchSlotCount(slotSize, scaledSectorCount, minSC, maxSC, false);
        if(!slotCount) slotCount = _matchSlotCount(slotSize, scaledSectorCount, scaledSectorCount, 8192, true);
        if(!slotCount) return false;
        slotSizeAcc += slotSize * 7;
    }

    // Read-word functor
    const PGMReadWord functor;

    // Additional steps for FormatStrategy::Balanced is needed
    if(formatStrategy == FormatStrategy::Balanced) {
        // Find the nearest slot size from the list of valid slot size
        uint16_t selSize  = 0;
        uint16_t prvDelta = 0xFFFF;
                 slotSize = slotSizeAcc / 8;
        for(uint8_t i = 0; i < arraySize(ValidSlotSize); ++i) {
            const uint16_t valSSize = functor( &ValidSlotSize[i] );
            const uint16_t curDelta = (valSSize > slotSize) ? (valSSize - slotSize) : (slotSize - valSSize);
            if(curDelta < prvDelta) {
                prvDelta = curDelta;
                selSize  = valSSize;
            }
        }
        if(!selSize) return false;
        // Update the result
        slotSize  = selSize;
        slotCount = scaledSectorCount / slotSize;
    }

    // Ensure the total number of slots do not go beyond the number of avilable sectors
    const uint32_t slotSizeBytes = static_cast<uint32_t>(slotSize) * SlotSizeMult;
    if(slotSizeBytes / SectorSize * slotCount > sectorCount) return false;

    // Done
    return true;
}


SlotFS::SlotFS()
: _sectorCount    (0)
, _slotSize       (0)
, _slotCount      (0)
, _activeOpenSlot (nullptr)
, _ssifCurSlotDesc(0)
{}


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


SlotFS::RMCResultCode SlotFS::begin()
{
    // Initialize the memory card
    const RMCResultCode rc = _rmc.begin();
    if(rc != RMCResultCode::NoError) return rc;

    // Done
    return RMCResultCode::NoError;
}


void SlotFS::end()
{
    unmount();
    _rmc.end();
}


template <typename PGMPointerT>
SlotFS::RMCResultCode SlotFS::_format(PGMPointerT userDesc, uint32_t sectorCount, uint16_t slotSize, uint16_t slotCount, FormatProgressCB progressCB)
{
    // Error if the memory card has not been initialized
    if( !_rmc.initialized() ) return RMCResultCode::InvalidOperation;

    // Error if the file system is already mounted
    if(_sectorCount) return RMCResultCode::InvalidOperation;

    // Begin streaming write
    RMCResultCode rc = _rmc.streamingWriteBegin(0, 1);
    if(rc != RMCResultCode::NoError) return rc;

    //
    // Write the SlotFS primary descriptor
    //

    // Write the magic number and magic string
    _writeBytesFromNullTerminatedPGMPtr( eCxx::PGM_PTR( reinterpret_cast<const char*>(MagicNumber) ), sizeof(MagicNumber) );
    _writeBytesFromNullTerminatedPGMPtr( eCxx::PGM_PTR( reinterpret_cast<const char*>(MagicString) ), sizeof(MagicString) );

    // Write the version number
    _writeUInt16_LE(FSVersion);

    // Write the reserved (PD_ReservedSize_1) zeores
    _writeZeroes(PD_ReservedSize_1);

    // Write the sector size and sector count
    _writeUInt16_LE(SectorSize);
    _writeUInt32_LE(sectorCount);

    // Write the slot size and slot count
    _writeUInt16_LE(slotSize);
    _writeUInt16_LE(slotCount);

    // Write the slot descriptor start and slot data start
    _writeUInt16_LE(SlotDescStart);
    _writeUInt16_LE(SlotDataStart);

    // Write the reserved (PD_ReservedSize_2) zeores
    _writeZeroes(PD_ReservedSize_2);

    // Write the user description
    _writeBytesFromNullTerminatedPGMPtr(userDesc, PD_UserDesc_Len);

    // End streaming write (the remaining bytes will be written with zeroes)
    rc = _rmc.streamingWriteEnd();
    if(rc != RMCResultCode::NoError) return rc;

    // Call the user callback for the first time (if supplied)
    if(progressCB) progressCB(0);

    // Clear slot descriptors
    return _clearSlotDescriptors(slotCount, progressCB);
}


SlotFS::RMCResultCode SlotFS::format(PGMCharPointer userDesc, uint32_t sectorCount, uint16_t slotSize, uint16_t slotCount, FormatProgressCB progressCB)
{ return _format(userDesc, sectorCount, slotSize, slotCount, progressCB); }


SlotFS::RMCResultCode SlotFS::format(PGMCharPointerFar userDesc, uint32_t sectorCount, uint16_t slotSize, uint16_t slotCount, FormatProgressCB progressCB)
{ return _format(userDesc, sectorCount, slotSize, slotCount, progressCB); }


template <typename PGMPointerT>
inline bool SlotFS::_format(PGMPointerT userDesc, FormatProgressCB progressCB, FormatStrategy formatStrategy, uint16_t minSlotCount, uint16_t maxSlotCount)
{
    // Get the number of sectors
    uint32_t sectorCount;
    if( _rmc.getSectorCount(sectorCount) != RMCResultCode::NoError ) return false;

    // Match the slot size and count
    uint16_t slotSize, slotCount;
    if( !matchSlotSizeAndCount(slotSize, slotCount, sectorCount, formatStrategy, minSlotCount, maxSlotCount) ) return false;

    // Format the file system
    return format(userDesc, sectorCount, slotSize, slotCount, progressCB) == RMCResultCode::NoError;
}


bool SlotFS::format(PGMCharPointer userDesc, FormatProgressCB progressCB, FormatStrategy formatStrategy, uint16_t minSlotCount, uint16_t maxSlotCount)
{ return _format(userDesc, progressCB, formatStrategy, minSlotCount, maxSlotCount); }


bool SlotFS::format(PGMCharPointerFar userDesc, FormatProgressCB progressCB, FormatStrategy formatStrategy, uint16_t minSlotCount, uint16_t maxSlotCount)
{ return _format(userDesc, progressCB, formatStrategy, minSlotCount, maxSlotCount); }


bool SlotFS::mount()
{
    // Simply exit if the file system is already mounted
    if(_sectorCount) return true;

    // Error if the memory card has not been initialized
    if( !_rmc.initialized() ) return false;

    // Begin streaming read
    if(_rmc.streamingReadBegin(0, 1) != RMCResultCode::NoError) return false;

    //
    // Read and check the SlotFS primary descriptor
    //
    bool ok = true;

    // Check the magic number and magic string
    ok &= _checkBytesWithPGMPtr( eCxx::PGM_PTR( reinterpret_cast<const char*>(MagicNumber) ), sizeof(MagicNumber) );
    ok &= _checkBytesWithPGMPtr( eCxx::PGM_PTR( reinterpret_cast<const char*>(MagicString) ), sizeof(MagicString) );

    // Check the version number
    ok &= _checkUInt16_LE(FSVersion);

    // Check the reserved (PD_ReservedSize_1) zeores
    ok &= _checkZeroes(PD_ReservedSize_1);

    // Check the sector size
    ok &= _checkUInt16_LE(SectorSize);

    // Read the sector count
    _sectorCount = _readUInt32_LE();

    // Read the slot size and slot count
    _slotSize  = _readUInt16_LE();
    _slotCount = _readUInt16_LE();

    // Check the slot descriptor start and slot data start
    ok &= _checkUInt16_LE(SlotDescStart);
    ok &= _checkUInt16_LE(SlotDataStart);

    // Check the reserved (PD_ReservedSize_2) zeores
    ok &= _checkZeroes(PD_ReservedSize_2);

    // Skip the user description
    _skipBytes(PD_UserDesc_Len);

    // Check the reserved (PD_ReservedSize_3) zeores
    ok &= _checkZeroes(PD_ReservedSize_3a);
    ok &= _checkZeroes(PD_ReservedSize_3b);

    // Check for error
    if(!ok) {
        // End streaming read explicitly in case the above code does not read all the bytes
        _rmc.streamingReadEnd();
        // Reset all variables
        _sectorCount = 0;
        _slotSize    = 0;
        _slotCount   = 0;
    }

    // Done
    return ok;
}


void SlotFS::unmount()
{
    // Simply exit if the file system is not mounted
    if(!_sectorCount) return;

    // End any read/write operation
    for(uint8_t i = 0; i < MaxOpenSlots; ++i) {
             if( _slot[i].openForRead () ) slotReadClose ( &_slot[i] );
        else if( _slot[i].openForWrite() ) slotWriteClose( &_slot[i] );
    }

    // Reset variables
    _sectorCount     = 0;
    _slotSize        = 0;
    _slotCount       = 0;
    _activeOpenSlot  = nullptr;
    _ssifCurSlotDesc = 0;
}


bool SlotFS::getUserDesc(char (&str)[PD_UserDesc_Len]) const
{
    // Simply exit if the file system is not mounted
    if(!_sectorCount) return false;

    // Simply exit if there is any slot open for reading/writing
    if( _anyOpenSlot() ) return false;

    // Begin streaming read
    if(_rmc.streamingReadBegin(0, 1) != RMCResultCode::NoError) return false;

    // Skip (PD_UserDesc_Skip) bytes before the user description string
    _rmc.streamingReadGet( reinterpret_cast<uint8_t*>(str), PD_UserDesc_Skip );

    // Read the user description
    _rmc.streamingReadGet( reinterpret_cast<uint8_t*>(str), PD_UserDesc_Len );

    // Done - end streaming read (the remaining bytes will be discarded)
    return _rmc.streamingReadEnd() == RMCResultCode::NoError;
}


// =================================================================================================
// =================================================================================================
// =================================================================================================


//
// ##### Slot read functions #####
//
SlotFS::SlotHandle SlotFS::slotReadOpen(uint16_t slotNumber)
{
    // Simply exit if a streaming slot informational function is in progress
    if(_ssifCurSlotDesc) return nullptr;

    // Check the slot number
    if(slotNumber >= _slotCount) return nullptr;

    // Simply exit if the same slot is already open
    if( _slotAlreadyOpen(slotNumber) ) return nullptr;

    // Find an available slot handle
    SlotHandle handle = _findAvailableSlotHandle();
    if(!handle) return nullptr;

    // Change the active slot
    if( !_changeActiveOpenSlotTo(nullptr) ) return nullptr;

    // Read and check the slot descriptor
    SlotDescriptor slotDescriptor;

    if( _rmc.readOneBlock(
            slotNumber + SlotDescStart, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
        ) != RMCResultCode::NoError
    ) return nullptr;

    if(slotDescriptor.slotType != SD_SlotOccupied) return nullptr;

    // Initialize the handle
    handle->initializeRead(slotNumber, _slotSize, slotDescriptor.dataSize);

    // Return the handle
    return handle;
}


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

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

    // Close the handle
    handle->close();

    // Done
    return true;
}


bool SlotFS::_slotReadFlush(SlotHandle handle, bool noChangeActiveOpenSlot)
{
    // Change the active slot as needed
    if(!noChangeActiveOpenSlot) {
        if( !_changeActiveOpenSlotTo(handle) ) return false;
    }

    // End streaming read as needed
    if( handle->_curPtrBytes && !handle->dirty() ) {
        if( _rmc.streamingReadEnd() != RMCResultCode::NoError ) return false;
    }

    // Set the dirty bit
    handle->setDirty();

    // Done
    return true;
}


bool SlotFS::slotReadSeek(SlotHandle handle, uint32_t seekPos, SeekMode mode)
{
    // Simply exit if the slot is not open for reading
    if( !handle->openForRead() ) return false;

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

    // Update and check the seek position
    if(mode == SeekMode::Cur) seekPos = seekPos + handle->_curPtrSector * SectorSize + handle->_curPtrBytes;
    if(seekPos >= dataSizeStored) return false;
    if(mode == SeekMode::End) seekPos = dataSizeStored - 1 - seekPos;

    // Change the active slot
    if( !_changeActiveOpenSlotTo(handle) ) return false;

    // End streaming read as needed
    if( handle->_curPtrBytes && !handle->dirty() ) {
        if( _rmc.streamingReadEnd() != RMCResultCode::NoError ) return false;
    }

    // Reinitialize the handle
    handle->reinitializeRead(seekPos);

    // Done
    return true;
}


uint16_t SlotFS::slotReadGet(SlotHandle handle, uint8_t* data, uint16_t size)
{
    // Simply exit if the slot is not open for reading
    if( !handle->openForRead() ) return 0;

    // Check the size
    const uint32_t rba = slotReadRemainingBytesAvailable(handle);
    if(size > rba) size = rba;

    if(!size) return 0;

    // Change the active slot
    if( !_changeActiveOpenSlotTo(handle) ) return 0;

    // Check if the slot is dirty
    if( handle->dirty() ) {
        // If the current bytes pointer is not zero, we need to read the current sector and discard some bytes
        if(handle->_curPtrBytes) {
            // Begin streaming read and discard some bytes
            _rmc.streamingReadBegin(handle->_sectorStart + handle->_curPtrSector, 1);
            _rmc.streamingReadGet(nullptr, handle->_curPtrBytes);
        }
        // Clear the dirty bit
        handle->clearDirty();
    }

    // Read the bytes and update the handle state
    uint16_t remSize = size;

    while(remSize) {
        // Start a new streaming read operation as needed
        if( !handle->_curPtrBytes ) _rmc.streamingReadBegin(handle->_sectorStart + handle->_curPtrSector, 1);
        // Calculate the number of bytes that can be read form the current sector
        const uint16_t rlen = eCxx::min<uint16_t>(remSize, SectorSize - handle->_curPtrBytes);
        // Read the bytes
        _rmc.streamingReadGet(data, rlen);
        // Update the sector pointer and bytes pointer
        handle->_curPtrBytes += rlen;
        if(handle->_curPtrBytes == SectorSize) {
            handle->_curPtrSector++;
            handle->_curPtrBytes = 0;
        }
        // Update the data pointer and remaining size
        data    += rlen;
        remSize -= rlen;
    }

    // Done
    return size;
}


//
// ##### Slot write functions #####
//
SlotFS::SlotHandle SlotFS::slotWriteNew(const char* userID_, const char* userDesc_)
{
    char userID  [SDUserIDLen  ] = { 0 };
    char userDesc[SDUserDescLen] = { 0 };

    strncpy( userID,   userID_,   sizeof(userID  ) );
    strncpy( userDesc, userDesc_, sizeof(userDesc) );

    return slotWriteNew(userID, userDesc);
}


template <typename PGMPointerT>
inline SlotFS::SlotHandle SlotFS::_slotWriteNew(PGMPointerT userID_, PGMPointerT userDesc_)
{
    char userID  [SDUserIDLen  ] = { 0 };
    char userDesc[SDUserDescLen] = { 0 };

    PGMStrNCpy()( userID,   userID_.p,   sizeof(userID  ) );
    PGMStrNCpy()( userDesc, userDesc_.p, sizeof(userDesc) );

    return slotWriteNew(userID, userDesc);
}


SlotFS::SlotHandle SlotFS::slotWriteNew(PGMCharPointer userID, PGMCharPointer userDesc)
{ return _slotWriteNew(userID, userDesc); }


SlotFS::SlotHandle SlotFS::slotWriteNew(PGMCharPointerFar userID, PGMCharPointerFar userDesc)
{ return _slotWriteNew(userID, userDesc); }


SlotFS::SlotHandle SlotFS::slotWriteNew(const char (&userID)[SDUserIDLen], const char (&userDesc)[SDUserDescLen])
{
    // Simply exit if a streaming slot informational function is in progress
    if(_ssifCurSlotDesc) return nullptr;

    // Find an available slot handle
    SlotHandle handle = _findAvailableSlotHandle();
    if(!handle) return nullptr;

    // Change the active slot
    if( !_changeActiveOpenSlotTo(nullptr) ) return nullptr;

    // Find an empty slot descriptor
    uint16_t       slotNumber = InvalidSlotNumber;
    SlotDescriptor slotDescriptor;

    for(uint16_t i = SlotDescStart; i < SlotDescStart + _slotCount; ++i) {
        // Read the slot decriptor
        if( _rmc.readOneBlock(
                i, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
            ) != RMCResultCode::NoError
        ) return nullptr;
        // Check if it is empty
        if( slotDescriptor.slotType != SD_SlotEmpty) continue;
        slotNumber = i - SlotDescStart;
        break;
    }

    if(slotNumber == InvalidSlotNumber) return nullptr; // No empty slot was found

    // Modify the slot descriptor and write it back
    slotDescriptor.slotType = SD_SlotOccupied;
    slotDescriptor.dataSize = 0;
    memcpy(slotDescriptor.userID,   userID,   SDUserIDLen  );
    memcpy(slotDescriptor.userDesc, userDesc, SDUserDescLen);

    if( _rmc.writeOneBlock(
            slotNumber + SlotDescStart, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
        ) != RMCResultCode::NoError
    ) return nullptr;

    // Initialize the handle
    handle->initializeWrite(slotNumber, _slotSize, 0);

    // Return the handle
    return handle;
}


SlotFS::SlotHandle SlotFS::slotWriteOpenTruncate(uint16_t slotNumber, uint32_t truncatePos)
{
    // Simply exit if a streaming slot informational function is in progress
    if(_ssifCurSlotDesc) return nullptr;

    // Check the slot number
    if(slotNumber >= _slotCount) return nullptr;

    // Simply exit if the same slot is already open
    if( _slotAlreadyOpen(slotNumber) ) return nullptr;

    // Find an available slot handle
    SlotHandle handle = _findAvailableSlotHandle();
    if(!handle) return nullptr;

    // Change the active slot
    if( !_changeActiveOpenSlotTo(nullptr) ) return nullptr;

    // Read and check the slot descriptor
    SlotDescriptor slotDescriptor;

    if( _rmc.readOneBlock(
            slotNumber + SlotDescStart, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
        ) != RMCResultCode::NoError
    ) return nullptr;

    if(slotDescriptor.slotType != SD_SlotOccupied) return nullptr;

    // Check the truncate position
    if(truncatePos > slotDescriptor.dataSize) truncatePos = slotDescriptor.dataSize;

    // Modify the slot descriptor and write it back
    slotDescriptor.dataSize = truncatePos;

    if( _rmc.writeOneBlock(
            slotNumber + SlotDescStart, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
        ) != RMCResultCode::NoError
    ) return nullptr;

    // Initialize the handle
    handle->initializeWrite(slotNumber, _slotSize, slotDescriptor.dataSize);

    // Return the handle
    return handle;
}


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

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

    // Close the handle
    handle->close();

    // Done
    return true;
}


bool SlotFS::_slotWriteFlush(SlotHandle handle, bool noChangeActiveOpenSlot)
{
    // Change the active slot
    if(!noChangeActiveOpenSlot) {
        if( !_changeActiveOpenSlotTo(handle) ) return false;
    }

    // End streaming write as needed
    if( handle->_curPtrBytes && !handle->dirty() ) {
        if( _rmc.streamingWriteEnd() != RMCResultCode::NoError ) return false;
    }

    // Flush the data (update the slot descriptor and handle state)
    if( !_slotWriteFlush_impl(handle) ) return false;

    // Set the dirty bit
    handle->setDirty();

    // Done
    return true;
}


bool SlotFS::_slotWriteFlush_impl(SlotHandle handle)
{
    // Read the slot descriptor
    SlotDescriptor slotDescriptor;

    if( _rmc.readOneBlock(
            handle->slotNumber() + SlotDescStart, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
        ) != RMCResultCode::NoError
    ) return false;

    // Modify the slot descriptor and write it back
    slotDescriptor.dataSize = handle->_curPtrSector * SectorSize + handle->_curPtrBytes;

    if( _rmc.writeOneBlock(
            handle->slotNumber() + SlotDescStart, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
        ) != RMCResultCode::NoError
    ) return false;


    // Update the handle state
    handle->_dataSizeSector = handle->_curPtrSector;
    handle->_dataSizeBytes  = handle->_curPtrBytes;

    // Done
    return true;
}


uint16_t SlotFS::slotWritePut(SlotHandle handle, const uint8_t* data, uint16_t size)
{
    // Simply exit if the slot is not open for writing
    if( !handle->openForWrite() ) return 0;

    // Check the size
    const uint32_t rba = slotWriteRemainingBytesAvailable(handle);
    if(size > rba) size = rba;

    if(!size) return 0;

    // Change the active slot
    if( !_changeActiveOpenSlotTo(handle) ) return 0;

    // Check if the slot is dirty
    if( handle->dirty() ) {
        // If the current bytes pointer is not zero and the slot has some stored bytes already,
        // we need to read the current sector and rewrite some bytes
        if( handle->_curPtrBytes && (handle->_dataSizeSector || handle->_dataSizeBytes) ) {
            // Read the current sector
            uint8_t buff[SectorSize];
            _rmc.readOneBlock(handle->_sectorStart + handle->_curPtrSector, buff, SectorSize);
            // Begin streaming write and write back some bytes
            _rmc.streamingWriteBegin(handle->_sectorStart + handle->_curPtrSector, 1);
            _rmc.streamingWritePut(buff, handle->_curPtrBytes);
        }
        // Clear the dirty bit
        handle->clearDirty();
    }

    // Write the bytes and update the handle state
    uint16_t remSize = size;

    while(remSize) {
        // Start a new streaming write operation as needed
        if( !handle->_curPtrBytes ) {
            // Flush the data (update the slot descriptor and handle state) as needed
            if(handle->_curPtrSector - handle->_dataSizeSector >= 8) {
                if( !_slotWriteFlush_impl(handle) ) break;
            }
            // Start a new streaming write operation
            _rmc.streamingWriteBegin(handle->_sectorStart + handle->_curPtrSector, 1);
        }
        // Calculate the number of bytes that will fit in the current sector
        const uint16_t wlen = eCxx::min<uint16_t>(remSize, SectorSize - handle->_curPtrBytes);
        // Write the bytes
        _rmc.streamingWritePut(data, wlen);
        // Update the sector pointer and bytes pointer
        handle->_curPtrBytes += wlen;
        if(handle->_curPtrBytes == SectorSize) {
            handle->_curPtrSector++;
            handle->_curPtrBytes = 0;
        }
        // Update the data pointer and remaining size
        data    += wlen;
        remSize -= wlen;
    }

    // Done
    return size;
}


//
// ##### Slot managerial functions #####
//
bool SlotFS::freeSlot(uint16_t slotNumber)
{
    // Simply exit if there is any slot open for reading/writing
    if( _anyOpenSlot() ) return false;

    // Check the slot number
    if(slotNumber >= _slotCount) return false;

    // Read and check the slot descriptor
    SlotDescriptor slotDescriptor;

    if( _rmc.readOneBlock(
            slotNumber + SlotDescStart, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
        ) != RMCResultCode::NoError
    ) return false;

    if(slotDescriptor.slotType != SD_SlotOccupied) return false;

    // Modify the slot descriptor and write it back
    slotDescriptor.slotType = SD_SlotEmpty;

    if( _rmc.writeOneBlock(
            slotNumber + SlotDescStart, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
        ) != RMCResultCode::NoError
    ) return false;

    // Done
    return true;
}


bool SlotFS::freeSlots(const uint16_t* slotNumber, uint8_t count)
{
    // Simply exit if there is any slot open for reading/writing
    if( _anyOpenSlot() ) return false;

    // Free the slots
    for(uint8_t i = 0; i < count; ++i) freeSlot(*slotNumber++);

    // Done
    return true;
}


bool SlotFS::slotChangeUserID(uint16_t slotNumber, const char* userID_)
{
    char userID[SDUserIDLen] = { 0 };

    strncpy( userID, userID_, sizeof(userID) );

    return slotChangeUserID(slotNumber, userID);
}


template <typename PGMPointerT>
inline bool SlotFS::_slotChangeUserID(uint16_t slotNumber, PGMPointerT userID_)
{
    char userID[SDUserIDLen] = { 0 };

    PGMStrNCpy()( userID, userID_.p, sizeof(userID) );

    return slotChangeUserID(slotNumber, userID);
}


bool SlotFS::slotChangeUserID(uint16_t slotNumber, PGMCharPointer userID)
{ return _slotChangeUserID(slotNumber, userID); }


bool SlotFS::slotChangeUserID(uint16_t slotNumber, PGMCharPointerFar userID)
{ return _slotChangeUserID(slotNumber, userID); }


bool SlotFS::slotChangeUserID(uint16_t slotNumber, const char (&userID)[SDUserIDLen])
{
    // Simply exit if there is any slot open for reading/writing
    if( _anyOpenSlot() ) return false;

    // Check the slot number
    if(slotNumber >= _slotCount) return false;

    // Read and check the slot descriptor
    SlotDescriptor slotDescriptor;

    if( _rmc.readOneBlock(
            slotNumber + SlotDescStart, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
        ) != RMCResultCode::NoError
    ) return false;

    if(slotDescriptor.slotType != SD_SlotOccupied) return false;

    // Modify the slot descriptor and write it back
    memcpy(slotDescriptor.userID, userID, SDUserIDLen);

    if( _rmc.writeOneBlock(
            slotNumber + SlotDescStart, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
        ) != RMCResultCode::NoError
    ) return false;

    // Done
    return true;
}


bool SlotFS::slotChangeUserDesc(uint16_t slotNumber, const char* userDesc_)
{
    char userDesc[SDUserDescLen] = { 0 };

    strncpy( userDesc, userDesc_, sizeof(userDesc) );

    return slotChangeUserDesc(slotNumber, userDesc);
}


template <typename PGMPointerT>
inline bool SlotFS::_slotChangeUserDesc(uint16_t slotNumber, PGMPointerT userDesc_)
{
    char userDesc[SDUserDescLen] = { 0 };

    PGMStrNCpy()( userDesc, userDesc_.p, sizeof(userDesc) );

    return slotChangeUserDesc(slotNumber, userDesc);
}


bool SlotFS::slotChangeUserDesc(uint16_t slotNumber, PGMCharPointer userDesc)
{ return _slotChangeUserDesc(slotNumber, userDesc); }


bool SlotFS::slotChangeUserDesc(uint16_t slotNumber, PGMCharPointerFar userDesc)
{ return _slotChangeUserDesc(slotNumber, userDesc); }


bool SlotFS::slotChangeUserDesc(uint16_t slotNumber, const char (&userDesc)[SDUserDescLen])
{
    // Simply exit if there is any slot open for reading/writing
    if( _anyOpenSlot() ) return false;

    // Check the slot number
    if(slotNumber >= _slotCount) return false;

    // Read and check the slot descriptor
    SlotDescriptor slotDescriptor;

    if( _rmc.readOneBlock(
            slotNumber + SlotDescStart, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
        ) != RMCResultCode::NoError
    ) return false;

    if(slotDescriptor.slotType != SD_SlotOccupied) return false;

    // Modify the slot descriptor and write it back
    memcpy(slotDescriptor.userDesc, userDesc, SDUserDescLen);

    if( _rmc.writeOneBlock(
            slotNumber + SlotDescStart, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
        ) != RMCResultCode::NoError
    ) return false;

    // Done
    return true;
}


uint32_t SlotFS::getSlotDataSize(uint16_t slotNumber) const
{
    // Simply exit if there is any slot open for reading/writing
    if( _anyOpenSlot() ) return InvalidSlotDataSize;

    // Check the slot number
    if(slotNumber >= _slotCount) return false;

    // Read and check the slot descriptor
    SlotDescriptor slotDescriptor;

    if( _rmc.readOneBlock(
            slotNumber + SlotDescStart, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
        ) != RMCResultCode::NoError
    ) return 0;

    if(slotDescriptor.slotType != SD_SlotOccupied) return 0;

    // Return the data size
    return slotDescriptor.dataSize;
}


bool SlotFS::getSlotUserID(uint16_t slotNumber, char (&userID)[SDUserIDLen]) const
{
    // Simply exit if there is any slot open for reading/writing
    if( _anyOpenSlot() ) return false;

    // Check the slot number
    if(slotNumber >= _slotCount) return false;

    // Read and check the slot descriptor
    SlotDescriptor slotDescriptor;

    if( _rmc.readOneBlock(
            slotNumber + SlotDescStart, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
        ) != RMCResultCode::NoError
    ) return false;

    if(slotDescriptor.slotType != SD_SlotOccupied) return false;

    // Copy the userID
    memcpy(userID, slotDescriptor.userID, SDUserIDLen);

    // Done
    return true;
}


bool SlotFS::getSlotUserDesc(uint16_t slotNumber, char (&userDesc)[SDUserDescLen]) const
{
    // Simply exit if there is any slot open for reading/writing
    if( _anyOpenSlot() ) return false;

    // Check the slot number
    if(slotNumber >= _slotCount) return false;

    // Read and check the slot descriptor
    SlotDescriptor slotDescriptor;

    if( _rmc.readOneBlock(
            slotNumber + SlotDescStart, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
        ) != RMCResultCode::NoError
    ) return false;

    if(slotDescriptor.slotType != SD_SlotOccupied) return false;

    // Copy the user description
    memcpy(userDesc, slotDescriptor.userDesc, SDUserDescLen);

    // Done
    return true;
}


//
// ##### Slot informational functions #####
//
uint16_t SlotFS::numberOfEmptySlots() const
{
    // Simply exit if there is any slot open for reading/writing
    if( _anyOpenSlot() ) return 0;

    // Read within the slot descriptors
    uint16_t       slotCount = 0;
    SlotDescriptor slotDescriptor;

    for(uint16_t i = SlotDescStart; i < SlotDescStart + _slotCount; ++i) {
        if( _rmc.readOneBlock(
                i, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
            ) != RMCResultCode::NoError
        ) return 0;

        if(slotDescriptor.slotType == SD_SlotEmpty) ++slotCount;
    }

    // Return the number of empty slots
    return slotCount;
}


uint16_t SlotFS::numberOfOccupiedSlots() const
{
    // Simply exit if there is any slot open for reading/writing
    if( _anyOpenSlot() ) return 0;

    // Read within the slot descriptors
    uint16_t       slotCount = 0;
    SlotDescriptor slotDescriptor;

    for(uint16_t i = SlotDescStart; i < SlotDescStart + _slotCount; ++i) {
        if( _rmc.readOneBlock(
                i, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
            ) != RMCResultCode::NoError
        ) return 0;

        if(slotDescriptor.slotType == SD_SlotOccupied) ++slotCount;
    }

    // Return the number of occupied slots
    return slotCount;
}


bool SlotFS::listOccupiedSlotNumberBegin() const
{
    // Simply exit if there is any slot open for reading/writing
    if( _anyOpenSlot() ) return false;

    // Simply exit if there is any streaming slot informational function in progress
    if(_ssifCurSlotDesc) return false;

    // Set variable
    _ssifCurSlotDesc = SlotDescStart;

    // Done
    return true;
}


uint16_t SlotFS::listOccupiedSlotNumberGet() const
{
    // Simply exit if there is no streaming slot informational function in progress
    if(!_ssifCurSlotDesc) return InvalidSlotNumber;

    // Loop until an occupied slot descriptor is found
    while(_ssifCurSlotDesc < SlotDescStart + _slotCount) {
        // Read and check the slot descriptor
        SlotDescriptor slotDescriptor;
        if( _rmc.readOneBlock(
                _ssifCurSlotDesc, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
            ) != RMCResultCode::NoError
        ) {
            // Reset the variable on error
            _ssifCurSlotDesc = 0;
            return InvalidSlotNumber;
        }
        // Save the current slot descriptor and increment the counter
        const uint16_t curSlotDesc = _ssifCurSlotDesc;
        ++_ssifCurSlotDesc;
        // Return the slot number if it is occupied
        if(slotDescriptor.slotType == SD_SlotOccupied) return curSlotDesc - SlotDescStart;
    }

    // Check if all slot descriptors have been processed
    if(_ssifCurSlotDesc == SlotDescStart + _slotCount) _ssifCurSlotDesc = 0;

    // No more occupied slot found
    return InvalidSlotNumber;
}


bool SlotFS::listOccupiedSlotNumberEnd() const
{
    // Simply exit if there is no streaming slot informational function in progress
    if(!_ssifCurSlotDesc) return false;

    // Simply reset the variable
    _ssifCurSlotDesc = 0;

    // Done
    return true;
}


uint16_t SlotFS::findFirstSlotNumberByUserID(const char* userID_, uint16_t slotNumberStart) const
{
    char userID[SDUserIDLen] = { 0 };

    strncpy( userID, userID_, sizeof(userID) );

    return findFirstSlotNumberByUserID(userID, slotNumberStart);
}


template <typename PGMPointerT>
inline uint16_t SlotFS::_findFirstSlotNumberByUserID(PGMPointerT userID_, uint16_t slotNumberStart) const
{
    char userID[SDUserIDLen] = { 0 };

    PGMStrNCpy()( userID, userID_.p, sizeof(userID) );

    return findFirstSlotNumberByUserID(userID, slotNumberStart);
}


uint16_t SlotFS::findFirstSlotNumberByUserID(PGMCharPointer userID, uint16_t slotNumberStart) const
{ return _findFirstSlotNumberByUserID(userID, slotNumberStart); }


uint16_t SlotFS::findFirstSlotNumberByUserID(PGMCharPointerFar userID, uint16_t slotNumberStart) const
{ return _findFirstSlotNumberByUserID(userID, slotNumberStart); }


uint16_t SlotFS::findFirstSlotNumberByUserID(const char (&userID)[SDUserIDLen], uint16_t slotNumberStart) const
{
    // Simply exit if there is any slot open for reading/writing
    if( _anyOpenSlot() ) return InvalidSlotNumber;

    // Search within the slot descriptors
    SlotDescriptor slotDescriptor;

    for(uint16_t i = SlotDescStart + slotNumberStart; i < SlotDescStart + _slotCount; ++i) {
        // Read the slot decriptor
        if( _rmc.readOneBlock(
                i, reinterpret_cast<uint8_t*>(&slotDescriptor), sizeof(slotDescriptor)
            ) != RMCResultCode::NoError
        ) return InvalidSlotNumber;
        // Check if it a match
        if( slotDescriptor.slotType == SD_SlotEmpty ) continue;
        if( memcmp(slotDescriptor.userID, userID, SDUserIDLen) != 0 ) continue;
        return i - SlotDescStart;
    }

    // Not found
    return InvalidSlotNumber;
}


// =================================================================================================
// =================================================================================================
// =================================================================================================


SlotFS::RMCResultCode SlotFS::_clearSlotDescriptors(uint16_t slotCount_, FormatProgressCB progressCB)
{
    // Write zeroes to the sectors
    uint16_t slotStart = SlotDescStart;
    uint16_t slotCount = slotCount_ ? slotCount_ : SlotMaxCount;
    uint8_t  slotInc   = (slotCount >= 32) ? 32 : slotCount;

    while(slotCount) {
        // Determine the number of sector to be written
        const uint16_t numSector = eCxx::min<uint16_t>(slotInc, slotCount);
        // Begin streaming write
        RMCResultCode rc = _rmc.streamingWriteBegin(slotStart, numSector);
        if(rc != RMCResultCode::NoError) return rc;
        // End streaming write immediately (so all the bytes will be written with zeroes)
        rc = _rmc.streamingWriteEnd();
        if(rc != RMCResultCode::NoError) return rc;
        // Update variables
        slotStart += numSector;
        slotCount -= numSector;
        // Call the user callback for every iteration (if supplied)
        if(progressCB) progressCB(slotStart);
    }

    // Call the user callback for the final time (if supplied)
    if(progressCB) progressCB(0xFFFF);

    // Done
    return RMCResultCode::NoError;
}


uint16_t SlotFS::_matchSlotCount(uint16_t& slotSize, uint32_t scaledSectorCount, uint16_t minimumSlotCount, uint16_t maximumSlotCount, bool revSearch)
{
    // Set the slot size to zero first
    slotSize = 0;

    // Ensure the slot count is not too large
    if(maximumSlotCount > SlotFS::SlotMaxCount) maximumSlotCount = SlotFS::SlotMaxCount;

    // Read-word functor
    const PGMReadWord functor;

    // Search within the list of valid slot size
    for(uint8_t i = 0; i < eCxx::arraySize(SlotFS::ValidSlotSize); ++i) {
        // Forward search or reverse search
        const uint16_t mult = functor(
                                  &SlotFS::ValidSlotSize[
                                      revSearch ? (eCxx::arraySize(SlotFS::ValidSlotSize) - 1 - i)
                                                : i
                                  ]
                              );
        // Calculate the current slot count
        const uint16_t slotCount = scaledSectorCount / mult;
        // Skip this slot size because it either produces no slot or too many slots
        if(!slotCount || slotCount > maximumSlotCount) continue;
        // If match is found, return the current slot size and slot count
        if(slotCount >= minimumSlotCount) {
            slotSize = mult;
            return slotCount;
        }
    }

    // No match
    return 0;
}


} // namespace eCxx
