#include "FileLib.h"

FileLibClass::FileLibClass(StorageType type) : _type(type) {}

FileLibClass::~FileLibClass()
{
    _printMessage("Unmounting file system\n");
    if (_type == StorageType::FL_SPIFFS)
    {
        LITTLEFS.end();
    }
    else if (_type == StorageType::FL_MMC)
    {
        SD_MMC.end();
    }
    else if (_type == StorageType::FL_SD)
    {
        SD.end();
    }
}

bool FileLibClass::begin(uint8_t csPin, uint8_t misoPin, uint8_t mosiPin, uint8_t sckPin)
{
    _printMessage("Initializing file system\n");
    if (_type == StorageType::FL_SPIFFS)
    {
        _filesystem = &LITTLEFS;
        return LITTLEFS.begin(true);
    }

    if (_type == StorageType::FL_MMC)
    {
        _filesystem = &SD_MMC;
        return SD_MMC.begin();
    }

    if (_type == StorageType::FL_SD)
    {
        _spi.begin(sckPin, misoPin, mosiPin, csPin);
        _filesystem = &SD;
        return SD.begin(5, _spi);
    }

    return false;
}

File FileLibClass::openForWriting(const char *fileName)
{
    _printMessage("Opening file '%s' for writing\n", fileName);
    return _filesystem->open(fileName, FILE_WRITE);
}

File FileLibClass::open(const char *fileName, const char *mode)
{
    if (!_filesystem)
    {
        _printMessage("File system not initialized");
        return File(); // Return an empty File object if filesystem is not initialized
    }
    return _filesystem->open(fileName, mode);
}

bool FileLibClass::readPartial(const char *fileName, uint32_t startPos, uint8_t *buffer, uint16_t bufferSize)
{
    if (!_filesystem)
    {
        _printMessage("File system not initialized");
        return false;
    }

    File file = _filesystem->open(fileName, FILE_READ);
    if (!file)
    {
        _printMessage("Failed to open file");
        return false;
    }

    if (!file.seek(startPos))
    {
        _printMessage("Failed to seek to position %u in file", startPos);
        file.close();
        return false;
    }

    size_t bytesRead = file.read(buffer, bufferSize);
    file.close();

    if (bytesRead == 0)
    {
        _printMessage("No data read from file");
        return false;
    }

    return true;
}

bool FileLibClass::write(const char *fileName, const uint8_t *data, size_t dataSize, bool append)
{
    _printMessage("Writing %d bytes to file '%s'\n", dataSize, fileName);
    File file = _filesystem->open(fileName, append ? FILE_APPEND : FILE_WRITE);

    // Sanity check
    if (!file || file.isDirectory())
    {
        _printMessage("Failed to open file '%s' for writing\n", fileName);
        return false;
    }

    // size_t written = file.write(reinterpret_cast<const uint8_t *>(data), dataSize);
    size_t written = 0;
    for (size_t i = 0; i < dataSize; i++)
    {
        written += file.write(data[i]);
    }

    // Confirm that the data was written
    if (written == dataSize)
    {
        file.close();
        return true;
    }

    if (written > 0)
    {
        _printMessage("Only wrote %d bytes of %d\n", written, dataSize);
        file.close();
        return false;
    }

    _printMessage("Failed to write to file '%s'\n", fileName);
    file.close();
    return false;
}

bool FileLibClass::writeString(const char *fileName, const uint8_t *data, bool append)
{
    _printMessage("Writing string to file '%s'\n", fileName);
    // use strlen so we don't write the null terminator
    int strLen = strlen((char *)&data);
    return write(fileName, reinterpret_cast<const uint8_t *>(data), strLen, append);
}

void FileLibClass::listDir(const char *dirName, uint8_t levels)
{
    if (!_verbose)
    {
        return;
    }

    _printMessage("Listing directory: %s\r\n", dirName);

    File root = _filesystem->open(dirName);
    if (!root)
    {
        _printMessage("− failed to open directory");
        return;
    }

    if (!root.isDirectory())
    {
        _printMessage(" − not a directory");
        return;
    }

    File file = root.openNextFile();
    while (file)
    {
        if (file.isDirectory())
        {
            Serial.printf("  DIR : %s\n", file.name());
            if (levels)
            {
                listDir(file.name(), levels - 1);
            }
        }
        else
        {
            Serial.printf("  FILE: %s", file.name());
            Serial.printf("\tSIZE: %d\n", file.size());
        }
        file = root.openNextFile();
    }
}

