/**
  ******************************************************************************
  * @file    emulated_eeprom.c
  * @author  Kiwi Software Team
  * @brief   Header file of emualted eeprom module.
  * @note
  *          V1.0.0,  2021/11/15, use data flash to emulate EEPROM
  *
  * Copyright (c) 2024, Kiwi Instruments Co,. Ltd.
  *
  * Redistribution and use in source and binary forms, with or without modification,
  * are permitted provided that the following conditions are met:
  *
  *   1. Redistributions of source code must retain the above copyright notice,
  *      this list of conditions and the following disclaimer.
  *
  *   2. Redistributions in binary form must reproduce the above copyright notice,
  *      this list of conditions and the following disclaimer in the documentation
  *      and/or other materials provided with the distribution.
  *
  *   3. Neither the name of the copyright holder nor the names of its contributors
  *      may be used to endorse or promote products derived from this software without
  *      specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
  * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
  * THE POSSIBILITY OF SUCH DAMAGE.
  ******************************************************************************
  */

#include "emulated_eeprom_16bitAddr.h"


/* No valid page define */
#define NO_VALID_PAGE         ((uint16_t)0xABAB)

/* Page status definitions */
#define ERASED                ((uint16_t)0xFFFF)     /* Page is empty */
#define RECEIVE_DATA          ((uint16_t)0xFF00)     /* Page is marked to receive data */
#define VALID_PAGE            ((uint16_t)0x0000)     /* Page containing valid data */

/* Valid pages in read and write defines */
#define READ_FROM_VALID_PAGE  ((uint8_t)0x00)
#define WRITE_TO_VALID_PAGE   ((uint8_t)0x01)

/* Page full define */
#define PAGE_FULL             ((uint16_t)0xFFFF)



/* Global variable used to store variable value in read sequence */
uint16_t g_Data = 0;

/* Virtual address defined by the user: 0xFFFF value is prohibited */
extern uint16_t g_VirtAddTab[VAR_NUM];


static DDL_Status_T EEPROM_Format(uint16_t bank);
static uint16_t EEPROM_FindValidPage(uint16_t bank, uint8_t operation);
static uint16_t EEPROM_VerifyPageFullWriteVariable(uint16_t bank, uint16_t virtAddress, uint16_t data);
static uint16_t EEPROM_PageTransfer(uint16_t bank, uint16_t virtAddress, uint16_t data);
static uint16_t EEPROM_VerifyPageFullyErased(uint32_t address);


/**
  * @brief  Restore the pages to a known good state in case of page's status
  *         corruption after a power loss.
  * @param  bank  this parameter can be one of the following values:
  *               @arg BANK0: data flash sector 0 and sector 1
  *               @arg BANK1: data flash sector 2 and sector 3
  * @retval - Flash error code: on write Flash error
  *         - FLASH_COMPLETE: on success
  */
