//#include "eeprom_emul.h"
#include <string.h>

// Внутренние переменные
static uint32_t eeprom_current_write_address = EEPROM_START_ADDRESS;
static uint8_t eeprom_initialized = 0;

// Прототипы внутренних функций
static EEPROM_Status EEPROM_FindLatestData(uint16_t virt_address, uint16_t* data);
static EEPROM_Status EEPROM_WriteItem(EEPROM_Item* item);
static EEPROM_Status EEPROM_ErasePage(uint32_t address);
static uint32_t EEPROM_FindNextWriteAddress(void);
static uint8_t EEPROM_IsPageErased(uint32_t address);
static uint32_t EEPROM_CalculateCRC(EEPROM_Item* item);

// Инициализация EEPROM
EEPROM_Status EEPROM_Init(void)
{
    if (eeprom_initialized)
    {
        return EEPROM_OK;
    }

    // Находим следующий адрес для записи
    eeprom_current_write_address = EEPROM_FindNextWriteAddress();

    // Если вся память заполнена, выполняем сборку мусора (форматирование)
    if (eeprom_current_write_address >= EEPROM_START_ADDRESS + EEPROM_SIZE)
    {
        EEPROM_Format();
    }
    else
    {
        eeprom_initialized = 1;
    }

    return EEPROM_OK;
}

// Чтение данных по виртуальному адресу
EEPROM_Status EEPROM_Read(uint16_t virt_address, uint16_t* data)
{
    if (!eeprom_initialized)
    {
        return EEPROM_ERROR;
    }

    if (virt_address >= EEPROM_MAX_VARIABLES || data == NULL)
    {
        return EEPROM_INVALID;
    }

    return EEPROM_FindLatestData(virt_address, data);
}

// Запись данных по виртуальному адресу
EEPROM_Status EEPROM_Write(uint16_t virt_address, uint16_t data)
{
    EEPROM_Item item;

    if (!eeprom_initialized)
    {
        return EEPROM_ERROR;
    }

    if (virt_address >= EEPROM_MAX_VARIABLES)
    {
        return EEPROM_INVALID;
    }

    // Подготавливаем элемент данных
    item.address = virt_address;
    item.data = data;
    item.timestamp = HAL_GetTick();  // Используем системный таймер

    // Записываем элемент
    return EEPROM_WriteItem(&item);
}

// Поиск последних данных для виртуального адреса
static EEPROM_Status EEPROM_FindLatestData(uint16_t virt_address, uint16_t* data)
{
    uint32_t address = EEPROM_START_ADDRESS;
    EEPROM_Item current_item;
    uint32_t latest_timestamp = 0;
    uint16_t latest_data = 0;
    uint8_t data_found = 0;

    // Сканируем всю область EEPROM
    while (address < EEPROM_START_ADDRESS + EEPROM_SIZE)
    {
        // Читаем элемент
        memcpy(&current_item, (void*)address, sizeof(EEPROM_Item));

        // Проверяем, является ли это валидными данными
        if (current_item.address == virt_address)
        {
            if (current_item.timestamp >= latest_timestamp)
            {
                latest_timestamp = current_item.timestamp;
                latest_data = current_item.data;
                data_found = 1;
            }
        }

        address += sizeof(EEPROM_Item);

        // Проверяем конец страницы
        if ((address - EEPROM_START_ADDRESS) % EEPROM_PAGE_SIZE == 0)
        {
            address += (EEPROM_PAGE_SIZE - (sizeof(EEPROM_Item) * 2));
        }
    }

    if (data_found)
    {
        *data = latest_data;
        return EEPROM_OK;
    }

    return EEPROM_INVALID;
}

