/***************************** LittleFS Wrapper *******************************
This file is part of the pdi stack.

This is free software. you can redistribute it and/or modify it but without any
warranty.

This file provides the implementation of the LittleFSWrapper class, which 
bridges the LittleFS library with the iStorageInterface abstraction.

Author          : Suraj I.
created Date    : 6th Apr 2025
******************************************************************************/

#include "LittleFSWrapper.h"

/**
 * @brief Constructor to initialize the LittleFSWrapper.
 * @param storage Reference to an iStorageInterface implementation.
 * @param defaultConfig Flag to use default configuration.
 *
 * This constructor initializes the LittleFS configuration, mounts the file
 * system, and formats it if mounting fails.
 */
LittleFSWrapper::LittleFSWrapper(iStorageInterface& storage, bool defaultConfig)
    : iFileSystemInterface(storage) {
    if (defaultConfig) {
        initLFSConfig();
    }
}

/**
 * @brief Destructor to unmount the LittleFS file system.
 */
LittleFSWrapper::~LittleFSWrapper() {
    lfs_unmount(&m_lfs);
}

/**
 * @brief Initialize and mount the file system.
 * @param lfscnfg Pointer to the LittleFS configuration.
 * @return 0 on success, or a negative error code on failure.
 */
int LittleFSWrapper::initLFSConfig(lfs_config *lfscnfg)
{
    memset(&m_lfs, 0, sizeof(m_lfs));
    memset(&m_lfscfg, 0, sizeof(m_lfscfg));

    // Initialize LittleFS configuration
    m_lfscfg.read = &LittleFSWrapper::readCallback;
    m_lfscfg.prog = &LittleFSWrapper::progCallback;
    m_lfscfg.erase = &LittleFSWrapper::eraseCallback;
    m_lfscfg.sync = &LittleFSWrapper::syncCallback;

    if (nullptr != lfscnfg) {
        m_lfscfg.read_size = lfscnfg->read_size; // Minimum read size
        m_lfscfg.prog_size = lfscnfg->prog_size; // Minimum program size
        m_lfscfg.block_size = lfscnfg->block_size; // Block size
        m_lfscfg.cache_size = lfscnfg->cache_size; // Cache size
        m_lfscfg.lookahead_size = lfscnfg->lookahead_size; // Lookahead buffer size
        m_lfscfg.block_cycles = lfscnfg->block_cycles; // Number of erase cycles before wear leveling
    } else {
        // very minimal default sizes to start with
        m_lfscfg.read_size = 8; // Minimum read size (adjust as needed)
        m_lfscfg.prog_size = 8; // Minimum program size (adjust as needed)
        m_lfscfg.block_size = 32; // Block size (adjust as needed)
        m_lfscfg.cache_size = 8; // Cache size (adjust as needed)
        m_lfscfg.lookahead_size = 8; // Lookahead buffer size (adjust as needed)
        m_lfscfg.block_cycles = 500; // Number of erase cycles before wear leveling    
    }
    m_lfscfg.block_count = m_istorage.size() / m_lfscfg.block_size;

    m_lfscfg.context = this; // Context for lfs storage operations

    // Mount the file system
    int ret = lfs_mount(&m_lfs, &m_lfscfg);
    if (ret != LFS_ERR_OK) {
        // Format and mount if mounting fails
        lfs_format(&m_lfs, &m_lfscfg);
        ret = lfs_mount(&m_lfs, &m_lfscfg);
    }

    return ret; // Success
}

/**
 * @brief Creates a file and writes content to it.
 * @param path The path of the file to create.
 * @param content The content to write to the file.
 * @param size The size of the content to write. Default is -1 for full content.
 * @return The number of bytes written, or -1 on failure.
 */
int LittleFSWrapper::createFile(const char* path, const char* content, int64_t size) {
    lfs_file_t file;
    int fileOpenOrErr = lfs_file_open(&m_lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC | LFS_O_EXCL);
    if (fileOpenOrErr < 0) {
        return fileOpenOrErr; // Failed to create file
    }
    int bytesWrittenOrErr = lfs_file_write(&m_lfs, &file, content, (size == -1) ? strlen(content) : size);
    lfs_file_close(&m_lfs, &file);
    return bytesWrittenOrErr;
}

/**
 * @brief Edit content to a file.
 * @param path The path of the file to write to.
 * @param offset Offset from where to modify the file content.
 * @param content The content to write at offset.
 * @param size The size of the content to write.
 * @return The number of bytes written, or -1 on failure.
 */
