#include "stm32f4xx_flash.h"
#include "cali_flash.h"

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

    if (sector == SECTOR_CALI0) {
        addr = ADDR_CALI0;
    } else if (sector == SECTOR_CALI1) {
        addr = ADDR_CALI1;
    } else {
        return;
    }
    size = SIZE_CALI_SECTOR;
    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) {
        while (1);
    }
    FLASH_Lock();
    for (uint32_t i = 0; i < size; i += 4) {
        if (*(volatile uint32_t *)(addr + i) == 0xFFFFFFFF) {
            continue;
        } else {
            while (1);
        }
    }
}

void cali_flash_copy(uint32_t dest, uint32_t src)
{
    FLASH_Unlock();
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | \
                FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
    for (uint32_t i = 0; i < CALI_FLASH_CALI_SIZE; i += 4) {
        if (FLASH_ProgramWord(src + i, *(uint32_t *)(dest + i)) != FLASH_COMPLETE) {
            while (1);
        }
    }
    FLASH_Lock();
}

void cali_flash_copy_empty(uint32_t addr)
{
    FLASH_Status sts;

    FLASH_Unlock();
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | \
                FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
    sts = FLASH_ProgramWord(addr, addr);
    if (sts != FLASH_COMPLETE) {
        while (1);
    }
    for (uint32_t i = 4; i < CALI_FLASH_CALI_SIZE; i += 4) {
        sts = FLASH_ProgramWord(addr + i, 0);
        if (sts != FLASH_COMPLETE) {
            while (1);
        }
    }
    FLASH_Lock();
}

uint32_t cali_flash_get_info(void)
{
    uint8_t state0, state1;
    uint32_t addr0, addr1;
    uint32_t addr, val;
    uint32_t addr_ret;

    /* cali sector0 */
    if (*(uint32_t *)ADDR_CALI0 == 0xFFFFFFFF) {
        state0 = CALI_FLASH_STATE_EMPTY;
        for (addr = ADDR_CALI0; addr < ADDR_CALI0_END; addr += 4) {
            val = *(uint32_t *)addr;
            if (val == 0xFFFFFFFF) {
                continue;
            } else {
                state0 = CALI_FLASH_STATE_ERROR;
                break;
            }
        }
        if ((addr >= ADDR_CALI0_END) && (state0 == CALI_FLASH_STATE_EMPTY)) {
            state0 = CALI_FLASH_STATE_PURE;
        }
    } else {
        state0 = CALI_FLASH_STATE_ERROR;
        for (addr = ADDR_CALI0; addr < ADDR_CALI0_END; addr += CALI_FLASH_CALI_SIZE) {
            val = *(uint32_t *)addr;
            if (val == addr) {
                continue;
            } else if (val == 0xFFFFFFFF) {
                addr0 = addr - CALI_FLASH_CALI_SIZE;
                state0 = CALI_FLASH_STATE_WORK;
                break;
            } else {
                state0 = CALI_FLASH_STATE_ERROR;
                break;
            }
        }
        if (addr >= ADDR_CALI0_END) {
            state0 = CALI_FLASH_STATE_FULL;
        }
    }
    /* cali sector1 */
    if (*(uint32_t *)ADDR_CALI1 == 0xFFFFFFFF) {
        state1 = CALI_FLASH_STATE_EMPTY;
        for (addr = ADDR_CALI1; addr < ADDR_CALI1_END; addr += 4) {
            val = *(uint32_t *)addr;
            if (val == 0xFFFFFFFF) {
                continue;
            } else {
                state1 = CALI_FLASH_STATE_ERROR;
                break;
            }
        }
        if ((addr >= ADDR_CALI1_END) && (state1 == CALI_FLASH_STATE_EMPTY)) {
            state1 = CALI_FLASH_STATE_PURE;
        }
    } else {
        state1 = CALI_FLASH_STATE_ERROR;
        for (addr = ADDR_CALI1; addr < ADDR_CALI1_END; addr += CALI_FLASH_CALI_SIZE) {
            val = *(uint32_t *)addr;
            if (val == addr) {
                continue;
            } else if (val == 0xFFFFFFFF) {
                addr1 = addr - CALI_FLASH_CALI_SIZE;
                state1 = CALI_FLASH_STATE_WORK;
                break;
            } else {
                state1 = CALI_FLASH_STATE_ERROR;
                break;
            }
        }
        if (addr >= ADDR_CALI1_END) {
            state1 = CALI_FLASH_STATE_FULL;
        }
    }
    /* operation by cali sector0&1 */
    if (state0 == CALI_FLASH_STATE_WORK) {
        if (state1 != CALI_FLASH_STATE_PURE) {
            cali_flash_sector_erase(SECTOR_CALI1);
        }
        addr_ret = addr0;
    } else if (state0 == CALI_FLASH_STATE_PURE) {
        if (state1 == CALI_FLASH_STATE_PURE) {
            addr_ret = ADDR_CALI0;
            cali_flash_copy_empty(addr_ret);
        } else if (state1 == CALI_FLASH_STATE_WORK) {
            addr_ret = addr1;
        } else if (state1 == CALI_FLASH_STATE_FULL) {
            cali_flash_copy(ADDR_CALI0, ADDR_CALI1_END - CALI_FLASH_CALI_SIZE);
            cali_flash_sector_erase(SECTOR_CALI1);
            addr_ret = ADDR_CALI0;
        } else {
            cali_flash_sector_erase(SECTOR_CALI1);
            addr_ret = ADDR_CALI0;
            cali_flash_copy_empty(addr_ret);
        }
    } else if (state0 == CALI_FLASH_STATE_FULL) {
        if (state1 == CALI_FLASH_STATE_PURE) {
            cali_flash_copy(ADDR_CALI1, ADDR_CALI0_END - CALI_FLASH_CALI_SIZE);
            cali_flash_sector_erase(SECTOR_CALI0);
            addr_ret = ADDR_CALI1;
        } else if (state1 == CALI_FLASH_STATE_WORK) {
            cali_flash_sector_erase(SECTOR_CALI0);
            addr_ret = addr1;
        } else {
            cali_flash_sector_erase(SECTOR_CALI0);
            cali_flash_sector_erase(SECTOR_CALI1);
            addr_ret = ADDR_CALI0;
            cali_flash_copy_empty(addr_ret);
        }
    } else {
        cali_flash_sector_erase(SECTOR_CALI0);
        cali_flash_sector_erase(SECTOR_CALI1);
        addr_ret = ADDR_CALI0;
        cali_flash_copy_empty(addr_ret);
    }
    return addr_ret;
}

