#include "EEPROM.h"
#include "mik32_hal_eeprom.h"
#include "mik32_hal.h"

HAL_EEPROM_HandleTypeDef heeprom;

void EEPROMClass:: begin()
{
    heeprom.Instance        = EEPROM_REGS;
    heeprom.Mode            = HAL_EEPROM_MODE_TWO_STAGE;
    heeprom.ErrorCorrection = HAL_EEPROM_ECC_ENABLE;
    heeprom.EnableInterrupt = HAL_EEPROM_SERR_DISABLE;

    HAL_EEPROM_Init(&heeprom);
    HAL_EEPROM_CalculateTimings(&heeprom, OSC_SYSTEM_VALUE);
}

uint8_t read_byte( int idx )
{
    // check if idx is valid
    if (idx < 0)
    {
        ErrorMsgHandler("EEPROM.read(): The eeprom cell address must be non-negative");
        return 0xFF;
    }
    if ((uint32_t)idx >= (uint32_t)EEPROM_LENGTH)
    {
        ErrorMsgHandler("EEPROM.read(): The address of the eeprom cell goes beyond the eeprom");
        return 0xFF;
    }

    uint32_t read_data_buf[EEPROM_PAGE_WORDS] = {};

    // calc start address of the desired page
    uint32_t addr = EEPROM_START_ADDR + (((uint32_t)idx) / EEPROM_PAGE_SIZE) * EEPROM_PAGE_SIZE;
    // read desired page
    HAL_EEPROM_Read(&heeprom, (uint16_t)addr, read_data_buf, EEPROM_PAGE_WORDS, EEPROM_OP_TIMEOUT);
    // address of the searched word in eeprom: EEPROM_START_ADDR + (uint32_t)idx
    uint32_t word_addr = EEPROM_START_ADDR + (((uint32_t)idx) / EEPROM_WORD_SIZE) * EEPROM_WORD_SIZE;
    // searched word index in the read_data_buf array: the word address in the eeprom minus the page beginning address 
    uint32_t word_idx  = (word_addr - addr) / EEPROM_WORD_SIZE;
    // byte number in a word
    uint32_t byte_idx  = ((uint32_t)idx) % EEPROM_WORD_SIZE;
    // get desired byte from page data
    return (uint8_t)(( read_data_buf[word_idx] & (((uint32_t)0xFF) << ((EEPROM_WORD_SIZE - byte_idx - 1) * 8)) ) >> ((EEPROM_WORD_SIZE - byte_idx - 1) * 8));
}

void write_byte( int idx, uint8_t val )
{
    // check if idx is valid
    if (idx < 0)
    {
        ErrorMsgHandler("EEPROM.write(): The eeprom cell address must be non-negative");
        return;
    }
    if ((uint32_t)idx >= (uint32_t)EEPROM_LENGTH)
    {
        ErrorMsgHandler("EEPROM.write(): The address of the eeprom cell goes beyond the eeprom");
        return;
    }
    update_byte(idx, val);
}

void update_byte( int idx, uint8_t val )
{
    // check if idx is valid
    if (idx < 0)
    {
        ErrorMsgHandler("EEPROM.update(): The eeprom cell address must be non-negative");
        return;
    }
    if ((uint32_t)idx >= (uint32_t)EEPROM_LENGTH)
    {
        ErrorMsgHandler("EEPROM.update(): The address of the eeprom cell goes beyond the eeprom");
        return;
    }

    uint32_t write_data_buf[EEPROM_PAGE_WORDS] = {};

    // calc start address of the desired page
    uint32_t addr = EEPROM_START_ADDR + (((uint32_t)idx) / EEPROM_PAGE_SIZE) * EEPROM_PAGE_SIZE;
    // read desired page
    HAL_EEPROM_Read(&heeprom, (uint16_t)addr, write_data_buf, EEPROM_PAGE_WORDS, EEPROM_OP_TIMEOUT);
    // address of the searched word in eeprom: EEPROM_START_ADDR + (uint32_t)idx
    uint32_t word_addr = EEPROM_START_ADDR + (((uint32_t)idx) / EEPROM_WORD_SIZE) * EEPROM_WORD_SIZE;
    // searched word index in the write_data_buf array: the word address in the eeprom minus the page beginning address 
    uint32_t word_idx  = (word_addr - addr) / EEPROM_WORD_SIZE;
    // byte number in a word
    uint32_t byte_idx  = ((uint32_t)idx) % EEPROM_WORD_SIZE;
    // get desired byte
    uint32_t shift = (EEPROM_WORD_SIZE - byte_idx - 1) * 8;
    uint32_t byte = ((uint32_t)val) << shift;
    uint8_t oldVal = (uint8_t)((write_data_buf[word_idx] >> shift) & 0xFF); 
    // checking if written byte is different from the new one
    if(oldVal != val)
    {  
        // clear page
        HAL_EEPROM_Erase(&heeprom, (uint16_t)addr, EEPROM_PAGE_WORDS, HAL_EEPROM_WRITE_SINGLE, EEPROM_OP_TIMEOUT);
        // get and replace the desired byte
        write_data_buf[word_idx] = (write_data_buf[word_idx] & (~((uint32_t)(0xFF) << shift))) | byte;

        HAL_EEPROM_Write(&heeprom, (uint16_t)addr, write_data_buf, EEPROM_PAGE_WORDS, HAL_EEPROM_WRITE_SINGLE, EEPROM_OP_TIMEOUT);
    }
}


void HAL_read(uint16_t addr, uint32_t * data)
{
    HAL_EEPROM_Read(&heeprom, (uint16_t)addr, data, EEPROM_PAGE_WORDS, EEPROM_OP_TIMEOUT);
}

void HAL_write(uint16_t addr, uint32_t * data)
{
    HAL_EEPROM_Write(&heeprom, (uint16_t)addr, data, EEPROM_PAGE_WORDS, HAL_EEPROM_WRITE_SINGLE, EEPROM_OP_TIMEOUT);
}

void HAL_erase(uint16_t addr)
{
    HAL_EEPROM_Erase(&heeprom, (uint16_t)addr, EEPROM_PAGE_WORDS, HAL_EEPROM_WRITE_SINGLE, EEPROM_OP_TIMEOUT);
}