int LittleFSWrapper::editFile(const char* path, uint64_t offset, const char* content, uint32_t size) {

    if( size == 0 ){
        return 0; // Nothing to write
    }

    lfs_file_t file;
    int fileOpenOrErr = lfs_file_open(&m_lfs, &file, path, LFS_O_WRONLY);
    if (fileOpenOrErr < 0) {
        return fileOpenOrErr; // Failed to open file
    }

    int64_t filesize = lfs_file_size(&m_lfs, &file);

    if (offset > (uint64_t)filesize) {
        // Move to end of file
        lfs_file_seek(&m_lfs, &file, 0, LFS_SEEK_END);

        uint8_t pad_buf[64]; // small buffer
        memset(pad_buf, 0, sizeof(pad_buf)); // fill with zeros

        uint64_t gap = offset - filesize;
        while (gap > 0) {
            uint32_t chunk = (gap > sizeof(pad_buf)) ? sizeof(pad_buf) : gap;
            int written = lfs_file_write(&m_lfs, &file, pad_buf, chunk);
            if (written <= 0) { // error or no progress
                lfs_file_close(&m_lfs, &file);
                return (written == 0) ? -3 : written;
            }
            gap -= written;
        }
    }

    // Move to the specified offset
    if (lfs_file_seek(&m_lfs, &file, offset, LFS_SEEK_SET) < 0) {
        lfs_file_close(&m_lfs, &file);
        return -1; // Failed to seek to offset
    }

    int bytesWrittenOrErr = lfs_file_write(&m_lfs, &file, content, size);
    lfs_file_close(&m_lfs, &file);

    if (bytesWrittenOrErr > 0 && (uint32_t)bytesWrittenOrErr != size) {
        return -2; // Partial write
    }
    
    return bytesWrittenOrErr;
}

/**
 * @brief Writes content to a file.
 * @param path The path of the file to write to.
 * @param content The content to write to the file.
 * @param size The size of the content to write.
 * @param append Whether to append to the file or overwrite it. Default is false (overwrite).
 * @return The number of bytes written, or -1 on failure.
 */
int LittleFSWrapper::writeFile(const char *path, const char *content, uint32_t size, bool append){
    if( size == 0 ){
        return 0;
    }

    lfs_file_t file;
    int fileOpenOrErr = lfs_file_open(&m_lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT | (append ? LFS_O_APPEND : LFS_O_TRUNC) );
    if (fileOpenOrErr < 0) {
        return fileOpenOrErr; // Failed to create file
    }
    int bytesWrittenOrErr = lfs_file_write(&m_lfs, &file, content, size);
    lfs_file_close(&m_lfs, &file);
    return bytesWrittenOrErr;
}

/**
 * @brief Reads content from a file.
 * @param path The path of the file to read.
 * @param size The maximum number of bytes to read in one loop.
 * @param readbackfn callback function for readback.
 * @param offset Offset from where to read the file content.
 * @param readUntilMatchStr Pointer to the sring match to read until.
 * @param didmatchfound Optional pointer to a boolean that will be set to true if the match string was found.
 * @return The number of bytes read, or -1 on failure.
 */
int LittleFSWrapper::readFile(const char* path, uint64_t size, pdiutil::function<bool(char *, uint32_t)> readbackfn, uint64_t offset, const char* readUntilMatchStr, bool *didmatchfound) {
    lfs_file_t file;
    int32_t okOrErr = lfs_file_open(&m_lfs, &file, path, LFS_O_RDONLY);
    if (okOrErr < 0) {
        return okOrErr; // Failed to open file
    }

    char *matchstrtempbuffer = nullptr;
    uint16_t matchstrlen = 0;
    if( nullptr != readUntilMatchStr ) {

        matchstrlen = strlen(readUntilMatchStr);

        if( matchstrlen > 0 ){
            
            matchstrtempbuffer = new char[matchstrlen + 1];
            memset(matchstrtempbuffer, 0, matchstrlen + 1);
        }
    }    

    // Buffer to store file content
    char buffer[size];
    memset(buffer, 0, size);
    int bytesReadOrErr = 0;

    if (nullptr != readbackfn) {

        lfs_size_t filesize = lfs_file_size(&m_lfs, &file);
        if( offset < filesize ){
            filesize -= offset;
            lfs_file_seek(&m_lfs, &file, offset, LFS_SEEK_SET); 
        } else {
            filesize = 0;
        }

        for (lfs_size_t i = 0; i < filesize; i += size) {

            lfs_size_t chunk = lfs_min(size, filesize - i);
            
            okOrErr = lfs_file_read(&m_lfs, &file, buffer, chunk);
            if (okOrErr < 0) {
                bytesReadOrErr = okOrErr; // Failed to read file
                break; // Failed to read file
            }

            chunk = okOrErr;
            
            bool matchfound = false;
            if( nullptr != matchstrtempbuffer ) {

                
                for (uint16_t j = 0; j < chunk; j++){

                    // Shift left by 1
                    memmove(matchstrtempbuffer, matchstrtempbuffer + 1, matchstrlen - 1);
                    matchstrtempbuffer[matchstrlen - 1] = buffer[j];

                    // Check if the read data contains the match string
                    int matchPos = __strstr(matchstrtempbuffer, readUntilMatchStr, matchstrlen);
                    if (matchPos != -1) {
                        // Found the match string, adjust chunk size to exclude it
                        chunk = matchPos > 0 ? matchPos - 1 : matchPos;
                        chunk += j;
                        // Call the readback function with the data up to the match
                        if(chunk > 0) {
                            readbackfn(buffer, chunk);
                        }
                        // Stop reading further
                        matchfound = true;
                        break;
                    }
                }
            }

            bytesReadOrErr += chunk;

            if( matchfound == true ){
                if( nullptr != didmatchfound ){
                    *didmatchfound = true;
                }
                break; // exit the for loop
            }

            // Call the readback function with the read data. break if callback returns false
            if(readbackfn(buffer, chunk) == false) {
                break;
            }
        }
    }

    if( nullptr != matchstrtempbuffer ){
        delete[] matchstrtempbuffer;
        matchstrtempbuffer = nullptr;
    }

    lfs_file_close(&m_lfs, &file);
    return bytesReadOrErr;
}