bool FileLibClass::read(const char *fileName, uint8_t *buffer, uint16_t bufferSize, bool clearBuffer)
{
    _printMessage("Reading file '%s'\n", fileName);
    if (clearBuffer)
    {
        memset(buffer, 0, bufferSize);
    }

    File file = _filesystem->open(fileName, FILE_READ);

    uint16_t fileSize = file.size();

    // Sanity check
    if (!file || file.isDirectory())
    {
        _printMessage("Failed to open file '%s'.\n", fileName);
        return false;
    }

    // check if the buffer is big enough
    if (bufferSize < fileSize)
    {
        _printMessage("Buffer size is too small for file '%s'.\n", fileName);
        file.close();
        return false;
    }

    if (!file.available())
    {
        _printMessage("File '%s' is empty.\n", fileName);
    }

    // size_t bytesRead = file.readBytes(buffer, fileSize);
    // read the data byte by byte
    for (size_t i = 0; i < fileSize; i++)
    {
        buffer[i] = file.read();
    }

    file.close();
    return true;
}

bool FileLibClass::remove(const char *fileName)
{
    _printMessage("Removing file '%s'\n", fileName);
    if (!_filesystem->exists(fileName))
    {
        _printMessage("File '%s' does not exist.\n", fileName);
        return false;
    }
    return _filesystem->remove(fileName);
}

bool FileLibClass::rename(const char *fileName, const char *newName, bool overwrite)
{
    _printMessage("Renaming file '%s' to '%s'\n", fileName, newName);
    if (_filesystem->exists(newName) && !overwrite)
    {
        _printMessage("File '%s' already exists.\n", newName);
        return false;
    }

    return _filesystem->rename(fileName, newName);
}

bool FileLibClass::mkdir(const char *path)
{
    _printMessage("Creating directory '%s'\n", path);

    if (_filesystem->exists(path))
    {
        _printMessage("Directory '%s' already exists.\n", path);
        return false;
    }

    return _filesystem->mkdir(path);
}

uint64_t FileLibClass::getFreeSpace()
{
    _printMessage("Getting free space\n");
    if (_type == StorageType::FL_SPIFFS)
    {
        return LITTLEFS.totalBytes() - LITTLEFS.usedBytes();
    }

    if (_type == StorageType::FL_MMC)
    {
        uint64_t cardSize = SD_MMC.totalBytes();
        uint64_t usedSpace = SD_MMC.usedBytes();

        if (usedSpace > cardSize)
        {
            _printMessage("ERROR: Used space is greater than card size \n");
            return 0;
        }

        uint64_t freeSpace = cardSize - usedSpace;
        return freeSpace;
    }

    if (_type == StorageType::FL_SD)
    {
        uint64_t cardSize = SD.totalBytes();
        uint64_t usedSpace = SD.usedBytes();

        if (usedSpace > cardSize)
        {
            _printMessage("Error: Used space is greater than card size \n");
            return 0;
        }

        uint64_t freeSpace = cardSize - usedSpace;
        return freeSpace;
    }

    return 0;
}

bool copy(File &source, File &dest)
{
    if (!source || !dest)
    {
        return false;
    }

    int bufferSize = 512;
    char buffer[bufferSize];

    while (source.available())
    {
        int bytesRead = source.readBytes(buffer, bufferSize);
        int bytesWritten = dest.write((uint8_t *)&buffer, bytesRead);

        if (bytesWritten != bytesRead)
        {
            return false;
        }
    }

    dest.flush();

    return true;
}

void FileLibClass::_printMessage(const char *message, ...)
{
    if (_verbose)
    {
        // get the type name
        const char *typeName;
        switch (_type)
        {
        case FL_SPIFFS:
            typeName = "FL_SPIFFS";
            break;
        case FL_SD:
            typeName = "FL_SD";
            break;
        case FL_MMC:
            typeName = "FL_MMC";
            break;
        default:
            typeName = "Unknown";
            break;
        }

        char buffer[256];

        va_list args;
        va_start(args, message);
        vsnprintf(buffer, sizeof(buffer), message, args);
        va_end(args);
        // switch for different storage types
        Serial.printf("%s: %s", typeName, buffer);
    }
}

//Create FileLibClass::exists function
bool FileLibClass::exists(const char* fileName)
{
  return _filesystem->exists(fileName);
}
