/***
* @file BSP_EE24.c
* This file contains functions to interact with an EEPROM driver and perform
* read and write operations on it. The driver uses I2C protocol and supports
* multiple partitions, which can be used to store different types of data.
*/

#include "BSP_EE24.h"
#include "ee24.h"
#include "i2c.h"
#include "cmsis_os2.h"
#include "gpio.h"

#define BSP_EE24_I2C &hi2c2
#define BSP_EE24_ADDRESS EE24_ADDRESS_DEFAULT
#define BSP_EE24_TIMEOUT 500
#define BSP_EE24_MUTEXT_TIMEOUT osWaitForever

// Main Pariton Table
static BSP_EE24_Partion PartionTable[__BSP_EE24_PART_MAX__] = {
    [BSP_EE24_PART_HEAD_PARTION] = {
        .address = -1,
        .len = sizeof(BSP_EE24_TableHeader)
    },
    [BSP_EE24_PART_GLOBAL_LIGHT] = {
        .address = -1,
        .len = 64,
    },
    [BSP_EE24_PART_RESERVED] = {
        .address = -1,
        .len = 128,
    }
};
static const uint32_t PartionTableVersion = 1;
static EE24_HandleTypeDef hee24 = {0};
static osMutexId_t ee24_lock;

void BSP_EE24_TableMigrate(uint32_t ee_version);

/**
 * Initializes the EEPROM Partition Table.
 *
 * This function calculates the addresses of each element in the Partition Table
 * based on their lengths and the previous element's address. It assumes that the
 * Partition Table is ordered by ascending addresses, and that there are no gaps
 * between elements.
 */
void BSP_EE24_InitTable() {
    uint32_t address = 0;   // Start at address 0
    for (int i = 0; i < __BSP_EE24_PART_MAX__; i++) {
        if (PartionTable[i].len > 0) {
            PartionTable[i].address = address;
            address += PartionTable[i].len;
        }
    }
}


/**
 * Initializes the EEPROM driver and performs any necessary migrations.
 */
bool BSP_EE24_Init(void) {
    BSP_EE24_InitTable();
    // Initialize the EE24 driver with the I2C interface and address
    bool status = EE24_Init(&hee24,BSP_EE24_I2C,BSP_EE24_ADDRESS);
    if(!status) {
        return status;
    }

    // os mutext 
    ee24_lock = osMutexNew(NULL);

    // wc control high disables write
    HAL_GPIO_WritePin(EEPROM_WC_GPIO_Port, EEPROM_WC_Pin, GPIO_PIN_SET);
    
    // Read the Partition Table header from the EEPROM
    BSP_EE24_TableHeader header = {0};
    status = BSP_EE24_PartRead(BSP_EE24_PART_HEAD_PARTION, (uint8_t*)&header, sizeof(header));
    if(!status) {
        return status;
    }

    // Check if the Partition Table version is different from the expected version
    uint32_t fw_table_version = PartionTableVersion;
    uint32_t ee_table_version = header.version;
    if(ee_table_version != fw_table_version) {
        BSP_EE24_TableMigrate(ee_table_version);
        if(!status) {
            return status;
        }
    }

    return true;
}


/**
 * Reads data from the EEPROM.
 * This function is blocking for the operation to complete.
 *
 * @param[in]  Address   The starting address of the data to be read.
 * @param[out] Data      A pointer to a buffer where the data will be stored.
 * @param[in]  Len       The number of bytes to be read.
 * @return     true if the operation was successful, false otherwise.
 */
bool BSP_EE24_Read(uint32_t Address, uint8_t *Data, size_t Len)  {
    osMutexAcquire(ee24_lock,BSP_EE24_MUTEXT_TIMEOUT);
    bool status = EE24_Read(&hee24,Address,Data,Len,BSP_EE24_TIMEOUT);
    osMutexRelease(ee24_lock);
    return status;
}

/**
 * Writes data to the EEPROM.
 * This function is blocking for the operation to complete.
 * 
 * @param[in]  Address   The starting address of the data to be written.
 * @param[out] Data      A pointer to a buffer containing the data to be written.
 * @param[in]  Len       The number of bytes to be written.
 * @return     true if the operation was successful, false otherwise.
 */
