/******************************************************************************
 * @file     data_manege.c
 * @version  V4.0.0
 * @date     30-July-2026
 *
 * @brief    Application for data save/access with flash
 *           This application can manage flash access efficiently and extend flash life
 *           (1) This application extend flash life(erase times) from 100K times to 1500K times
 *           (2) It provides a well designed flash algorithm to avoid erase sector frequently.
 *           (3) Easy to use : Only 3 steps
 *               <1> Users define own data structure in struct UserData_t
 *               <2> Call the initial function DATA_AccessInit and set the sector adress
 *                   (We have prepared a valid default address for you)
 *               <3> Call DATA_Save(&DataPack_t) and  DATA_Read(&DataPack_t) to access flash
 *
 *           Please notice the following issue
 *           (1) Max data length in UserData_t is 256 byte.
 *           (2) User defined data type in DataPack_t.UserData_t
 *           (3) Please initial DataPack_t with DATA_AccessInit() before Data save/read
 *               Be sure to assign correct Flash address(32 bit) that align with the start of the sector
 *           (4) User can use DATA_Save(&DataPack_t) and  DATA_Read(&DataPack_t) to access flash
 *           (5) We also provide a basic byte series(array) function to access flash.
 *           (6) Every 15 times save, it will cost more about 27ms to erase sector
 *
 *
 * @note
 * Copyright (C) 2022 Spintrol Electronic Technology (Shanghai) Co., Ltd.. All rights reserved.
*****************************************************************************/



#include "data_manage.h"
#include<stdio.h>

struct ManagementData_t gEmulationData;

/******************************************************************************
 * @brief      Initializes the control structure that used in data management
 *
 * @param[in]  u32StartAddr   : eeprom emulation start address, this value should
 *                       be align with 0x200.
 * @param[in]  u32EndAddr           : eeprom emulation end address.this value should
 *                       be align with 0x200.
 * @param[in]  u32UserDataSize: size of user data.
 *
 * @return Success Flag     : SUCCESS = Init Success
 *                            ERROR = Init Error
 *
 *******************************************************************************/
ErrorStatus EEPROM_Init(uint32_t u32StartAddr, uint32_t u32EndAddr, uint32_t u32UserDataSize)
{
    ErrorStatus ret = SUCCESS;
    int32_t addr = -1;
    uint16_t sectors = (u32EndAddr - u32StartAddr) / FLASH_SECTOR_SIZE;
    uint16_t indexlinecnt = FLASH_SECTOR_SIZE / 4;

    if (u32StartAddr % FLASH_SECTOR_SIZE != 0) //start address should be align with 0x200
    {
        ret = ERROR;
        return ret;
    }

    if (u32EndAddr % FLASH_SECTOR_SIZE != 0) //end address should be align with 0x200
    {
        ret = ERROR;
        return ret;
    }

    if (u32EndAddr - u32StartAddr < 2 * FLASH_SECTOR_SIZE)  //Emulation region size should be large
    {
        //than 2 sector.
        ret = ERROR;
        return ret;
    }

    if (u32EndAddr > FLASH_END_ADDR)
    {
        ret = ERROR;
        return ret;
    }

    //try to split index region and data region
    gEmulationData.u16IndexLineExSectors = 1;
    while (gEmulationData.u16IndexLineExSectors * indexlinecnt < (sectors - gEmulationData.u16IndexLineExSectors))
    {
        gEmulationData.u16IndexLineExSectors++;

        if (gEmulationData.u16IndexLineExSectors > (sectors - 1))
        {
            return ERROR;
        }
    }

    gEmulationData.u16UserDataAlignSize = u32UserDataSize + ((u32UserDataSize % 4 == 0) ? 0 : (4 - u32UserDataSize % 4));
    gEmulationData.u32IndexStartAddr = u32StartAddr;
    gEmulationData.u32DataStartAddr = u32StartAddr + gEmulationData.u16IndexLineExSectors * FLASH_SECTOR_SIZE;

    gEmulationData.u32DataEndAddr = u32EndAddr;
    gEmulationData.u16OneUserDataExSectors = u32UserDataSize / FLASH_SECTOR_SIZE +
            ((u32UserDataSize % FLASH_SECTOR_SIZE) == 0 ? 0 : 1);
    gEmulationData.u32IndexLineCnt = (gEmulationData.u32DataEndAddr - gEmulationData.u32DataStartAddr) / (gEmulationData.u16OneUserDataExSectors * FLASH_SECTOR_SIZE);

    //clear all regions
    for (addr = u32StartAddr; addr < u32EndAddr; addr += FLASH_SECTOR_SIZE)
    {
        ret = FLASH_EraseSector(addr);
        if (ret == ERROR)
        {
            printf("flash erase fail\n");
            return ERROR;
        }
    }

    return ret;
}