/**
 * @brief Set a custom attribute on a file or directory.
 * @param path The path of the file or directory.
 * @param type User-defined attribute identifier (0-255).
 * @param buffer Pointer to the attribute data to write.
 * @param size Size of the attribute data in bytes.
 * @return 0 on success, or a negative error code on failure.
 */
int LittleFSWrapper::setFileAttr(const char *path, uint8_t type, const void *buffer, uint32_t size){
    return lfs_setattr(&m_lfs, path, type, buffer, (lfs_size_t)size);
}

/**
 * @brief Get a custom attribute from a file or directory.
 * @param path The path of the file or directory.
 * @param type User-defined attribute identifier (0-255).
 * @param buffer Pointer to the buffer to receive the attribute data.
 * @param size Capacity of the buffer in bytes.
 * @return The size of the attribute on success, or a negative error code on failure.
 */
int LittleFSWrapper::getFileAttr(const char *path, uint8_t type, void *buffer, uint32_t size){
    return lfs_getattr(&m_lfs, path, type, buffer, (lfs_size_t)size);
}

/**
 * @brief Remove a custom attribute from a file or directory.
 * @param path The path of the file or directory.
 * @param type User-defined attribute identifier (0-255).
 * @return 0 on success, or a negative error code on failure.
 */
int LittleFSWrapper::removeFileAttr(const char *path, uint8_t type){
    return lfs_removeattr(&m_lfs, path, type);
}

/**
 * @brief Find the string in file.
 * @param path The path of the file to find in.
 * @param findStr Pointer to the find sring.
 * @param findindices A vector to store the indices of found occurrences.
 * @param maxindices Maximum indices of found occurrences. default unlimited i.e. -1.
 * @param everynthindice Get only nth indices of found occurrences. default every i.e. 1.
 * @param offset Optional Offset from where to read the file content.
 * @param yield Optional callback function to yield control during long operations.
 * @return The number of finding, or -1 on failure.
 */