bool BSP_EE24_Write(uint32_t Address, uint8_t *Data, size_t Len) {
    osMutexAcquire(ee24_lock,BSP_EE24_MUTEXT_TIMEOUT);
    HAL_GPIO_WritePin(EEPROM_WC_GPIO_Port, EEPROM_WC_Pin, GPIO_PIN_RESET); // low enables write;
    bool status =  EE24_Write(&hee24,Address,Data,Len,BSP_EE24_TIMEOUT);
    HAL_GPIO_WritePin(EEPROM_WC_GPIO_Port, EEPROM_WC_Pin, GPIO_PIN_SET); // high disables write;
    osMutexRelease(ee24_lock);
    return status;
}

/**
 * Reads data from a specific partition in the EEPROM.
 * This function is blocking for the operation to complete.
 * 
 * @param[in]  part      The identifier of the partition to read from.
 *                       Must be one of the BSP_EE24_PART_* constants.
 * @param[out] Data      A pointer to a buffer where the data will be stored.
 * @param[in]  LenBuffer The maximum number of bytes to be read from the partition.
 * @return     true if the operation was successful, false otherwise.
 */
bool BSP_EE24_PartRead(BSP_EE24_PartionEntry_e part, uint8_t *Data, size_t LenBuffer) {
    // Check if the partition is valid and has data to read
    if (!(part < __BSP_EE24_PART_MAX__ && PartionTable[part].len > 0))  {
        return false;
    }

    // Read the Partion address
    uint32_t Address = PartionTable[part].address;
    
    // Calculate the length to be read
    uint32_t Len = 0;
    if (PartionTable[part].len < LenBuffer) {
        Len = PartionTable[part].len;
    } else {
        Len = LenBuffer;
    }

    return BSP_EE24_Read(Address,Data,Len);

}

/**
 * Writes data to a specific partition in the EEPROM.
 * This function is blocking for the operation to complete.
 * 
 * @param[in]  part      The identifier of the partition to write to.
 *                       Must be one of the BSP_EE24_PART_* constants.
 * @param[out] Data      A pointer to a buffer containing the data to be written.
 * @param[in]  LenBuffer The maximum number of bytes to be written to the partition.
 * @return     true if the operation was successful, false otherwise.
 */
bool BSP_EE24_PartWrite(BSP_EE24_PartionEntry_e part, uint8_t *Data, size_t LenBuffer) {
    // Check if the partition is valid and has data to read
    if (!(part < __BSP_EE24_PART_MAX__ && PartionTable[part].len > 0))  {
        return false;
    }

    // Read the Partion address
    uint32_t Address = PartionTable[part].address;

    // Calculate the length to be read
    uint32_t Len = 0;
    if (PartionTable[part].len < LenBuffer) {
        Len = PartionTable[part].len;
    } else {
        Len = LenBuffer;
    }

    return BSP_EE24_Write(Address,Data,Len);
}

static inline size_t MIN_MEM(size_t a, size_t b) { return((a) < (b) ? a : b); }


/**
 * Clear a new or undefined eeprom 
 */
void BSP_EE24_FullClear() {
    size_t len = 0;
    for (int i = 1; i < __BSP_EE24_PART_MAX__; i++) {
        len += PartionTable[i].len;
    }

    BSP_EE24_TableHeader header = {
        .version = PartionTableVersion,
        .used_len = len,
        .reserved = {0},
    };
    
    // write header
    BSP_EE24_PartWrite(BSP_EE24_PART_HEAD_PARTION, (uint8_t*)&header, sizeof(header));
    
    // Calculate the start address and length of the clear operation
    uint32_t clear_start = PartionTable[BSP_EE24_PART_HEAD_PARTION].address + PartionTable[BSP_EE24_PART_HEAD_PARTION].len;
    uint32_t clear_len = header.used_len - PartionTable[BSP_EE24_PART_HEAD_PARTION].len;

    // Use a 16-byte buffer to perform the write operation in chunks of 16 bytes
    uint8_t clear_buffer[16] = {0};

    while (clear_start < header.used_len) {
        size_t chunk_size = MIN_MEM(clear_len, sizeof(clear_buffer));
        BSP_EE24_Write(clear_start, clear_buffer, chunk_size);
        clear_start += chunk_size;
    }

}

/**
 * migrate form diffrent versions of the eeprom
 */
void BSP_EE24_TableMigrate(uint32_t ee_version) {

    if(ee_version == PartionTableVersion) {
        // current fw version, do nothing
        return;
    }

    switch (ee_version)
    {
    
    default:  // undefined write zeros.
        BSP_EE24_FullClear();
        break;
    }
}