// Запись элемента в EEPROM
static EEPROM_Status EEPROM_WriteItem(EEPROM_Item* item)
{
    HAL_StatusTypeDef hal_status;

    // Проверяем, нужно ли стирать страницу
    if ((eeprom_current_write_address - EEPROM_START_ADDRESS) % EEPROM_PAGE_SIZE == 0)
    {
        if (!EEPROM_IsPageErased(eeprom_current_write_address))
        {
            if (EEPROM_ErasePage(eeprom_current_write_address) != EEPROM_OK)
            {
                return EEPROM_ERROR;
            }
        }
    }

    // Разблокируем Flash
    HAL_FLASH_Unlock();

    // Записываем данные по словам (32 бита)
    uint32_t* data_ptr = (uint32_t*)item;

    for (uint8_t i = 0; i < sizeof(EEPROM_Item) / 4; i++)
    {
        hal_status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,
                                       eeprom_current_write_address + (i * 4),
                                       data_ptr[i]);

        if (hal_status != HAL_OK)
        {
            HAL_FLASH_Lock();
            return EEPROM_ERROR;
        }
    }

    // Блокируем Flash
    HAL_FLASH_Lock();

    // Обновляем адрес для следующей записи
    eeprom_current_write_address += sizeof(EEPROM_Item);

    // Проверяем переполнение
    if (eeprom_current_write_address >= EEPROM_START_ADDRESS + EEPROM_SIZE)
    {
        // Выполняем сборку мусора (в данном случае - форматирование)
        EEPROM_Format();
    }

    return EEPROM_OK;
}

// Стирание страницы Flash
static EEPROM_Status EEPROM_ErasePage(uint32_t address)
{
    FLASH_EraseInitTypeDef erase;
    uint32_t page_error;

    // Определяем номер страницы
    uint32_t page = (address - FLASH_BASE) / EEPROM_PAGE_SIZE;

    HAL_FLASH_Unlock();

    erase.TypeErase = FLASH_TYPEERASE_PAGES;
    erase.PageAddress = address;
    erase.NbPages = 1;

    if (HAL_FLASHEx_Erase(&erase, &page_error) != HAL_OK)
    {
        HAL_FLASH_Lock();
        return EEPROM_ERROR;
    }

    HAL_FLASH_Lock();
    return EEPROM_OK;
}

// Поиск следующего адреса для записи
static uint32_t EEPROM_FindNextWriteAddress(void)
{
    uint32_t address = EEPROM_START_ADDRESS;
    EEPROM_Item item;

    // Ищем первую свободную позицию
    while (address < EEPROM_START_ADDRESS + EEPROM_SIZE)
    {
        memcpy(&item, (void*)address, sizeof(EEPROM_Item));

        // Если нашли пустой элемент (все FFFF), это свободная позиция
        if (item.address == 0xFFFF && item.data == 0xFFFF && item.timestamp == 0xFFFFFFFF)
        {
            break;
        }

        address += sizeof(EEPROM_Item);

        // Проверяем границу страницы
        if ((address - EEPROM_START_ADDRESS) % EEPROM_PAGE_SIZE == 0)
        {
            address += (EEPROM_PAGE_SIZE - (sizeof(EEPROM_Item) * 2));
        }
    }

    return address;
}

// Проверка, стерта ли страница
static uint8_t EEPROM_IsPageErased(uint32_t address)
{
    uint32_t* check_addr = (uint32_t*)address;

    // Проверяем первые несколько слов
    for (uint8_t i = 0; i < 8; i++)
    {
        if (check_addr[i] != 0xFFFFFFFF)
        {
            return 0;
        }
    }

    return 1;
}

// Полное форматирование EEPROM
EEPROM_Status EEPROM_Format(void)
{
    uint32_t address = EEPROM_START_ADDRESS;

    // Стираем все страницы, используемые для EEPROM
    while (address < EEPROM_START_ADDRESS + EEPROM_SIZE)
    {
        if (EEPROM_ErasePage(address) != EEPROM_OK)
        {
            return EEPROM_ERROR;
        }
        address += EEPROM_PAGE_SIZE;
    }

    eeprom_current_write_address = EEPROM_START_ADDRESS;
    eeprom_initialized = 1;

    return EEPROM_OK;
}

// Получение информации об использовании EEPROM
void EEPROM_GetInfo(uint32_t* used, uint32_t* total)
{
    uint32_t address = EEPROM_START_ADDRESS;
    uint32_t used_bytes = 0;

    if (used)
    {
        // Подсчитываем использованные байты
        while (address < EEPROM_START_ADDRESS + EEPROM_SIZE)
        {
            EEPROM_Item item;
            memcpy(&item, (void*)address, sizeof(EEPROM_Item));

            if (item.address != 0xFFFF || item.data != 0xFFFF || item.timestamp != 0xFFFFFFFF)
            {
                used_bytes += sizeof(EEPROM_Item);
            }

            address += sizeof(EEPROM_Item);
        }

        *used = used_bytes;
    }

    if (total)
    {
        *total = EEPROM_SIZE;
    }
}