uint16_t EEPROM_Init(uint16_t bank)
{
	uint16_t pageStatus0 = 6, pageStatus1 = 6;
	uint16_t varIdx = 0;
	uint16_t eepromStatus = 0, readStatus = 0;
	int16_t x = -1;
	DDL_Status_T  flashStatus;
	uint32_t page0BaseAddr = 0, page1BaseAddr = 0;
	uint32_t page0ID = 0, page1ID = 0;

	if (bank == BANK0)
	{
		page0BaseAddr = PAGE0_BASE_ADDRESS;
		page1BaseAddr = PAGE1_BASE_ADDRESS;
		page0ID = PAGE0_ID;
		page1ID = PAGE1_ID;
	}
	else
	{
		page0BaseAddr = PAGE2_BASE_ADDRESS;
		page1BaseAddr = PAGE3_BASE_ADDRESS;
		page0ID = PAGE2_ID;
		page1ID = PAGE3_ID;
	}

	pageStatus0 = (*(__IO uint16_t*)page0BaseAddr);
	pageStatus1 = (*(__IO uint16_t*)page1BaseAddr);

	/* Check for invalid header states and repair if necessary */
	switch (pageStatus0)
	{
	case ERASED:
		if (pageStatus1 == VALID_PAGE) /* Page0 erased, Page1 valid */
		{
			if(!EEPROM_VerifyPageFullyErased(page0BaseAddr))
			{
				flashStatus = DDL_FLASH_EraseSector(page0ID);
				if (flashStatus != DDL_OK)
				{
					return flashStatus;
				}
			}
		}
		else if (pageStatus1 == RECEIVE_DATA) /* Page0 erased, Page1 receive */
		{
			if(!EEPROM_VerifyPageFullyErased(page0BaseAddr))
			{
				flashStatus = DDL_FLASH_EraseSector(page0ID);
				if (flashStatus != DDL_OK)
				{
					return flashStatus;
				}
			}

			flashStatus = DDL_FLASH_ProgramHalfWord(page1BaseAddr, VALID_PAGE);
			if (flashStatus != DDL_OK)
			{
				return flashStatus;
			}
		}
		else /* First EEPROM access (Page0&1 are erased) or invalid state -> format EEPROM */
		{
			flashStatus = EEPROM_Format(bank);
			if (flashStatus != DDL_OK)
			{
				return flashStatus;
			}
		}
		break;

	case RECEIVE_DATA:
		if (pageStatus1 == VALID_PAGE) /* Page0 receive, Page1 valid */
		{
			/* Transfer data from Page1 to Page0 */
			for (varIdx = 0; varIdx < VAR_NUM; varIdx++)
			{
				if (( *(__IO uint16_t*)(page0BaseAddr + 6)) == g_VirtAddTab[varIdx])
				{
					x = varIdx;
				}
				if (varIdx != x)
				{
					/* Read the last variables' updates */
					readStatus = EEPROM_Read(bank, g_VirtAddTab[varIdx], &g_Data);
					if (readStatus != 0x1)
					{
						/* Transfer the variable to the Page0 */
						eepromStatus = EEPROM_VerifyPageFullWriteVariable(bank, g_VirtAddTab[varIdx], g_Data);
						if (eepromStatus != DDL_OK)
						{
							return eepromStatus;
						}
					}
				}
			}

			flashStatus = DDL_FLASH_ProgramHalfWord(page0BaseAddr, VALID_PAGE);
			if (flashStatus != DDL_OK)
			{
				return flashStatus;
			}

			if(!EEPROM_VerifyPageFullyErased(page1BaseAddr))
			{
				flashStatus = DDL_FLASH_EraseSector(page1ID);
				if (flashStatus != DDL_OK)
				{
					return flashStatus;
				}
			}
		}
		else if (pageStatus1 == ERASED) /* Page0 receive, Page1 erased */
		{
			if(!EEPROM_VerifyPageFullyErased(page1BaseAddr))
			{

				flashStatus = DDL_FLASH_EraseSector(page1ID);
				if (flashStatus != DDL_OK)
				{
					return flashStatus;
				}
			}

			flashStatus = DDL_FLASH_ProgramHalfWord(page0BaseAddr, VALID_PAGE);
			if (flashStatus != DDL_OK)
			{
				return flashStatus;
			}
		}
		else /* Invalid state -> format eeprom */
		{
			flashStatus = EEPROM_Format(bank);
			if (flashStatus != DDL_OK)
			{
				return flashStatus;
			}
		}
		break;

	case VALID_PAGE:
		if (pageStatus1 == VALID_PAGE) /* Invalid state -> format eeprom */
		{
			flashStatus = EEPROM_Format(bank);
			if (flashStatus != DDL_OK)
			{
				return flashStatus;
			}
		}
		else if (pageStatus1 == ERASED) /* Page0 valid, Page1 erased */
		{
			if(!EEPROM_VerifyPageFullyErased(page1BaseAddr))
			{
				flashStatus = DDL_FLASH_EraseSector(page1ID);
				if (flashStatus != DDL_OK)
				{
					return flashStatus;
				}
			}
		}
		else /* Page0 valid, Page1 receive */
		{
			/* Transfer data from Page0 to Page1 */
			for (varIdx = 0; varIdx < VAR_NUM; varIdx++)
			{
				if ((*(__IO uint16_t*)(page1BaseAddr + 6)) == g_VirtAddTab[varIdx])
				{
					x = varIdx;
				}
				if (varIdx != x)
				{
					/* Read the last variables' updates */
					readStatus = EEPROM_Read(bank, g_VirtAddTab[varIdx], &g_Data);
					if (readStatus != 0x1)
					{
						/* Transfer the variable to the Page1 */
						eepromStatus = EEPROM_VerifyPageFullWriteVariable(bank, g_VirtAddTab[varIdx], g_Data);
						if (eepromStatus != DDL_OK)
						{
							return eepromStatus;
						}
					}
				}
			}

			flashStatus = DDL_FLASH_ProgramHalfWord(page1BaseAddr, VALID_PAGE);
			if (flashStatus != DDL_OK)
			{
				return flashStatus;
			}

			if(!EEPROM_VerifyPageFullyErased(page0BaseAddr))
			{
				flashStatus = DDL_FLASH_EraseSector(page0ID);
				if (flashStatus != DDL_OK)
				{
					return flashStatus;
				}
			}
		}
		break;

	default:  /* Any other state -> format eeprom */
		flashStatus = EEPROM_Format(bank);
		if (flashStatus != DDL_OK)
		{
			return flashStatus;
		}
		break;
	}

	return DDL_OK;
}