int LittleFSWrapper::findInFile(const char* path, const char* findStr, pdiutil::vector<uint32_t> *findindices, int maxindices, int everynthindice, int64_t offset, CallBackVoidArgFn yield){

    int foundcount = 0;
    if(findindices) findindices->clear();

    if( nullptr != findStr ){

        int filesize = getFileSize(path);
        if(filesize < 0) return filesize;
        
        int bytesReadOrErr = 0;
        int64_t endoffset = (offset < 0) ? -offset : -1;
        uint64_t offsetindex = (offset < 0) ? 0 : offset;
        int findstrlen = strlen(findStr);
        bool didmatchfound = false;
        uint64_t chunksizetoread = (endoffset < 0 || endoffset > 250) ? 250 : (uint64_t)endoffset;
        if(maxindices > 0 && everynthindice > 1){
            maxindices = maxindices / everynthindice;
        }

        int nextPushAt = everynthindice;
        if(offset < 0 && everynthindice > 1){
            int foundmatches = findInFile(path, findStr, nullptr, -1, 1, offset, yield);
            nextPushAt = (foundmatches % everynthindice) + 1;
        }

        do{

            bytesReadOrErr = readFile(path, chunksizetoread, [&](char *data, uint32_t size) -> bool {
                // Continue reading
                return true;
            }, offsetindex, findStr, &didmatchfound);

            if( bytesReadOrErr < 0 ){
                return bytesReadOrErr; // error
            }

            offsetindex += bytesReadOrErr + 1; // move to next char to continue search
            if( didmatchfound ){
                didmatchfound = false; // reset for next read
                foundcount++;
                if(foundcount == nextPushAt){
                    if(findindices){
                        if(offset < 0 && maxindices > 0 && (int)findindices->size() >= maxindices){
                            findindices->erase(findindices->begin());
                        }
                        findindices->push_back(offsetindex - findstrlen); // store the index where found
                    }
                    nextPushAt += everynthindice;
                }
            }

            if( yield ){
                yield();
            }

            if( endoffset > 0 ){
                
                if( offsetindex > endoffset ){
                    break;
                }

                if( (offsetindex + chunksizetoread) > endoffset ){
                    chunksizetoread = endoffset - offsetindex + 1;
                }
            }
        }while( offsetindex < filesize && ( maxindices == -1 || offset < 0 || (findindices ? findindices->size() : foundcount) < maxindices  ) );
    }else{
        return -99;
    }

    return foundcount;
}

/**
 * @brief Find the offset in file if line number given.
 * @param path The path of the file to find in.
 * @param linenumber line number to count offset till.
 * @param yield Optional callback function to yield control during long operations.
 * @return The offset found, or -1 on failure.
 */
int64_t LittleFSWrapper::getOffsetFromLineNumber(const char* path, int linenumber, CallBackVoidArgFn yield){

    int64_t offset = 0;

    if( linenumber < 0 ){

        uint32_t totalnumberoflines = 0;
        int iStatus = readFile(path, 250, [&](char *data, uint32_t size)->bool{
            for(uint32_t i = 0; i < size; i++){
                if(data[i] == '\n') totalnumberoflines++;
            }
            return true;
        });
        if( iStatus < 0 ) return iStatus; // error

        int64_t fs = getFileSize(path);
        if( fs > 0 ){
            char lastChar = '\n';
            readFile(path, 1, [&](char *data, uint32_t size) -> bool {
                if(size > 0) lastChar = data[0];
                return true;
            }, (uint64_t)(fs - 1));
            if( lastChar != '\n' ){
                totalnumberoflines++;
            }
        }

        linenumber = totalnumberoflines + linenumber;
        if( linenumber < 0 ) return -1;
    }

    // Find the file content offset from line number offset
    if( linenumber > 0 ){

        int newlinesFound = 0;

        int iStatus = readFile(path, 250, [&](char *data, uint32_t size)->bool{

            for(uint32_t i = 0; i < size; i++){

                if(data[i] == '\n') {

                    newlinesFound++;

                    if(newlinesFound >= linenumber){

                        offset += i + 1;
                        return false;
                    }
                }
            }

            offset += size;

            if(yield){
                yield();
            }

            return true;
        });

        if( iStatus < 0 ) return iStatus; // error

        // int filesize = getFileSize(path);
        // if(filesize < 0){
        //     return filesize;
        // }

        // int newlinesFound = 0;
        // bool didmatchfound = false;

        // do
        // {
        //     int bytesReadOrErr = readFile(path, 250, [&](char *data, uint32_t size) -> bool {
        //         return true;
        //     }, offset, "\n", &didmatchfound);

        //     if(bytesReadOrErr < 0){
        //         return bytesReadOrErr;
        //     }

        //     offset += bytesReadOrErr + 1;
        //     if(didmatchfound){
        //         newlinesFound++;
        //         didmatchfound = false;
        //     }

        //     if(yield){
        //         yield();
        //     }
        // } while (offset < (uint64_t)filesize && newlinesFound < linenumber);
    }

    return offset;
}

/**
 * @brief Find the line in file if offset given.
 * @param path The path of the file to find in.
 * @param offset offset to find line number for.
 * @param yield Optional callback function to yield control during long operations.
 * @return The line number found, or -1 on failure.
 */
