#include "ff.h"
#include "file.h"
#include "sdio_sd.h"
#include "partition.h"
#include "led.h"

#define RETRY_MAX (5)

FATFS fs;
FIL f;
uint8_t file_buffer[FILE_BUFF_SIZE];
TCHAR file_name[FILE_IDX_MAX][FILE_NAME_LEN];

static void file_copy_name(TCHAR *dst, TCHAR *src)
{
    for (uint32_t i=0; i<FILE_NAME_LEN; i++) {
        dst[i] = src[i];
    }
}

static uint32_t file_compare(TCHAR *name)
{
    if (name[0] == '\0') {
        return FILE_IDX_END;
    } else if ((name[0] == 'C' || name[0] == 'c') && \
        (name[1] == 'O' || name[1] == 'o') && \
        (name[2] == 'N' || name[2] == 'n') && \
        (name[3] == 'F' || name[3] == 'f') && \
        (name[4] == 'I' || name[4] == 'i') && \
        (name[5] == 'G' || name[5] == 'g') && \
        (name[6] == '.'                  ) && \
        (name[7] == 'T' || name[7] == 't') && \
        (name[8] == 'X' || name[8] == 'x') && \
        (name[9] == 'T' || name[9] == 't')) {
        return FILE_IDX_CONFIG;
    } else if ((name[0] == 'M' || name[0] == 'm') && \
               (name[1] == 'A' || name[1] == 'a') && \
               (name[2] == 'I' || name[2] == 'i') && \
               (name[3] == 'N' || name[3] == 'n') && \
               (name[8] == '.'                  ) && \
               (name[9] == 'B' || name[9] == 'b') && \
               (name[10] == 'I' || name[10] == 'i') && \
               (name[11] == 'N' || name[11] == 'n')) {
        return FILE_IDX_MAIN;
    } else if ((name[0] == 'G' || name[0] == 'g') && \
               (name[1] == 'A' || name[1] == 'a') && \
               (name[2] == 'S' || name[2] == 's') && \
               (name[8] == '.'                  ) && \
               (name[9] == 'B' || name[9] == 'b') && \
               (name[10] == 'I' || name[10] == 'i') && \
               (name[11] == 'N' || name[11] == 'n')) {
        if (name[3] == '1') {
            return FILE_IDX_GAS1;
        } else if (name[3] == '2') {
            return FILE_IDX_GAS2;
        } else if (name[3] == '3') {
            return FILE_IDX_GAS3;
        } else if (name[3] == '4') {
            return FILE_IDX_GAS4;
        } else {
            return FILE_IDX_OTHER;
        }
    } else if  ((name[0] == 'L' || name[0] == 'l') && \
                (name[1] == 'A' || name[1] == 'a') && \
                (name[2] == 'R' || name[2] == 'r') && \
                (name[3] == 'K' || name[3] == 'k') && \
                (name[4] == '1'                  ) && \
                (name[5] == 'F' || name[5] == 'f') && \
                (name[6] == 'Q' || name[6] == 'q') && \
                (name[7] == '.' || name[7] == '.') && \
                (name[8] == 'B' || name[8] == 'b') && \
                (name[9] == 'I' || name[9] == 'i') && \
                (name[10] == 'N' || name[10] == 'n')) {
        return FILE_IDX_FW;
    } else {
        return FILE_IDX_OTHER;
    }
}

