#ifndef FILE_LIB_H
#define FILE_LIB_H

#include <FS.h>
#include <LITTLEFS.h>
#include <SD_MMC.h>
#include <SPI.h>
#include <SD.h>

/**
 * @enum StorageType
 */
enum StorageType
{
    /**
     * Use onboard SPIFFS
     */
    FL_SPIFFS,
    /**
     * Use the SD library
     */
    FL_SD,
    /**
     * Use the SD_MMC library
     */
    FL_MMC
};

/**
 * @class FileLibClass
 * @brief Wrapper for SPIFFS and SD file systems
 */
class FileLibClass
{
private:
    fs::FS *_filesystem = nullptr;
    SPIClass _spi = SPIClass(VSPI);
    StorageType _type;

    bool _verbose = false;

    /**
     * Print out message, if verbose mode is enabled
     */
    void _printMessage(const char *message, ...);

public:
    /**
     * Constructor
     * @param type StorageType::SPIFFS or StorageType::SD
     */
    FileLibClass(StorageType type);

    /**
     * Destructor
     */
    ~FileLibClass();

    /**
     * Begin the file system
     * @param csPin Optional, default 5. Chip select pin for SD card
     * @param misoPin Optional, default 19. MISO pin for SD card
     * @param mosiPin Optional, default 23. MOSI pin for SD card
     * @param sckPin Optional, default 18. SCK pin for SD card
     *
     * @return True if successful
     */
    bool begin(uint8_t csPin = 5, uint8_t misoPin = 19, uint8_t mosiPin = 23, uint8_t sckPin = 18);

    /**
     * Enable or disable verbose mode
     * @param verbose True to enable verbose mode
     */
    void setVerbose(bool verbose) { _verbose = verbose; }

    /**
     * Open the file
     * @param fileName Path and filename to open
     *
     * @return File object
     */
    File openForWriting(const char *fileName);

    /**
     * Open the file
     * @param fileName Filename to open
     *
     * @return File object
     */
    File open(const char *fileName, const char *mode = FILE_READ);

    /**
     * List the contents of a directory
     * @param dirName Path to the directory
     * @param levels Optional, default 0. How many levels deep to list
     */
    void listDir(const char *dirName, uint8_t levels = 0);

    /**
     * Read from a file. This opens the file and reads
     * the data into a buffer. Clears the buffer before reading
     * by default.
     * @param fileName Path and filename to read from
     * @param buffer Buffer to read to.
     * @param bufferSzie Size of the buffer.
     * @param clearBuffer Optional, default true. If true, the buffer will be cleared before reading
     *
     * @return True if successful
     */
    bool read(const char *fileName, uint8_t *buffer, uint16_t bufferSize, bool clearBuffer = true);

    /**
     * Write to a file. This opens the file and
     * writes the bytes to the file.
     * @param fileName Path and filename to write to
     * @param data What will be written to the file
     * @param dataSize Size of the data
     * @param append Optional, default false. If true, the data will be appended to the file
     *
     * @return True if successful
     */
    bool write(const char *fileName, const uint8_t *data, size_t dataSize, bool append = false);

    /**
     * Write to a file. This opens the file and
     * writes string data to the file. This is a wrapper
     * for write().
     * @param fileName Path and filename to write to
     * @param data What will be written to the file
     * @param dataSize Size of the data
     * @param append Optional, default false. If true, the data will be appended to the file
     *
     * @return True if successful
     */
    bool writeString(const char *fileName, const uint8_t *data, bool append = false);

    /**
     * Delete a file
     * @param fileName Path and filename to delete
     *
     * @return True if successful
     */
    bool remove(const char *fileName);

    /**
     * Rename a file
     * @param fileName Path and filename to rename
     * @param newName New path and filename
     * @param overwrite Optional, default false. If true, the new file will overwrite an existing file
     *
     * @return True if successful
     */
    bool rename(const char *fileName, const char *newName, bool overwrite = false);

    /**
     * Create a directory. Spiff currently doesn't support this.
     * @param path Path to the directory
     *
     * @return True if successful
     */
    bool mkdir(const char *path);

    /**
     * Get free space on the file system. Return
     *
     * @return Free space in bytes
     */
    uint64_t getFreeSpace();

    /**
     * Copy a file from a source to a destination
     *
     * @param source File object to copy from
     * @param dest File object to copy to
     */
    bool copy(File &source, File &dest);

    /**
     * Read a portion of a file
     *
     * @param fileName Path and filename to read from
     * @param startPos Starting position to read from
     * @param buffer Buffer to read to
     * @param bufferSize Size of the buffer
     */
    bool readPartial(const char *fileName, uint32_t startPos, uint8_t *buffer, uint16_t bufferSize);

    bool exists(const char* fileName);
};

#endif