int64_t LittleFSWrapper::getLineNumberFromOffset(const char* path, int64_t offset, CallBackVoidArgFn yield){
    
    int64_t linenumber = 0;
    int64_t filesize = getFileSize(path);
    if( filesize < 0 ){
        return filesize; // error
    }

    if( offset < 0 ){
        offset = filesize + offset;
    }

    // Find the line number from offset
    if( offset > 0 ){

        int64_t offsetcount = 0;
        int iStatus = readFile(path, 250, [&](char *data, uint32_t size)->bool{

            for(uint32_t i = 0; i < size; i++){
                
                if(offset <= (offsetcount + i)){
                    return false;
                }

                if(data[i] == '\n') {
                    linenumber++;
                }
            }

            offsetcount += size;
            
            if(yield){
                yield();
            }

            return true;
        });
        if( iStatus < 0 ) return iStatus; // error

        // int64_t offsetcount = 0;
        // bool didmatchfound = false;

        // do
        // {
        //     int bytesReadOrErr = readFile(path, 250, [&](char *data, uint32_t size) -> bool {
        //         return true;
        //     }, offsetcount, "\n", &didmatchfound);

        //     if(bytesReadOrErr < 0){
        //         return bytesReadOrErr;
        //     }

        //     offsetcount += bytesReadOrErr + 1;
        //     if(didmatchfound){
        //         if((offsetcount - 1) < (uint64_t)offset){
        //             linenumber++;
        //         }else{
        //             break;
        //         }
        //         didmatchfound = false;
        //     }

        //     if(yield){
        //         yield();
        //     }
        // } while (offsetcount < (uint64_t)offset && offsetcount < (uint64_t)filesize);
    }

    return linenumber;
}

/**
 * @brief Find the number of lines in file.
 * @param path The path of the file to find in.
 * @param linenumberindices A vector to store the line numbers found.
 * @param maxlinenumbers Optional to provide max limit for line number indices to get in linenumbers vector.
 * @param linenumberoffset Offset from which line number to count.
 * @param yield Optional callback function to yield control during long operations.
 * @return The number of line found, or -1 on failure.
 */
int LittleFSWrapper::getLineNumbersInFile(const char* path, pdiutil::vector<uint32_t> &linenumberindices, int maxlinenumbers, int linenumberoffset, CallBackVoidArgFn yield){

    // Find the file content offset from line number offset
    int64_t offset = getOffsetFromLineNumber(path, linenumberoffset);

    if( offset < 0 ){
        offset = 0;
    }

    int status = findInFile(path, "\n", &linenumberindices, maxlinenumbers, 1, offset, yield);

    if( status < 0 ){
        return status; // error
    }

    if( linenumberindices.size() > 0 ){

        int64_t filesize = getFileSize(path);
        linenumberindices.insert(linenumberindices.begin(), (uint32_t)offset); // add start of first counted line as first index

        for (size_t i = 1; i < linenumberindices.size(); i++){
            linenumberindices[i] = linenumberindices[i] + 1; // move to next char after \n
        }  

        if( (linenumberindices.back() + 1) > (uint32_t)filesize ){
            linenumberindices.pop_back(); // remove last index if it exceeds file size
        }
    }

    return linenumberindices.size();
}

/**
 * @brief Read the line in file.
 * @param path The path of the file.
 * @param linenumber A line number to read. line number starts from 0. Supports negative line number to read from end.
 * @param linedata A string to store the line data found.
 * @param pattern Optional pattern to match in the line.
 * @param yield Optional callback function to yield control during long operations.
 * @return number of bytes read, or -1 on failure.
 */
