/**
 *!
 * \file        b_mod_fs.c
 * \version     v0.0.1
 * \date        2020/06/02
 * \author      Bean(notrynohigh@outlook.com)
 *******************************************************************************
 * @attention
 *
 * Copyright (c) 2020 Bean
 *
 * Permission is hereby granted, free of charge, to any person obtaining 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.
 *******************************************************************************
 */

/*Includes ----------------------------------------------*/
#include "b_mod_fs.h"
#if _FS_ENABLE
#include <stdio.h>
/**
 * \addtogroup BABYOS
 * \{
 */

/**
 * \addtogroup MODULES
 * \{
 */

/**
 * \addtogroup FS
 * \{
 */

/**
 * \defgroup FS_Private_TypesDefinitions
 * \{
 */

/**
 * \}
 */

/**
 * \defgroup FS_Private_Defines
 * \{
 */

/**
 * \}
 */

/**
 * \defgroup FS_Private_Macros
 * \{
 */

/**
 * \}
 */

/**
 * \defgroup FS_Private_Variables
 * \{
 */
#if _FS_SELECT == 0
static FATFS bFATFS_Table[E_DEV_NUMBER];
#endif

#if _FS_SELECT == 1
lfs_t bLittleFS;
#endif

/**
 * \}
 */

/**
 * \defgroup FS_Private_FunctionPrototypes
 * \{
 */

/**
 * \}
 */

/**
 * \defgroup FS_Private_Functions
 * \{
 */
#if _FS_SELECT == 1
#include "b_core.h"
#include "b_device.h"

static int _bFS_DeviceRead(const struct lfs_config *c, lfs_block_t block, lfs_off_t off,
                           void *buffer, lfs_size_t size)
{
    int      fd     = -1;
    int      retval = LFS_ERR_OK;
    uint32_t e_size = 4096;
    fd              = bOpen(SPIFLASH, BCORE_FLAG_RW);
    if (fd >= 0)
    {
        if (bCtl(fd, bCMD_GET_SECTOR_SIZE, &e_size) == 0)
        {
            bLseek(fd, block * e_size + off);
            bRead(fd, buffer, size);
        }
        else
        {
            retval = LFS_ERR_CORRUPT;
        }
        bClose(fd);
    }
    else
    {
        retval = LFS_ERR_CORRUPT;
    }
    return retval;
}

int _bFS_DeviceWrite(const struct lfs_config *c, lfs_block_t block, lfs_off_t off,
                     const void *buffer, lfs_size_t size)
{
    int      fd     = -1;
    uint32_t e_size = 4096;
    int      retval = LFS_ERR_OK;
    fd              = bOpen(SPIFLASH, BCORE_FLAG_RW);
    if (fd >= 0)
    {
        if (bCtl(fd, bCMD_GET_SECTOR_SIZE, &e_size) == 0)
        {
            bLseek(fd, block * e_size + off);
            bWrite(fd, (uint8_t *)buffer, size);
        }
        else
        {
            retval = LFS_ERR_CORRUPT;
        }
        bClose(fd);
    }
    else
    {
        return LFS_ERR_CORRUPT;
    }
    return retval;
}

int _bFS_DeviceErase(const struct lfs_config *c, lfs_block_t block)
{
    int          fd     = -1;
    int          retval = LFS_ERR_OK;
    uint32_t     e_size = 4096;
    bCMD_Erase_t cmd;
    fd = bOpen(SPIFLASH, BCORE_FLAG_RW);
    if (fd >= 0)
    {
        if (bCtl(fd, bCMD_GET_SECTOR_SIZE, &e_size) == 0)
        {
            cmd.addr = block * e_size;
            cmd.num  = 1;
            bCtl(fd, bCMD_ERASE_SECTOR, &cmd);
        }
        else
        {
            retval = LFS_ERR_CORRUPT;
        }
        bClose(fd);
    }
    else
    {
        return LFS_ERR_CORRUPT;
    }
    return retval;
}

int _bFS_DeviceSync(const struct lfs_config *c)
{
    return LFS_ERR_OK;
}

#endif
/**
 * \}
 */

/**
 * \addtogroup FS_Exported_Functions
 * \{
 */
