/** \file
 *  \brief This file contains functions that manage the on-chip EEPROM.
 *
 */

/* Include Files */
#include "KTag.h"

/* Local Definitions and Constants */

/* External Variables [Only if necessary!] */

/* External Function Prototypes [Only if necessary!] */

/* Public Variables */

//! Mutex controlling access to the EEPROM to ensure data/CRC integrity.
SemaphoreHandle_t xSemaphoreOnChipEEPROMLock;

TaskHandle_t NVM_OnChipEEPROM_Task_Handle;

volatile bool NVM_IsOnChipEEPROMInitialized = false;

/* Private Variables */

static QueueHandle_t xQueueOnChipEEPROM;

#if CY_PSOC4
const uint8_t Emulated_EEPROM_Storage[On_Chip_Emulated_EEPROM_PHYSICAL_SIZE]
__ALIGNED(CY_FLASH_SIZEOF_ROW) = {0u};
#endif // CY_PSOC4

/* Private Function Prototypes */

/* Inline Functions */

#if CY_PSOC4
//! Reads a block of \a n bytes from EEPROM address \a source to SRAM \a destination.
static inline void EEPROM_read_block(uint8_t * destination, uint16_t source, size_t n)
{
    On_Chip_Emulated_EEPROM_Read(source, destination, n);
}

//! Writes a block of \a n bytes from SRAM \a source to EEPROM address \a destination.
static inline void EEPROM_write_block(uint8_t * source, uint16_t destination, size_t n)
{
    On_Chip_Emulated_EEPROM_Write(destination, source, n);
}
#endif // CY_PSOC4

#if CY_PSOC5
//! Reads a block of \a n bytes from EEPROM address \a source to SRAM \a destination.
static inline void EEPROM_read_block(uint8_t * destination, uint16_t source, size_t n)
{
    for (uint_fast16_t i = 0; i < n; i++)
    {
        uint8_t temp = On_Chip_EEPROM_ReadByte(source + i);
        *(destination + i) = temp;
    }
}

//! Writes a block of \a n bytes from SRAM \a source to EEPROM address \a destination.
static inline void EEPROM_write_block(uint8_t * source, uint16_t destination, size_t n)
{
    for (uint_fast16_t i = 0; i < n; i++)
    {
        On_Chip_EEPROM_WriteByte(*(source + i), destination + i);
    }
}
#endif // CY_PSOC5

#if CY_PSOC6
//! Reads a block of \a n bytes from EEPROM address \a source to SRAM \a destination.
static inline void EEPROM_read_block(uint8_t * destination, uint16_t source, size_t n)
{
    cy_en_em_eeprom_status_t result = On_Chip_EEPROM_Read(source, destination, n);
    
    if (result != CY_EM_EEPROM_SUCCESS)
    {
        CY_ASSERT(0);
    }
}

//! Writes a block of \a n bytes from SRAM \a source to EEPROM address \a destination.
static inline void EEPROM_write_block(uint8_t * source, uint16_t destination, size_t n)
{
    cy_en_em_eeprom_status_t result = On_Chip_EEPROM_Write(destination, source, n);
    
    if (result != CY_EM_EEPROM_SUCCESS)
    {
        CY_ASSERT(0);
    }
}
#endif // CY_PSOC6

/* Public Functions */

//! Sets up the on-chip EEPROM, but does not read from it (yet).
void NVM_InitOnChipEEPROM(void)
{
    /// Create a mutex-type semaphore.
    xSemaphoreOnChipEEPROMLock = xSemaphoreCreateMutex();

    if (xSemaphoreOnChipEEPROMLock == NULL)
    {
       CY_ASSERT(0);
    }
    
    xQueueOnChipEEPROM = xQueueCreate(5, sizeof(uint8_t));

#if CY_PSOC4
    On_Chip_Emulated_EEPROM_Init((uint32_t)Emulated_EEPROM_Storage);
#endif // CY_PSOC4   

#if CY_PSOC5    
    On_Chip_EEPROM_Start();
#endif // CY_PSOC5

#if CY_PSOC6
    // From the docs: "For PSoC 6, if Emulated EEPROM is selected for EEPROM storage, the start address will be
    //  overwritten to some address from Emulated EEPROM flash area."
    On_Chip_EEPROM_Init(0);
#endif // CY_PSOC6
}

//! Handles the ongoing on-chip EEPROM tasks.
/*!
 *  First, it loops through all the on-chip EEPROM entries, and reads them in to RAM.
 *  Then, it priodically loops through all the on-chip EEPROM entries, and saves the ones that have been flagged.
 */
