/**
 * The MB_FS, filesystems wrapper class v1.0.16
 *
 * This wrapper class is for SD and Flash filesystems interface which supports SdFat (//https://github.com/greiman/SdFat)
 *
 *  Created June 14, 2023
 *
 * The MIT License (MIT)
 * Copyright (c) 2023 K. Suwatchai (Mobizt)
 *
 *
 * Permission is hereby granted, free of charge, to any person returning a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef MBFS_CLASS_H
#define MBFS_CLASS_H

#include <Arduino.h>
#include <FS.h>
#include "MB_FS_Interfaces.h"
#include MB_STRING_INCLUDE_CLASS


#define MB_FS_ERROR_FILE_IO_ERROR -300
#define MB_FS_ERROR_FILE_NOT_FOUND -301
#define MB_FS_ERROR_FLASH_STORAGE_IS_NOT_READY -302
#define MB_FS_ERROR_SD_STORAGE_IS_NOT_READY -303
#define MB_FS_ERROR_FILE_STILL_OPENED -304


#define mbfs_file_type mb_fs_mem_storage_type
#define mbfs_flash mb_fs_mem_storage_type_flash
#define mbfs_sd mb_fs_mem_storage_type_sd
#define mbfs_undefined mb_fs_mem_storage_type_undefined

#define mbfs_type (mbfs_file_type)

typedef enum
{
    mb_fs_mem_storage_type_undefined,
    mb_fs_mem_storage_type_flash,
    mb_fs_mem_storage_type_sd
} mb_fs_mem_storage_type;

typedef enum
{
    mb_fs_open_mode_undefined = -1,
    mb_fs_open_mode_read = 0,
    mb_fs_open_mode_write,
    mb_fs_open_mode_append
} mb_fs_open_mode;

extern FS *ufsp;

class MB_FS {

public:
    MB_FS() {}
    ~MB_FS() {}


    bool sdBegin(int ss = -1, int sck = -1, int miso = -1, int mosi = -1, uint32_t frequency = 4000000) {
        mfsp = ufsp;
        return true;
    }

    bool sdSPIBegin(int ss, SPIClass *spiConfig, uint32_t frequency) {
        mfsp = ufsp;
        return true;
    }

    // Check the mounting status of Flash storage.
    bool flashReady() {
        return true;
    }

    // Check the mounting status of SD storage.
    bool sdReady() {
        return true;
    }

    // Check the mounting status of Flash or SD storage with mb_fs_mem_storage_type.
    bool checkStorageReady(mbfs_file_type type) {
        return true;
    }

    int open(const MB_String &filename, mbfs_file_type type, mb_fs_open_mode mode) {
        char fmode[8];
        switch (mode) {
            case mb_fs_open_mode_read:
                strcpy(fmode, "r");
                break;
            case mb_fs_open_mode_write:
                strcpy(fmode, "w");
                break;
            case mb_fs_open_mode_append:
                strcpy(fmode, "a");
                break;
        }
        flash_file = filename;
        mfsp = ufsp;
        mb_File = mfsp->open(filename.c_str(), fmode);
        //Serial.printf(">>> 1 %s, %s, %d ", filename.c_str(), fmode, mb_File);
        return mb_File;
    }

    bool ready(mbfs_file_type type) {
        return true;
    }

    int size(mbfs_file_type type) {
        return mb_File.size();
    }

    // Check if file is ready to read/write.
    int available(mbfs_file_type type) {
        return mb_File.available();
    }

    // Read byte array. Return the number of bytes that completed read or negative value for error.
    int read(mbfs_file_type type, uint8_t *buf, size_t len) {
        return mb_File.read(buf, len);
    }

    // Print char array. Return the number of bytes that completed write or negative value for error.
    int print(mbfs_file_type type, const char *str) {
        return mb_File.print(str);
    }

    // Print char array with new line. Return the number of bytes that completed write or negative value for error.
    int println(mbfs_file_type type, const char *str) {
        return mb_File.print(str);
    }

    // Print integer. Return the number of bytes that completed write or negative value for error.
    int print(mbfs_file_type type, int v) {
        return mb_File.print(v);
    }

    // Print integer with newline. Return the number of bytes that completed write or negative value for error.
    int println(mbfs_file_type type, int v) {
        return mb_File.print(v);
    }

    int print(mbfs_file_type type, unsigned int v) {
        return mb_File.print(v);
    }

    // Print integer with newline. Return the number of bytes that completed write or negative value for error.
    int println(mbfs_file_type type, unsigned int v) {
        return mb_File.print(v);
    }

    // Write byte array. Return the number of bytes that completed write or negative value for error.
    int write(mbfs_file_type type, uint8_t *buf, size_t len) {
        return mb_File.write(buf, len);
    }

    // Close file.
    void close(mbfs_file_type type) {
        mb_File.close();
    }

    // Check file existence.
    bool existed(const MB_String &filename, mbfs_file_type type) {
        return mfsp->exists(filename.c_str());
    }

    // Seek to position in file.
    bool seek(mbfs_file_type type, int pos) {
        return mb_File.seek(pos);
    }

    // Read byte. Return the 1 for completed read or negative value for error.
    int read(mbfs_file_type type) {
        return mb_File.read();
    }

    // Write byte. Return the 1 for completed write or negative value for error.
    int write(mbfs_file_type type, uint8_t v) {
        return mb_File.write(v);
    }

    bool remove(const MB_String &filename, mbfs_file_type type) {
        return mfsp->remove(filename.c_str());
    }

    fs::File &getFlashFile() {
        return mb_File;
    }

    // Get name of opened file.
    const char *name(mbfs_file_type type) {
        return flash_file.c_str();
    }

    // Calculate CRC16 of byte array.
    uint16_t calCRC(const char *buf) {
        uint8_t x;
        uint16_t crc = 0xFFFF;

        int length = (int)strlen(buf);

        while (length--) {
            x = crc >> 8 ^ *buf++;
            x ^= x >> 4;
            crc = (crc << 8) ^ ((uint16_t)(x << 12)) ^ ((uint16_t)(x << 5)) ^ ((uint16_t)x);
        }
        return crc;
    }

    // Free reserved memory at pointer.
    void delP(void *ptr) {
        void **p = (void **)ptr;
        if (*p) {
            free(*p);
            *p = 0;
        }
    }

    // Allocate memory
    void *newP(size_t len, bool clear = true) {
        void *p;
        size_t newLen = getReservedLen(len);
#if defined(BOARD_HAS_PSRAM) && defined(MB_STRING_USE_PSRAM)

        if (ESP.getPsramSize() > 0)
            p = (void *)ps_malloc(newLen);
        else
            p = (void *)malloc(newLen);

        if (!p)
            return NULL;

#else

#if defined(ESP8266_USE_EXTERNAL_HEAP)
        ESP.setExternalHeap();
#endif

        p = (void *)malloc(newLen);
        bool nn = p ? true : false;

#if defined(ESP8266_USE_EXTERNAL_HEAP)
        ESP.resetHeap();
#endif

        if (!nn)
            return NULL;

#endif
        if (clear)
            memset(p, 0, newLen);
        return p;
    }

    size_t getReservedLen(size_t len) {
        int blen = len + 1;

        int newlen = (blen / 4) * 4;

        if (newlen < blen)
            newlen += 4;

        return (size_t)newlen;
    }

    void createDirs(MB_String dirs, mbfs_file_type type) {
        if (!longNameSupported())
            return;

        MB_String dir;
        int count = 0;
        int lastPos = 0;
        for (size_t i = 0; i < dirs.length(); i++) {
            dir.append(1, dirs[i]);
            count++;
            if (dirs[i] == '/' && i > 0) {
                if (dir.length() > 0)
                {

                    lastPos = dir.length() - 1;

#if defined(MBFS_FLASH_FS)
                    if (type == mbfs_flash)
                        MBFS_FLASH_FS.mkdir(dir.substr(0, dir.length() - 1).c_str());
#endif

#if defined(MBFS_SD_FS)
                    if (type == mbfs_sd)
                        MBFS_SD_FS.mkdir(dir.substr(0, dir.length() - 1).c_str());
#endif
                }
                count = 0;
            }
        }

        if (count > 0)
        {
            if (dir.find('.', lastPos) == MB_String::npos)
            {
#if defined(MBFS_FLASH_FS)
                if (type == mbfs_flash)
                    MBFS_FLASH_FS.mkdir(dir.c_str());
#endif

#if defined(MBFS_SD_FS)
                if (type == mbfs_sd)
                    MBFS_SD_FS.mkdir(dir.c_str());
#endif
            }
        }

        dir.clear();
    }

    bool longNameSupported() {

#if defined(MBFS_SDFAT_ENABLED) || defined(MBFS_FLASH_FS)
        return true;
#endif

#if defined(MBFS_SD_FS) && (defined(ESP32) || defined(ESP8266) || defined(MB_ARDUINO_PICO))
        return true;
#endif

        return false;
    }

private:
    uint16_t flash_filename_crc = 0;
    MB_String flash_file, sd_file;
    FS *mfsp;
    File mb_File;

};

#endif