int LittleFSWrapper::readLineInFile(const char* path, int32_t linenumber, pdiutil::string &linedata, const char* pattern, CallBackVoidArgFn yield){

    linedata.clear();

    int64_t filesize = getFileSize(path);
    if(filesize < 0) return (int)filesize;

    int64_t lineoffset = -1;

    if(pattern == nullptr){

        lineoffset = getOffsetFromLineNumber(path, linenumber, yield);
        if(lineoffset < 0) return (int)lineoffset;
        if(lineoffset >= filesize) return -99;

    }else{

        const int CAP = (linenumber > 0) ? linenumber : ((linenumber < 0) ? -linenumber : 1);

        pdiutil::vector<uint32_t> patternindices;
        int rc = findInFile(path, pattern, &patternindices, CAP, CAP,
                            (linenumber < 0) ? -filesize : 0, yield);
        if(rc < 0) return rc;
        if(patternindices.size() == 0) return -99;

        int64_t anchorLine = getLineNumberFromOffset(path, patternindices[0], yield);
        if(anchorLine < 0) return -99;

        const int WINDOW = 50;
        int windowStartLine = (int)anchorLine - WINDOW;
        if(windowStartLine < 0) windowStartLine = 0;

        pdiutil::vector<uint32_t> linenumbersvec;
        rc = getLineNumbersInFile(path, linenumbersvec, 2 * WINDOW + 1, windowStartLine, yield);
        if(rc < 0) return rc;
        if(linenumbersvec.size() == 0) return -99;

        pdiutil::vector<uint32_t> winpatternindices;
        rc = findInFile(path, pattern, &winpatternindices, -1, 1, (int64_t)linenumbersvec[0], yield);
        if(rc < 0) return rc;

        pdiutil::vector<uint32_t> filtered;
        for(size_t i = 0; i < linenumbersvec.size(); i++){
            uint32_t lineStart = linenumbersvec[i];
            uint32_t lineEnd = (i+1 < linenumbersvec.size()) ? linenumbersvec[i+1] : (uint32_t)filesize;
            for(size_t j = 0; j < winpatternindices.size(); j++){
                if(winpatternindices[j] >= lineStart && winpatternindices[j] < lineEnd){
                    filtered.push_back(lineStart);
                    break;
                }
            }
        }

        if(filtered.size() == 0) return -99;

        int32_t idx = (linenumber < 0) ? ((int32_t)filtered.size() + linenumber) : linenumber;
        if(idx < 0 || (size_t)idx >= filtered.size()) return -99;

        lineoffset = (int64_t)filtered[idx];
        if(lineoffset >= filesize) return -99;
    }

    int bytesReadedOrError = readFile(path, 250, [&](char *data, uint32_t size) -> bool {
        linedata += pdiutil::string(data, size);
        return true;
    }, (uint64_t)lineoffset, "\n");

    if( !linedata.empty() && linedata.back() == '\r' ){
        linedata.pop_back();
        bytesReadedOrError--;
    }

    return bytesReadedOrError;
}


/**
 * @brief Creates a directory.
 * @param path The path of the directory to create.
 * @return 0 on success, or a negative error code on failure.
 */
int LittleFSWrapper::createDirectory(const char* path) {
    if (!path || path[0] == '\0') return LFS_ERR_INVAL;

    char temp[LFS_NAME_MAX*2]; // Adjust size as needed
    size_t len = strlen(path);
    if (len >= sizeof(temp)) return LFS_ERR_NAMETOOLONG;

    int res = 0;
    size_t i = 0;
    size_t last = 0;

    // Skip leading slash
    if (path[0] == '/') last = 1;

    while (i <= len) {
        if (path[i] == '/' || path[i] == '\0') {
            if (i > last) {
                memcpy(temp, path, i);
                temp[i] = '\0';
                res = lfs_mkdir(&m_lfs, temp);
                if (res != 0 && res != LFS_ERR_EXIST) return res;
            }
            last = i + 1;
        }
        i++;
    }
    return 0;
}

/**
 * @brief Deletes a directory.
 * @param path The path of the directory to delete.
 * @return 0 on success, or a negative error code on failure.
 */
int LittleFSWrapper::deleteDirectory(const char* path) {
    lfs_dir_t dir;
    lfs_info info;

    int dirOpenOrErr = lfs_dir_open(&m_lfs, &dir, path);
    if (dirOpenOrErr < 0) {
        return dirOpenOrErr; // Failed to open directory
    }

    while (lfs_dir_read(&m_lfs, &dir, &info) > 0) {
        // Skip "." and ".."
        if (strcmp(info.name, ".") == 0 || strcmp(info.name, "..") == 0) continue;

        // Build full child path
        char childPath[256];
        snprintf(childPath, sizeof(childPath), "%s/%s", path, info.name);

        if (info.type == LFS_TYPE_DIR) {
            // Recursively delete subdirectory
            int res = deleteDirectory(childPath);
            if (res < 0) {
                lfs_dir_close(&m_lfs, &dir);
                return res;
            }
        } else {
            // Delete file
            int res = lfs_remove(&m_lfs, childPath);
            if (res < 0) {
                lfs_dir_close(&m_lfs, &dir);
                return res;
            }
        }
    }
    lfs_dir_close(&m_lfs, &dir);

    // Now delete the (now empty) directory itself
    return lfs_remove(&m_lfs, path);
}

/**
 * @brief Renames a file or directory.
 * @param oldPath The current path of the file or directory.
 * @param newPath The new path of the file or directory.
 * @return 0 on success, or a negative error code on failure.
 */
int LittleFSWrapper::rename(const char* oldPath, const char* newPath) {
    return lfs_rename(&m_lfs, oldPath, newPath);
}

/**
 * @brief Copies a file to a new path.
 * @param sourcePath The path of the source file.
 * @param destPath The path of the destination file.
 * @return 0 on success, or a negative error code on failure.
 */