uint32_t file_init(void)
{
    SD_Error sd_err;
    FRESULT ret;
    uint32_t retry;
    DIR dir;
    FILINFO fileinfo;
    TCHAR name[FILE_NAME_LEN];
    uint32_t mask = FILE_MASK_NULL;

    retry = RETRY_MAX;
    while (retry--) {
        sd_err = SD_Init();
        if (sd_err == SD_OK) {
            break;
        } else if (retry == 0) {
            return FILE_MASK_NULL;
        }
    }
    
    retry = RETRY_MAX;
    while (retry--) {
        ret = f_mount(0, &fs);
        if (ret == FR_OK) {
            break;
        } else {
            return FILE_MASK_NULL;
        }
    }

    retry = RETRY_MAX;
    while (retry--) {
        ret = f_getcwd (name, sizeof(name));
        if (ret == FR_OK) {
            break;
        } else {
            return FILE_MASK_NULL;
        }
    }

    retry = RETRY_MAX;
    while (retry--) {
        ret = f_opendir(&dir, name);
        if (ret == FR_OK) {
            break;
        } else {
            return FILE_MASK_NULL;
        }
    }

    while (1) {
        ret = f_readdir(&dir, &fileinfo);
        if (ret == FR_OK) {
            uint32_t idx = file_compare(fileinfo.fname);
            if (idx == FILE_IDX_OTHER) {
                continue;
            } else if (idx == FILE_IDX_END) {
                return mask;
            } else if (idx == FILE_IDX_CONFIG) {
                file_copy_name(file_name[idx], fileinfo.fname);
                mask |= FILE_MASK_CONFIG;
            } else if (idx == FILE_IDX_MAIN) {
                file_copy_name(file_name[idx], fileinfo.fname);
                mask |= FILE_MASK_MAIN;
            } else if (idx == FILE_IDX_GAS1) {
                file_copy_name(file_name[idx], fileinfo.fname);
                mask |= FILE_MASK_GAS1;
            } else if (idx == FILE_IDX_GAS2) {
                file_copy_name(file_name[idx], fileinfo.fname);
                mask |= FILE_MASK_GAS2;
            } else if (idx == FILE_IDX_GAS3) {
                file_copy_name(file_name[idx], fileinfo.fname);
                mask |= FILE_MASK_GAS3;
            } else if (idx == FILE_IDX_GAS4) {
                file_copy_name(file_name[idx], fileinfo.fname);
                mask |= FILE_MASK_GAS4;
            } else if (idx == FILE_IDX_FW) {
                file_copy_name(file_name[idx], fileinfo.fname);
                mask |= FILE_MASK_FW;
            }
        } else {
            return FILE_MASK_NULL;
        }
    }
    return FILE_MASK_NULL;
}

void file_secctor_erase(uint16_t sector)
{
    uint32_t addr, size;

    if (sector == SECTOR_CONFIG) {
        addr = ADDR_CONFIG;
        size = SIZE_CONFIG;
    } else if (sector == SECTOR_BITMAP) {
        addr = ADDR_BITMAP;
        size = SIZE_BITMAP;
    } else if (sector == SECTOR_FW0) {
        addr = ADDR_FW + SIZE_FW_SECTOR * 0;
        size = SIZE_FW_SECTOR;
    } else if (sector == SECTOR_FW1) {
        addr = ADDR_FW + SIZE_FW_SECTOR * 1;
        size = SIZE_FW_SECTOR;
    } else if (sector == SECTOR_FW2) {
        addr = ADDR_FW + SIZE_FW_SECTOR * 2;
        size = SIZE_FW_SECTOR;
    } else {
        return;
    }
    FLASH_Unlock();
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | \
                  FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
    if (FLASH_EraseSector(sector, VoltageRange_3) != FLASH_COMPLETE) {
        led_indicate_error();
    }
    FLASH_Lock();
    for (uint32_t i = 0; i < size; i += 4) {
        if (*(volatile uint32_t *)(addr + i) == 0xFFFFFFFF) {
            continue;
        } else {
            led_indicate_error();
        }
    }
}

/* unit of len is byte, len must multiple of 4 */
void file_write_flash(uint32_t flash, uint8_t *buff, uint32_t len)
{
    uint32_t i;
    uint32_t *p_buff;
    volatile uint32_t *p_flash;

    /* program data */
    p_buff = (uint32_t *)buff;
    FLASH_Unlock();
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | \
                FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
    for (i = 0; i < len / 4; i++) {
        if (FLASH_ProgramWord(flash + i * 4, p_buff[i]) != FLASH_COMPLETE) {
            led_indicate_error();
        }
    }
    FLASH_Lock();
    /* check data */
    p_flash = (volatile uint32_t *)flash;
    for (i = 0; i < len / 4; i++) {
        if (p_flash[i] != p_buff[i]) {
            led_indicate_error();
        }
    }
}