/******************************************************************************
 * @brief      Read data index from index region sector
 *
 * @param[in]  none
 *
* @return     index : indecate the clean region
 *
 *******************************************************************************/
int32_t EERPROM_GetIdleIndex()
{
    uint32_t au32ReadBuffer = FLASH_DURTY_FLAG << 16 | 0;
    int32_t count = 0;
    BOOL found = FALSE;
    ErrorStatus EStatus;

    for (count = 0; count < gEmulationData.u32IndexLineCnt; count++)
    {
        EStatus = FLASH_Read(&au32ReadBuffer, gEmulationData.u32IndexStartAddr + (count * 4), 1);
        if (EStatus == ERROR)
        {
            printf("flash read fail\n");
            return ERROR;
        }

        if ((au32ReadBuffer >> 16) != FLASH_DURTY_FLAG)
        {
            found = TRUE;
            break;
        }
    }

    if (found == FALSE)
    {
        count = 0xFFFF;
    }

    return count;
}

/******************************************************************************
 * @brief      Set data index
 *
 * @param[in]  none
 *
 * @return Success Flag     : SUCCESS = Init Success
 *                            ERROR = Init Error
 *
 *******************************************************************************/
ErrorStatus EERPROM_SetIndexDurty(uint16_t index)
{
    ErrorStatus EStatus;

    EStatus = FLASH_ProgramWord((FLASH_DURTY_FLAG << 16) | index,
                                gEmulationData.u32IndexStartAddr + (index * 4));

    if (EStatus == ERROR)
    {
        return ERROR;    //Error
    }
    else
    {
        return SUCCESS;    //SUCCESS
    }
}


/******************************************************************************
 * @brief      Save data into flash at corresponding position(index)
 *
 * @param[in]  UserData : Pointer point to User data struct
 *
 * @return Success Flag     : SUCCESS = Init Success
 *                            ERROR = Init Error
 *
 *******************************************************************************/
ErrorStatus EEPROM_Write(struct UserData_t *UserData)
{
    ErrorStatus EStatus = SUCCESS;
    uint32_t cnt = 0;
    uint32_t index = EERPROM_GetIdleIndex();
    uint32_t words = gEmulationData.u16UserDataAlignSize / 4;

    if (index == 0xFFFF)
    {
        //all emulation region is durty, clean the whole index region
        for (cnt = 0; cnt < gEmulationData.u16IndexLineExSectors; cnt++)
        {
            EStatus = FLASH_EraseSector(gEmulationData.u32IndexStartAddr + cnt * FLASH_SECTOR_SIZE);
            if (EStatus == ERROR)
            {
                printf("flash erase fail\n");
                return ERROR;
            }
        }
        //clean the whole data region
        for (cnt = gEmulationData.u32DataStartAddr; cnt < gEmulationData.u32DataEndAddr;
                cnt += FLASH_SECTOR_SIZE)
        {
            EStatus = FLASH_EraseSector(cnt);
            if (EStatus == ERROR)
            {
                printf("flash erase fail\n");
                return ERROR;
            }
        }
        //try to get index again
        index = EERPROM_GetIdleIndex();
    }

    EStatus = FLASH_Program((uint32_t *)UserData, gEmulationData.u32DataStartAddr +
                            index * gEmulationData.u16OneUserDataExSectors * FLASH_SECTOR_SIZE, words);
    if (EStatus == ERROR)
    {
        return EStatus;    //Error
    }


    EStatus = EERPROM_SetIndexDurty(index);
    if (EStatus == ERROR)
    {
        return EStatus;    //Error
    }

    return EStatus;
}

/******************************************************************************
 * @brief      Read user data
 *
 * @param[in]  UserData : Pointer point to memory where to hold user data
 *
 * @return Success Flag     : SUCCESS = Init Success
 *                            ERROR = Init Error
 *
 *******************************************************************************/
void EEPROM_Read(struct UserData_t *UserData)
{
    uint32_t words = gEmulationData.u16UserDataAlignSize / 4;
    uint32_t index = EERPROM_GetIdleIndex();
    ErrorStatus EStatus;

    if (index == 0xFFFF)
    {
        index = gEmulationData.u32IndexLineCnt;
    }

    EStatus = FLASH_Read((uint32_t *)UserData, gEmulationData.u32DataStartAddr +
               (index - 1) * gEmulationData.u16OneUserDataExSectors * FLASH_SECTOR_SIZE, words);
    if (EStatus == ERROR)
    {
        printf("flash read fail\n");
        return;
    }
}

/*** (C) COPYRIGHT 2022 Spintrol Technology Corp. ***/