// void PendSV_Handler(void)
// {
//     SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; /* set pendsv pending */
//     NVIC_SetPriority(PendSV_IRQn, 1);
//     static uint32_t val = 12345678;
//     cli_data_comb.force_available |= (1 << 1);
//     cli_data_comb.force_data[1] = val;
//     val--;
// }

void cali_flash_write(void)
{
    uint8_t cnt;
    FLASH_Status sts;
    uint32_t *p;
    static uint32_t addr;

    p = (uint32_t *)(&data_cali_flash);
    if (data_cali_flash.word_remain == 0) {
        return;
    } else if (data_cali_flash.word_remain == CALI_FLASH_WORD_CNT) {
        addr = data_cali_flash.id + CALI_FLASH_CALI_SIZE;
        if (addr % CALI_FLASH_CALI_SIZE != 0) {
            addr = (addr + CALI_FLASH_CALI_SIZE) / CALI_FLASH_CALI_SIZE;
            addr *= CALI_FLASH_CALI_SIZE;
        } else if ((addr >= ADDR_CALI0_END) && (addr < ADDR_CALI1)) {
            addr = ADDR_CALI1;
        } else if (addr >= ADDR_CALI1_END) {
            addr = ADDR_CALI0;
        }
        p[0] = addr;
    }
    cnt = CALI_FLASH_WORD_CNT - data_cali_flash.word_remain;
    FLASH_Unlock();
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | \
                FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
    sts = FLASH_ProgramWord(addr + cnt * 4, p[cnt]);
    if (sts != FLASH_COMPLETE) {
        while (1);
    }
    FLASH_Lock();
    data_cali_flash.word_remain--;
}