void NVM_OnChipEEPROMTask(void * pvParameters)
{
    portBASE_TYPE xStatus;
    static TickType_t xTicksToWait = pdMS_TO_TICKS(NVM_ON_CHIP_EEPROM_TASK_RATE_IN_ms);
    
    for (uint8_t i = 0; i < NVM_N_ONCHIP_EEPROM_ENTRIES; i++)
    {
        NVM_CRC_t calculated_crc;
        NVM_CRC_t stored_crc = 0;
  
        EEPROM_read_block(NVM_OnChipEEPROMEntries[i]->Value, NVM_OnChipEEPROMEntries[i]->EE_Address, NVM_OnChipEEPROMEntries[i]->Size);
        EEPROM_read_block((uint8_t *)&stored_crc, NVM_OnChipEEPROMEntries[i]->EE_CRC_Address, sizeof(NVM_CRC_t)); 
        
        calculated_crc = NVM_CRC_init();
        calculated_crc = NVM_CRC_update(calculated_crc, NVM_OnChipEEPROMEntries[i]->Value, NVM_OnChipEEPROMEntries[i]->Size);
        calculated_crc = NVM_CRC_finalize(calculated_crc);
        
        if (calculated_crc == stored_crc)
        {
            NVM_OnChipEEPROMEntries[i]->State = NVM_STATE_IDLE;
        }
        else
        {
            NVM_OnChipEEPROMEntries[i]->State = NVM_STATE_CRC_FAILED;
            
            COMM_Console_Print_String("[NVMOn ");
            COMM_Console_Print_UInt16((uint16_t) i);
            COMM_Console_Print_String("] Calculated/Stored CRCs: ");
            COMM_Console_Print_UInt16((uint16_t) calculated_crc);
            COMM_Console_Print_String("/");
            COMM_Console_Print_UInt16((uint16_t) stored_crc);
            COMM_Console_Print_String("\n");
			
            COMM_Console_Print_String("[NVMOn ");
            COMM_Console_Print_UInt16((uint16_t) i);
            COMM_Console_Print_String("] Applying defaults.\n");
			
			memcpy(NVM_OnChipEEPROMEntries[i]->Value, NVM_OnChipEEPROMEntries[i]->Default, NVM_OnChipEEPROMEntries[i]->Size);

            // Auto-fix the CRC.
            NVM_SaveOnChipEEPROMEntry(NVM_OnChipEEPROMEntries[i]);
        }
    }
    
    taskENTER_CRITICAL();
    NVM_IsOnChipEEPROMInitialized = true;
    taskEXIT_CRITICAL();
    
    while(true)
    {
        uint8_t dummy;
        
        // Wait for a call to NVM_SaveOnChipEEPROMEntry().
        xStatus = xQueueReceive(xQueueOnChipEEPROM, &dummy, xTicksToWait);
        
        if (xStatus == pdPASS)
        {
            for (uint8_t i = 0; i < NVM_N_ONCHIP_EEPROM_ENTRIES; i++)
            {
                NVM_CRC_t crc;
                
#if (defined CY_PSOC4) || (defined CY_PSOC6)
                if (NVM_OnChipEEPROMEntries[i]->State == NVM_STATE_SAVE_REQUESTED)
                {
                    if (xSemaphoreTake(xSemaphoreOnChipEEPROMLock, ( TickType_t ) 1000) == pdTRUE)
                    {
                        EEPROM_write_block(NVM_OnChipEEPROMEntries[i]->Value, NVM_OnChipEEPROMEntries[i]->EE_Address, NVM_OnChipEEPROMEntries[i]->Size);
                        
                        // Calculate the CRC.
                        crc = NVM_CRC_init();
                        crc = NVM_CRC_update(crc, NVM_OnChipEEPROMEntries[i]->Value, NVM_OnChipEEPROMEntries[i]->Size);
                        crc = NVM_CRC_finalize(crc);
                        
                        EEPROM_write_block((uint8_t *)&crc, NVM_OnChipEEPROMEntries[i]->EE_CRC_Address, sizeof(uint16_t));
                        NVM_OnChipEEPROMEntries[i]->State = NVM_STATE_IDLE;
                        xSemaphoreGive(xSemaphoreOnChipEEPROMLock);
                    }
                }
#endif // (defined CY_PSOC4) || (defined CY_PSOC6)

#if CY_PSOC5
                // From the component datasheet:
                //    "[On_Chip_EEPROM_UpdateTemperature()] updates the store temperature value. This should
                //     be called anytime the EEPROM is active and temperature may have changed by more than
                //     10°C."
                if (On_Chip_EEPROM_UpdateTemperature() == CYRET_SUCCESS)
                {
                    if (NVM_OnChipEEPROMEntries[i]->State == NVM_STATE_SAVE_REQUESTED)
                    {
                        if (On_Chip_EEPROM_Query() == CYRET_SUCCESS)
                        {
                            if (xSemaphoreTake(xSemaphoreOnChipEEPROMLock, ( TickType_t ) 1000) == pdTRUE)
                            {
                                EEPROM_write_block(NVM_OnChipEEPROMEntries[i]->Value, NVM_OnChipEEPROMEntries[i]->EE_Address, NVM_OnChipEEPROMEntries[i]->Size);
                        
                                // Calculate the CRC.
                                crc = NVM_CRC_init();
                                crc = NVM_CRC_update(crc, NVM_OnChipEEPROMEntries[i]->Value, NVM_OnChipEEPROMEntries[i]->Size);
                                crc = NVM_CRC_finalize(crc);
                                
                                EEPROM_write_block((uint8_t *)&crc, NVM_OnChipEEPROMEntries[i]->EE_CRC_Address, sizeof(uint16_t));
                                NVM_OnChipEEPROMEntries[i]->State = NVM_STATE_IDLE;
                                xSemaphoreGive(xSemaphoreOnChipEEPROMLock);
                            }
                        }
                    }
                }
                else
                {
                    vSerialPutString("ERROR: Couldn't update EEPROM temperature!", 80);
                }
#endif // CY_PSOC5
            }
        }
    }
}

//! Flags the given on-chip EEPROM entry to be saved next time the NVM_OnChipEEPROMTask() is run.
void NVM_SaveOnChipEEPROMEntry(NVM_EEPROMEntry_T * const this)
{
    if (xSemaphoreTake(xSemaphoreOnChipEEPROMLock, ( TickType_t ) 1000) == pdTRUE)
    {
        this->State = NVM_STATE_SAVE_REQUESTED;
        xSemaphoreGive(xSemaphoreOnChipEEPROMLock);
        uint8_t dummy = 0;
        xQueueSend(xQueueOnChipEEPROM, &dummy, 0);
    }
}

/* Private Functions */