/**
  * @brief  Verify if specified page is fully erased.
  * @param  address  page start address
  * @retval page fully erased status:
  *           - 0: if Page not erased
  *           - 1: if Page erased
  */
uint16_t EEPROM_VerifyPageFullyErased(uint32_t address)
{
	uint32_t readStatus = 1;
	uint16_t addressValue = 0x5555;
	uint32_t pageEndAddr = address + PAGE_SIZE - 1;

	while (address <= pageEndAddr)
	{
		addressValue = (*(__IO uint16_t*)address);
		if (addressValue != ERASED)
		{
			readStatus = 0;
			break;
		}

		address = address + 4;
	}

	return readStatus;
}


/**
  * @brief  Returns the last stored variable data, if found, which correspond to
  *         the passed virtual address
  * @param  bank         Specify which bank to use
  * @param  virtAddress  Variable virtual address
  * @param  data         Global variable contains the read variable value
  * @retval Success or error status:
  *           - 0: if variable was found
  *           - 1: if the variable was not found
  *           - NO_VALID_PAGE: if no valid page was found.
  */
uint16_t EEPROM_Read(uint16_t bank, uint16_t virtAddress, uint16_t* data)
{
	uint16_t validPage = PAGE0;
	uint16_t addressValue = 0x5555, readStatus = 1;
	uint32_t address = EEPROM_START_ADDRESS, pageStartAddress = EEPROM_START_ADDRESS;

	/* Get active Page for read operation */
	validPage = EEPROM_FindValidPage(bank, READ_FROM_VALID_PAGE);
	if (validPage == NO_VALID_PAGE)
	{
		return  NO_VALID_PAGE;
	}

	pageStartAddress = (uint32_t)(EEPROM_START_ADDRESS + (uint32_t)(validPage * PAGE_SIZE));
	address = (uint32_t)((EEPROM_START_ADDRESS - 2) + (uint32_t)((1 + validPage) * PAGE_SIZE));

	/* Check each active page address starting from end */
	while (address > (pageStartAddress + 2))
	{
		addressValue = (*(__IO uint16_t*)address);
		if (addressValue == virtAddress)
		{
			*data = (*(__IO uint16_t*)(address - 2));
			readStatus = 0;

			break;
		}
		else
		{
			address = address - 4;
		}
	}

	return readStatus;
}


/**
  * @brief  Writes/updates variable data in EEPROM.
  * @param  bank         BANK0 or BANK1
  * @param  virtAddress  Variable virtual address
  * @param  data         16 bit data to be written
  * @retval Success or error status:
  *           - FLASH_COMPLETE: on success
  *           - PAGE_FULL: if valid page is full
  *           - NO_VALID_PAGE: if no valid page was found
  *           - Flash error code: on write Flash error
  */
uint16_t EEPROM_Write(uint16_t bank, uint16_t virtAddress, uint16_t data)
{
	uint16_t Status = 0;

	/* Write the variable virtual address and value in the EEPROM */
	Status = EEPROM_VerifyPageFullWriteVariable(bank, virtAddress, data);
	if (Status == PAGE_FULL)
	{
		Status = EEPROM_PageTransfer(bank, virtAddress, data);
	}

	return Status;
}


/**
  * @brief  Erases PAGE0 and PAGE1 and writes VALID_PAGE header to PAGE0 for the specify bank
  * @param  bank  BANK0 or BANK1
  * @retval Status of the last operation (Flash write or erase) done during
  *         EEPROM formating
  */
