#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;
    }
}