#if _FS_SELECT == 0
uint8_t bFileSystemWorkBuf[FF_MAX_SS];
int     bFS_Init()
{
    FRESULT result = FR_OK;
    uint8_t disk_str[8];
    FATFS * fs;
    DWORD   fre_clust, fre_sect, tot_sect;
#if _SPIFLASH_ENABLE
    sprintf((char *)disk_str, "%d:", DEV_SPIFLASH);
    result = f_mount(&bFATFS_Table[E_DEV_SPIFLASH], (const char *)disk_str, 1);
    if (result == FR_NO_FILESYSTEM)
    {
        b_log_w("no filesystem\r\n");
        if (f_mkfs((const char *)disk_str, NULL, bFileSystemWorkBuf, FF_MAX_SS) != FR_OK)
        {
            b_log_e("mkfs err..\r\n");
            return -1;
        }
    }
    else if (result != FR_OK)
    {
        b_log_e("sd mount err..%d\r\n", result);
        return -1;
    }
    /* Get volume information and free clusters of drive 1 */
    f_getfree((const char *)disk_str, &fre_clust, &fs);
    tot_sect = (fs->n_fatent - 2) * fs->csize;
    fre_sect = fre_clust * fs->csize;

    /* Print the free space (assuming 512 bytes/sector) */
    b_log("%10lu KiB total drive space.\n%10lu KiB available.\r\n", tot_sect * 4, fre_sect * 4);

#endif

#if _SD_ENABLE
    sprintf((char *)disk_str, "%d:", DEV_SDCARD);
    result = f_mount(&bFATFS_Table[E_DEV_SDCARD], (const char *)disk_str, 1);
    if (result != FR_OK)
    {
        b_log_e("sd mount err..%d\r\n", result);
        return -1;
    }
    f_getfree((const char *)disk_str, &fre_clust, &fs);
    tot_sect = (fs->n_fatent - 2) * fs->csize;
    fre_sect = fre_clust * fs->csize;

    /* Print the free space (assuming 512 bytes/sector) */
    b_log("%10lu KiB total drive space.\n%10lu KiB available.\r\n", tot_sect / 2, fre_sect / 2);
#endif
    return 0;
}

#if _FS_TEST_ENABLE
FIL fil;
int bFS_Test()
{
    // read current count
    uint32_t boot_count = 0;
    UINT     brw        = 0;
    FRESULT  fr; /* FatFs return code */
#if _SD_ENABLE    
    fr = f_open(&fil, "1:test.txt", FA_OPEN_ALWAYS | FA_WRITE | FA_READ);
#else
    fr = f_open(&fil, "0:test.txt", FA_OPEN_ALWAYS | FA_WRITE | FA_READ);
#endif    
    if (fr)
    {
        b_log_e("open %d\r\n", fr);
        f_close(&fil);
        return -1;
    }
    f_lseek(&fil, 0);
    fr = f_read(&fil, &boot_count, sizeof(boot_count), &brw);
    if (fr)
    {
        b_log_e("read %d\r\n", fr);
        f_close(&fil);
        return -1;
    }
    if (brw != sizeof(boot_count))
    {
        b_log_e("read %d %d\r\n", brw, boot_count);
        boot_count = 0;
    }
    else
    {
        b_log("boot_count %d\r\n", boot_count);
        boot_count++;
    }
    f_lseek(&fil, 0);
    fr = f_write(&fil, &boot_count, sizeof(boot_count), &brw);
    if (fr)
    {
        b_log_e("write %d\r\n", fr);
        f_close(&fil);
        return -1;
    }
    f_close(&fil);
    return 0;
}
#else
int bFS_Test()
{
    return 0;
}
#endif

#endif

#if (_FS_SELECT == 1)
// configuration of the filesystem is provided by this struct
const struct lfs_config cfg = {
    // block device operations
    .read  = _bFS_DeviceRead,
    .prog  = _bFS_DeviceWrite,
    .erase = _bFS_DeviceErase,
    .sync  = _bFS_DeviceSync,

    // block device configuration
    .read_size      = 16,
    .prog_size      = 16,
    .block_size     = 4096,
    .block_count    = _SPIFLASH_SIZE * 1024 * 1024 / 4096,
    .cache_size     = 16,
    .lookahead_size = 16,
    .block_cycles   = 500,
};

int bFS_Init()
{
    // mount the filesystem
    int err = lfs_mount(&bLittleFS, &cfg);
    b_log("mount:%d\r\n", err);
    // reformat if we can't mount the filesystem
    // this should only happen on the first boot
    if (err)
    {
        lfs_format(&bLittleFS, &cfg);
        lfs_mount(&bLittleFS, &cfg);
    }
    return 0;
}

#if _FS_TEST_ENABLE
lfs_file_t file;
int        bFS_Test()
{
    // read current count
    lfs_t *  plsf       = &bLittleFS;
    uint32_t boot_count = 0;
    lfs_file_open(plsf, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT);
    lfs_file_read(plsf, &file, &boot_count, sizeof(boot_count));

    // update boot count
    boot_count += 1;
    lfs_file_rewind(plsf, &file);
    lfs_file_write(plsf, &file, &boot_count, sizeof(boot_count));

    // remember the storage is not updated until the file is closed successfully
    lfs_file_close(plsf, &file);

    // release any resources we were using
    lfs_unmount(plsf);

    // print the boot count
    b_log("boot_count: %d\n", boot_count);
    return 0;
}
#else
int bFS_Test()
{
    return 0;
}
#endif

#endif

/**
 * \}
 */

/**
 * \}
 */

/**
 * \}
 */

/**
 * \}
 */
#endif

/************************ Copyright (c) 2020 Bean *****END OF FILE****/