int LittleFSWrapper::copyFile(const char* sourcePath, const char* destPath) {
    // Buffer to store file content
    char buffer[m_lfscfg.read_size];
    memset(buffer, 0, sizeof(buffer));

    lfs_file_t sourceFile;
    int fileOpenOrErr = lfs_file_open(&m_lfs, &sourceFile, sourcePath, LFS_O_RDONLY);
    if (fileOpenOrErr < 0) {
        return fileOpenOrErr; // Failed to open source file
    }

    lfs_file_t destFile;
    fileOpenOrErr = lfs_file_open(&m_lfs, &destFile, destPath, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC);
    if (fileOpenOrErr < 0) {
        return fileOpenOrErr; // Failed to open/create file
    }

    // Copy the content from source file to destination file
    lfs_size_t filesize = lfs_file_size(&m_lfs, &sourceFile); 
    for (lfs_size_t i = 0; i < filesize; i += m_lfscfg.read_size) {
        lfs_size_t chunk = lfs_min(m_lfscfg.read_size, filesize - i);
        lfs_file_read(&m_lfs, &sourceFile, buffer, chunk);
        lfs_file_write(&m_lfs, &destFile, buffer, chunk);
    }

    // Close the files
    lfs_file_close(&m_lfs, &sourceFile);
    lfs_file_close(&m_lfs, &destFile);

    return LFS_ERR_OK; // Success
}

/**
 * @brief Deletes a file.
 * @param path The path of the file to delete.
 * @return 0 on success, or a negative error code on failure.
 */
int LittleFSWrapper::deleteFile(const char *path){
    return lfs_remove(&m_lfs, path);
}

/**
 * @brief Moves a file to a new path.
 * @param oldPath The current path of the file.
 * @param newPath The new path of the file.
 * @return 0 on success, or a negative error code on failure.
 */
int LittleFSWrapper::moveFile(const char *oldPath, const char *newPath){
    return lfs_rename(&m_lfs, oldPath, newPath);
}

/**
 * @brief Gets the size of a file.
 * @param path The path of the file.
 * @return The size of the file in bytes, or -1 on failure.
 */
int64_t LittleFSWrapper::getFileSize(const char *path) {
    lfs_file_t file;
    int fileOpenOrErr = lfs_file_open(&m_lfs, &file, path, LFS_O_RDONLY);
    if (fileOpenOrErr < 0) {
        return fileOpenOrErr; // Failed to open file
    }    
    int64_t size = lfs_file_size(&m_lfs, &file);
    lfs_file_close(&m_lfs, &file);
    return size;
}

/**
 * @brief Get the list of files in a provided path. 
 *        Please deallocate dirs & files char* after use.
 * @param path The path of the directory to list.
 * @param items A vector to store the file information.
 * @param pattern Optional pattern to filter files.
 * @return 0 on success, or negative on failure.
 */
int LittleFSWrapper::getDirFileList(const char *path, pdiutil::vector<file_info_t>& items, const char* pattern)
{
    lfs_dir_t dir;
    lfs_info info;
    
    int dirOpenOrErr = lfs_dir_open(&m_lfs, &dir, path);
    if (dirOpenOrErr < 0) {
        return dirOpenOrErr; // Failed to open directory
    }    

    while (lfs_dir_read(&m_lfs, &dir, &info) > 0) {

        char *name = new char[strlen(info.name) + 1];
        memset(name, 0, strlen(info.name) + 1);
        strcpy(name, info.name);

        if( nullptr != pattern && strlen(pattern) > 0 ){
            if( strlen(pattern) > strlen(name) || 
                __are_arrays_equal(name, (char*)pattern, strlen(pattern)) == false ){
                delete[] name;
                continue; // pattern not matched, skip this file
            }
        }

        items.push_back({info.type==LFS_TYPE_DIR?FILE_TYPE_DIR:FILE_TYPE_REG, info.size, name});
    }

    lfs_dir_close(&m_lfs, &dir);
    return LFS_ERR_OK;
}

/**
 * @brief Checks if a file exists at the specified path.
 * @param path The path of the file to check.
 * @return True if the file exists, false otherwise.
 */
bool LittleFSWrapper::isFileExist(const char *path)
{
    lfs_file_t file;
    int fileOpenOrErr = lfs_file_open(&m_lfs, &file, path, LFS_O_RDONLY);
    if (fileOpenOrErr < 0) {
        return false;
    }
    lfs_file_close(&m_lfs, &file);
    return true;
}

/**
 * @brief Checks if a directory exists at the specified path.
 * @param path The path of the directory to check.
 * @return True if the directory exists, false otherwise.
 */
