#include "at32f403a_407.h"
#include "user_fatfs.h"
#include "common_config.h"
#include "common.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "spi_flash.h"
#include <string.h>
#include <stdlib.h>

#ifdef PRINTF_STDLIB
#include <stdio.h>
#endif
#ifdef PRINTF_CUSTOM
#include "tinystdio.h"
#endif

#define MAX_DRV_NUM 2  // Текущее число поддерживаемых дисков

SemaphoreHandle_t spiFlashMutex;

FIL update_file;  // Файл прошивки

FATFS *disks[MAX_DRV_NUM];

static unsigned long writePtr[MAX_DRV_NUM] = {0, 0};  // Указатель на позицию для записи в файл

static uint32_t strCount = 0;  // Число строк в файле
static uint32_t pageCount = 0; // Число страниц в файле
char file_path[32];

static bool fSecDrvEn = false;  // Флаг присутствия второстепенного диска
static bool fPrimDrvEn = false;  // Флаг присутствия основного диска
static bool fatfs_init = false;


//
bool InitFS(BYTE drv) 
{
    FRESULT  res;
    FATFS   *disk;
    DWORD free;
    uint32_t space;
    
    spiFlashMutex = xSemaphoreCreateMutex();

    snprintf(file_path, sizeof(file_path), "%u:", drv);

    disk = pvPortMalloc(sizeof(FATFS));
    if (disk == NULL) {
        printf("DRIVE %u: malloc error\n\r", drv);
        fatfs_init = true;
        return false;
    }
    
    // Монтировать диск
    res = f_mount(disk, "", 1);
   
    // Создать файловую систему
    if (res != FR_OK) {
        res = f_mkfs(file_path,0,0);
    }

    f_getfree("/", &free, &disk);
    space = free * disk->ssize;
    
    if (space == 0) {
        f_mkfs(file_path,0,0);
        f_getfree("/", &free, &disk);
        space = free * disk->ssize;
    }

    res = f_chdir(file_path);

    if (res == FR_NO_FILESYSTEM) 
    {
        printf("DRIVE %u: no filesystem found\n\r", drv);

        switch(drv) 
        {
            case SPI_FLASH:
                spi_flash_erase_sector(0,0);
                spi_flash_erase_sector(1,0);
                spi_flash_erase_sector(2,0);
                spi_flash_erase_sector(3,0);
            break;
        }
        f_mkfs(file_path,0,0);

        if (f_chdir(file_path) != FR_OK) {
            printf("DRIVE %u: filesystem error\n\r", drv);
            vPortFree(disk);
            fatfs_init = true;
            return false;
        }
        else {
            printf("DRIVE %u: filesystem created successfully\n\r", drv);
        }
    }
    else if (res == FR_NOT_READY) {
        printf("DRIVE %u: not ready\n\r", drv);
        printf("DRIVE %u: File system is not initialized. Error!\n\r", drv);
        vPortFree(disk);
        fatfs_init = true;
        return false;
    }
    else if(res == FR_OK) 
    {
        // Проверка файла лога
        res = f_open(&update_file, LOG_NAME, FA_READ); 
        if (res != FR_OK) {
            res = f_open(&update_file, LOG_NAME, FA_WRITE | FA_CREATE_NEW); 
            if (res != FR_OK) {
                f_close(&update_file);
                printf("DRIVE %u: File system is not initialized. Error!\n\r", drv);
                fatfs_init = true;
                return false;
            }
        }
        f_close(&update_file);
    }

    printf("DRIVE %u: filesystem initialized successfully\n\r", drv);
    fatfs_init = true;
    return true;
}

/**
  * @brief  Возвращает число файлов в папке
  * @param *path имя папки в виде "/LOG"
  * @retval 
  */
uint32_t GetFilesCount(char *path)
{
    FRESULT res;
    FILINFO fno;
    DIR dir;
    char *fn;
    uint32_t fileCount;
  
    res = f_opendir(&dir, path);
  
    if (res != FR_OK)
        return 0;  // Не удалось открыть директорию
  
    for (;;)
    {
        res = f_readdir(&dir, &fno); // Чтение объекта директории 
    
	    // Останов цикла при ошибке или при достижении конца списка директории 
	    if (res != FR_OK || fno.fname[0] == 0)
            return fileCount;
            
	    if (fno.fname[0] == '.')
            continue; // Игнорирование элемента 'точка' 
    
        fn = fno.fname;

        if (!(fno.fattrib & AM_DIR)) 
        {   // Это директория 
            fileCount++;
	        DBG printf("%s\r\n",fn);
        }
        else
        {
            DBG printf("%s/%s\r\n", path, fn);
        }
  }
}

/* Get fs file raw data */
uint32_t GetFileData(BYTE drv, char *file_name, unsigned long ptr, char *str, uint32_t size)
{
     unsigned long readPtr;
     FRESULT res;
     uint32_t count = 0;

     res = f_open(&update_file, file_name, FA_WRITE); 
     if (res != FR_OK) {
         f_close(&update_file);
         return 0;
     }

     readPtr = ptr;

     res = f_lseek(&update_file, readPtr);
     if (res != FR_OK) {
         f_close(&update_file);
         return 0;
     }

     res = f_read(&update_file, str, size, (UINT *)&count);
     if (res != FR_OK) {
         f_close(&update_file);
         return 0;
     }
     f_close(&update_file);

     return count;
}

/* Put fs file raw data */
uint32_t PutFileData(BYTE drv, char *file_name, unsigned long ptr, char *str, uint32_t size, bool truncate)
{
    FRESULT res;
    uint32_t count = 0;
    
    if (xSemaphoreTake(spiFlashMutex, 2000) != pdTRUE) 
        return 0;

    res = f_open(&update_file, file_name, FA_WRITE); 
    if (res != FR_OK) {
        f_close(&update_file);
        xSemaphoreGive(spiFlashMutex);
        return 0;
    }
    if( ptr == 1 ){
      res = f_lseek(&update_file, 0);
    }
    else{
      res = f_lseek(&update_file, update_file.fsize);
    }
    if (res != FR_OK) {
        f_close(&update_file);
        xSemaphoreGive(spiFlashMutex);
        return 0;
    }

    res = f_write(&update_file, str, size, (UINT *)&count);
    if (res != FR_OK) {
        f_close(&update_file);
        xSemaphoreGive(spiFlashMutex);
        return 0;
    }

    if (truncate) {
        res = f_truncate(&update_file);
        if (res != FR_OK) {
            xSemaphoreGive(spiFlashMutex);
        }
    }
    
    f_close(&update_file);

    xSemaphoreGive(spiFlashMutex);
    
    return count;
}

//
void EraseFlash()
{
    if (xSemaphoreTake(spiFlashMutex, 10000) != pdTRUE) 
        return;

    spi_flash_erase_sector(0,0);
    spi_flash_erase_sector(1,0);
    spi_flash_erase_sector(2,0);
    spi_flash_erase_sector(3,0);

    xSemaphoreGive(spiFlashMutex);
}

//
void flash_check_space(FATFS *fs)
{
    DWORD fre_clust, fre_sect;
    
    // Получение информации о томе и количество свободных кластеров
    f_getfree(0, &fre_clust, &fs);
    fre_sect = fre_clust * fs->csize;
    //availableSpace = fre_sect/2;
}

//
bool fatfs_isinit(void)
{
    return fatfs_init;
}