static DDL_Status_T EEPROM_Format(uint16_t bank)
{
	DDL_Status_T flashStatus = DDL_OK;
	uint32_t page0BaseAddr = 0, page1BaseAddr = 0;
	uint32_t page0ID = 0, page1ID = 0;

	if (bank == BANK0)
	{
		page0BaseAddr = PAGE0_BASE_ADDRESS;
		page1BaseAddr = PAGE1_BASE_ADDRESS;
		page0ID = PAGE0_ID;
		page1ID = PAGE1_ID;
	}
	else
	{
		page0BaseAddr = PAGE2_BASE_ADDRESS;
		page1BaseAddr = PAGE3_BASE_ADDRESS;
		page0ID = PAGE2_ID;
		page1ID = PAGE3_ID;
	}

	if(!EEPROM_VerifyPageFullyErased(page0BaseAddr))
	{
		flashStatus = DDL_FLASH_EraseSector(page0ID);
		if (flashStatus != DDL_OK)
		{
			return flashStatus;
		}
	}
	/* Set Page0 as valid page: Write VALID_PAGE at Page0 base address */
	flashStatus = DDL_FLASH_ProgramHalfWord(page0BaseAddr, VALID_PAGE);
	if (flashStatus != DDL_OK)
	{
		return flashStatus;
	}

	if(!EEPROM_VerifyPageFullyErased(page1BaseAddr))
	{
		flashStatus = DDL_FLASH_EraseSector(page1ID);
		if (flashStatus != DDL_OK)
		{
			return flashStatus;
		}
	}

	return DDL_OK;
}


/**
  * @brief  Find valid Page for write or read operation
  * @param  operation  operation to achieve on the valid page.
  * @param bank        BANK0 or BANK1
  * @retval Valid page number or NO_VALID_PAGE in case
  *   of no valid page was found
  */
static uint16_t EEPROM_FindValidPage(uint16_t bank, uint8_t operation)
{
	uint16_t pageStatus0 = 6, pageStatus1 = 6;
	uint32_t page0BaseAddr = 0, page1BaseAddr = 0;
	uint32_t page0 = 0, page1 = 0;

	if (bank == BANK0)
	{
		page0BaseAddr = PAGE0_BASE_ADDRESS;
		page1BaseAddr = PAGE1_BASE_ADDRESS;
		page0 = PAGE0;
		page1 = PAGE1;
	}
	else
	{
		page0BaseAddr = PAGE2_BASE_ADDRESS;
		page1BaseAddr = PAGE3_BASE_ADDRESS;
		page0 = PAGE2;
		page1 = PAGE3;
	}

	pageStatus0 = (*(__IO uint16_t*)page0BaseAddr);
	pageStatus1 = (*(__IO uint16_t*)page1BaseAddr);

	switch (operation)
	{
	case WRITE_TO_VALID_PAGE:
		if (pageStatus1 == VALID_PAGE)
		{
			/* Page0 receiving data */
			if (pageStatus0 == RECEIVE_DATA)
			{
				return page0;         /* Page0 valid */
			}
			else
			{
				return page1;         /* Page1 valid */
			}
		}
		else if (pageStatus0 == VALID_PAGE)
		{
			/* Page1 receiving data */
			if (pageStatus1 == RECEIVE_DATA)
			{
				return page1;         /* Page1 valid */
			}
			else
			{
				return page0;         /* Page0 valid */
			}
		}
		else
		{
			return NO_VALID_PAGE;   /* No valid Page */
		}

	case READ_FROM_VALID_PAGE:
		if (pageStatus0 == VALID_PAGE)
		{
			return page0;           /* Page0 valid */
		}
		else if (pageStatus1 == VALID_PAGE)
		{
			return page1;           /* Page1 valid */
		}
		else
		{
			return NO_VALID_PAGE ;  /* No valid Page */
		}

	default:
		return page0;             /* Page0 valid */
	}
}


/**
  * @brief  Verify if active page is full and Writes variable in EEPROM.
  * @param  bank         Bank0 or Bank1
  * @param  virtAddress  16 bit virtual address of the variable
  * @param  data         16 bit data to be written as variable value
  * @retval Success or error status:
  *           - FLASH_COMPLETE: on success
  *           - PAGE_FULL: if valid page is full
  *           - NO_VALID_PAGE: if no valid page was found
  *           - Flash error code: on write Flash error
  */
static uint16_t EEPROM_VerifyPageFullWriteVariable(uint16_t bank, uint16_t virtAddress, uint16_t data)
{
	DDL_Status_T flashStatus = DDL_OK;
	uint16_t validPage = PAGE0;
	uint32_t address = EEPROM_START_ADDRESS, pageEndAddress = EEPROM_START_ADDRESS + PAGE_SIZE;

	validPage = EEPROM_FindValidPage(bank, WRITE_TO_VALID_PAGE);
	if (validPage == NO_VALID_PAGE)
	{
		return  NO_VALID_PAGE;
	}

	address = (uint32_t)(EEPROM_START_ADDRESS + (uint32_t)(validPage * PAGE_SIZE));
	pageEndAddress = (uint32_t)((EEPROM_START_ADDRESS - 1) + (uint32_t)((validPage + 1) * PAGE_SIZE));

	/* Check each active page address starting from begining */
	while (address < pageEndAddress)
	{
		/* Verify if address and address+2 contents are 0xFFFFFFFF */
		if ((*(__IO uint32_t*)address) == 0xFFFFFFFF)
		{
			flashStatus = DDL_FLASH_ProgramHalfWord(address, data);
			if (flashStatus != DDL_OK)
			{
				return flashStatus;
			}

			flashStatus = DDL_FLASH_ProgramHalfWord(address + 2, virtAddress);
			return flashStatus;
		}
		else
		{
			address = address + 4;
		}
	}

	return PAGE_FULL;
}