bool LittleFSWrapper::isDirExist(const char *path)
{
    lfs_dir_t dir;
    int dirOpenOrErr = lfs_dir_open(&m_lfs, &dir, path);
    if (dirOpenOrErr < 0) {
        return false;
    }
    lfs_dir_close(&m_lfs, &dir);
    return true;
}

/**
 * @brief Checks whether path is directory or not
 * @param path The path of the directory to check.
 * @return True if the type is directory, false otherwise.
 */
bool LittleFSWrapper::isDirectory(const char *path)
{
    return isDirExist(path);
}

/**
 * @brief Gets the total size of the LittleFS file system.
 * @return The total size of the file system in bytes.
 */
uint64_t LittleFSWrapper::getTotalSize() {
    return m_lfscfg.block_size * m_lfscfg.block_count;
}

/**
 * @brief Gets the used size of the LittleFS file system.
 * @return The used size of the file system in bytes.
 */
uint64_t LittleFSWrapper::getUsedSize() {
    lfs_info info;
    int32_t usedBlocks = lfs_fs_size(&m_lfs);

    // Iterate through all files and directories to calculate used blocks
    // lfs_dir_t dir;
    // if (lfs_dir_open(&m_lfs, &dir, "/") == 0) {
    //     while (lfs_dir_read(&m_lfs, &dir, &info) > 0) {
    //         if (info.type == LFS_TYPE_REG || info.type == LFS_TYPE_DIR) {
    //             usedBlocks += (info.size + m_lfscfg.block_size - 1) / m_lfscfg.block_size;
    //         }
    //     }
    //     lfs_dir_close(&m_lfs, &dir);
    // }

    return usedBlocks * m_lfscfg.block_size;
}

/**
 * @brief Gets the free size of the LittleFS file system.
 * @return The free size of the file system in bytes.
 */
uint64_t LittleFSWrapper::getFreeSize() {
    uint64_t totalSize = getTotalSize();
    uint64_t usedSize = getUsedSize();
    return totalSize > usedSize ? totalSize - usedSize : 0;
}

/**
 * @brief Callback for reading data from storage.
 * @param c The LittleFS configuration.
 * @param block The block number to read from.
 * @param offset The offset within the block.
 * @param buffer The buffer to store the read data.
 * @param size The number of bytes to read.
 * @return 0 on success, or a negative error code on failure.
 */
int LittleFSWrapper::readCallback(const struct lfs_config *c, lfs_block_t block,
                                  lfs_off_t offset, void *buffer, lfs_size_t size)
{
    auto* wrapper = static_cast<LittleFSWrapper*>(c->context);
    int64_t byteRead = wrapper->m_istorage.read(block * c->block_size + offset, buffer, size);
    // LogFmtI("\nlfsread callback: %d, %d, %d, %d", block, c->block_size, offset, byteRead);
    if( byteRead < 0 ){
        return LFS_ERR_IO;
    }
    return LFS_ERR_OK;
}

/**
 * @brief Callback for writing data to storage.
 * @param c The LittleFS configuration.
 * @param block The block number to write to.
 * @param offset The offset within the block.
 * @param buffer The data to write.
 * @param size The number of bytes to write.
 * @return 0 on success, or a negative error code on failure.
 */
int LittleFSWrapper::progCallback(const struct lfs_config* c, lfs_block_t block,
                                  lfs_off_t offset, const void* buffer, lfs_size_t size) {
    auto* wrapper = static_cast<LittleFSWrapper*>(c->context);
    int64_t byteWritten = wrapper->m_istorage.write(block * c->block_size + offset, buffer, size);
    // LogFmtI("\nlfsprog callback: %d, %d, %d, %d", block, c->block_size, offset, byteWritten);
    if( byteWritten < 0 ){
        return LFS_ERR_IO;
    }
    return LFS_ERR_OK;
}

/**
 * @brief Callback for erasing a block of storage.
 * @param c The LittleFS configuration.
 * @param block The block number to erase.
 * @return 0 on success, or a negative error code on failure.
 */
int LittleFSWrapper::eraseCallback(const struct lfs_config* c, lfs_block_t block) {
    auto* wrapper = static_cast<LittleFSWrapper*>(c->context);
    bool bytesErased = wrapper->m_istorage.erase(block * c->block_size, c->block_size);
    // LogFmtI("\nlfserase callback: %d, %d", block, c->block_size);
    if( bytesErased ){
        return LFS_ERR_OK;
    }
    return LFS_ERR_IO;
}

/**
 * @brief Callback for syncing storage (no-op for most backends).
 * @param c The LittleFS configuration.
 * @return 0 on success.
 */
int LittleFSWrapper::syncCallback(const struct lfs_config* c) {
    return LFS_ERR_OK;
}