/**
  * @brief  Transfers last updated variables data from the full Page to
  *   an empty one.
  * @param  bank         BANK0 or BANK1
  * @param  virtAddress  16 bit virtual address of the variable
  * @param  data         16 bit data to be written as variable value
  * @retval Success or error status:
  *           - FLASH_COMPLETE: on success
  *           - PAGE_FULL: if valid page is full
  *           - NO_VALID_PAGE: if no valid page was found
  *           - Flash error code: on write Flash error
  */
static uint16_t EEPROM_PageTransfer(uint16_t bank, uint16_t virtAddress, uint16_t data)
{
	DDL_Status_T flashStatus = DDL_OK;
	uint32_t newPageAddress = EEPROM_START_ADDRESS;
	uint16_t oldPageId=0;
	uint16_t validPage = PAGE0, varIdx = 0;
	uint16_t eepromStatus = 0, readStatus = 0;

	validPage = EEPROM_FindValidPage(bank, READ_FROM_VALID_PAGE);
	if (bank == BANK0)
	{
		if (validPage == PAGE1)       /* Page1 valid */
		{
			/* New page address where variable will be moved to */
			newPageAddress = PAGE0_BASE_ADDRESS;

			/* Old page ID where variable will be taken from */
			oldPageId = PAGE1_ID;
		}
		else if (validPage == PAGE0)  /* Page0 valid */
		{
			/* New page address  where variable will be moved to */
			newPageAddress = PAGE1_BASE_ADDRESS;

			/* Old page ID where variable will be taken from */
			oldPageId = PAGE0_ID;
		}
		else
		{
			return NO_VALID_PAGE;       /* No valid Page */
		}
	}
	else
	{
		if (validPage == PAGE2)       /* Page2 valid */
		{
			/* New page address where variable will be moved to */
			newPageAddress = PAGE3_BASE_ADDRESS;

			/* Old page ID where variable will be taken from */
			oldPageId = PAGE2_ID;
		}
		else if (validPage == PAGE3)  /* Page3 valid */
		{
			/* New page address  where variable will be moved to */
			newPageAddress = PAGE2_BASE_ADDRESS;

			/* Old page ID where variable will be taken from */
			oldPageId = PAGE3_ID;
		}
		else
		{
			return NO_VALID_PAGE;       /* No valid Page */
		}
	}

	flashStatus = DDL_FLASH_ProgramByte(newPageAddress, (uint8_t)RECEIVE_DATA);
	if (flashStatus != DDL_OK)
	{
		return flashStatus;
	}

	/* Write the variable passed as parameter in the new active page */
	eepromStatus = EEPROM_VerifyPageFullWriteVariable(bank, virtAddress, data);
	if (eepromStatus != DDL_OK)
	{
		return eepromStatus;
	}

	/* Transfer process: transfer variables from old to the new active page */
	for (varIdx = 0; varIdx < VAR_NUM; varIdx++)
	{
		if (g_VirtAddTab[varIdx] != virtAddress)  /* Check each variable except the one passed as parameter */
		{
			/* Read the other last variable updates */
			readStatus = EEPROM_Read(bank, g_VirtAddTab[varIdx], &data);
			if (readStatus != 0x1)
			{
				/* Transfer the variable to the new active page */
				eepromStatus = EEPROM_VerifyPageFullWriteVariable(bank, g_VirtAddTab[varIdx], data);
				if (eepromStatus != DDL_OK)
				{
					return eepromStatus;
				}
			}
		}
	}

	flashStatus = DDL_FLASH_EraseSector(oldPageId);
	if (flashStatus != DDL_OK)
	{
		return flashStatus;
	}

	flashStatus = DDL_FLASH_ProgramByte(newPageAddress + 1, VALID_PAGE);
	if (flashStatus != DDL_OK)
	{
		return flashStatus;
	}

	return flashStatus;
}


