/** 
**************************************************************************
* @file trace.h
* @brief Заголочный файл для работы с трассировкой.
**************************************************************************
* @addtogroup   TRACE           Trace defines
* @brief        Дефайны для работы с трассировкой
*************************************************************************/
#ifndef __TRACE_H_
#define __TRACE_H_
#include "mylibs_defs.h"

#include <string.h>
/** 
  * @addtogroup TRACE_SERIAL    Serial trace defines
  * @ingroup    TRACE
  * @brief      Дефайны для работы с serial трассировкой (SWO, RTT)
  * @details    В зависимости от настроек определяется дефайн @ref my_printf() и @ref log_printf() для работы с трассировкой:
    - @ref SERIAL_TRACE_ENABLE - Если трассировка  отключена, то все дефайны определяются как 'ничего' 
      и на производительность кода не влияют

    - @ref RTT_TRACE_ENABLE - для RTT это будет вызов функции SEGGER_RTT_printf()
      
      Предварительно надо подключить библиотеку SEGGER RTT (SEGGER_RTT.h) и вызвать функцию SEGGER_RTT_Init()

    - @ref SWO_TRACE_ENABLE для SWO это будет просто printf()
    
      Предварительно надо подключить библиотеку STDOUT и retarget под ITM:
      
      @verbatim
        Manage Run-Time Environment -> Compiler -> I/O -> STDOUT -> ITM
      @endverbatim
      
      Для SWO также надо включить трассировку:    
      
      @verbatim
        Options For Target -> Debug -> Debugger Settings
      @endverbatim
      
      В вкладке Debug: 
        - Port = SW
      В вкладке Trace: 
        - Указать Core Clock
        - Выставить Trace Port = SWO
        - ITM - выбрать нужный порт (для Keil нулевой порт)   
    

  * @{
  *
  * @def my_printf(...)
  * @brief Универсальный макрос для вывода трассировки
  * @details Варианты реализации:
  *  - RTT_TRACE_ENABLE    `SEGGER_RTT_printf(0, ...)`
  *  - SWO_TRACE_ENABLE - `printf(...)`
  *  - NO_TRACE - пустой макрос
  *
  * @def log_printf(TAG, fmt, ...)
  * @brief Макрос логирования с поддержкой уровней @ref LOG_LEVEL
  * @param TAG  Тэг лога
  * @param fmt, ...  Форматируемая строка
  * @details Варианты реализации:
  *  - @ref LOG_LEVEL == 0 - логирование отключено (макрос пустой)
  *  - @ref LOG_LEVEL == 1 - выводится время @ref local_time и TAG
  * @code
            [123] [ADC] Measure Done
            [456] [ADC] Measure Err
  * @endcode
  *  - @ref LOG_LEVEL >= 2 - выводится время, TAG, имя файла и номер строки
  * @code
            [123] [ADC] (../Core/Src/adc.c:75) Measure Done
            [456] [ADC] (../Core/Src/adc.c:80) Measure Err
  * @endcode
  */
#ifdef SERIAL_TRACE_ENABLE

  #if defined(RTT_TRACE_ENABLE)
    #undef SWO_TRACE_ENABLE
    #include "SEGGER_RTT.h"
    #define my_printf(...)        SEGGER_RTT_printf(0, __VA_ARGS__)
  #elif defined(SWO_TRACE_ENABLE)
    #undef RTT_TRACE_ENABLE
    #define my_printf(...)        printf(__VA_ARGS__)
  #else // NO_TRACE
    #define my_printf(...)  
    #warning No trace is selected. Serial debug wont work.
  #endif // RTT_TRACE_ENABLE/SWO_TRACE_ENABLE/NO_TRACE
#else //SERIAL_TRACE_ENABLE

  #define my_printf(...)   
  #undef RTT_TRACE_ENABLE
  #undef SWO_TRACE_ENABLE
  
#endif //SERIAL_TRACE_ENABLE


#ifndef local_time
#define local_time()  HAL_GetTick() ///< Локальное время
#endif
#ifndef LOG_LEVEL
#define LOG_LEVEL 1  ///< @brief Уровень логирования (по умолчанию == 1)
#endif


#if LOG_LEVEL == 0 // лог отключен
    #define \
log_printf(TAG, fmt, ...)  
#elif LOG_LEVEL == 1 // только тэг
    #define log_printf(TAG, fmt, ...)  \
my_printf("\n[%lu] [%s] " fmt, \
(unsigned long)local_time(), TAG, ##__VA_ARGS__)
#elif LOG_LEVEL >= 2 // всё
    #define log_printf(TAG, fmt, ...)  \
my_printf("\n[%lu] [%s] (%s:%d) " fmt, \
(unsigned long)local_time(), TAG, __FILE__, __LINE__, ##__VA_ARGS__)
#endif
  
/** TRACE_SERIAL
  * @}
  */

/** 
  * @addtogroup TRACE_GPIO    GPIO trace defines
  * @ingroup    TRACE
  * @brief      Дефайны для работы с GPIO трассировкой
  * @details    Определяется дефайны для работы с GPIO трассировкой:
    - TRACE_GPIO_RESET() - для сброса ножки GPIO (через BSRR)
    - TRACE_GPIO_SET() - для выставления ножки GPIO (через BSRR)
    
    - Если трассировка @ref GPIO_TRACE_ENABLE отключена, то все дефайны определяются как 'ничего' 
      и на производительность кода не влияют
  * @{  
  *
  * @def TRACE_GPIO_RESET(_gpio_, _pin_)
  * @brief Сбросить указанную ножку GPIO
  * @param _gpio_  Указатель на структуру GPIO (напр.  GPIOA)
  * @param _pin_   Номер ножки (напр. GPIO_PIN_0)
  * @details Варианты реализации:
  *  - GPIO_TRACE_ENABLE не определён - макрос пустой
  *  - GPIO_TRACE_ENABLE определён - устанавливает бит сброса через BSRR ((_pin_)<<16)
  *
  * @def TRACE_GPIO_SET(_gpio_, _pin_)
  * @brief Установить указанную ножку GPIO
  * @param _gpio_  Указатель на структуру GPIO (например GPIOA)
  * @param _pin_   Номер ножки (напр. GPIO_PIN_0)
  * @details Варианты реализации:
  *  - GPIO_TRACE_ENABLE не определён - макрос пустой
  *  - GPIO_TRACE_ENABLE определён - устанавливает бит установки через BSRR (_pin_)
  */
#ifndef GPIO_TRACE_ENABLE
  #define TRACE_GPIO_SET(_gpio_,_pin_)
  #define TRACE_GPIO_RESET(_gpio_,_pin_)
#else
  #define TRACE_GPIO_SET(_gpio_,_pin_)          (_gpio_)->BSRR = (((_pin_)))
  #define TRACE_GPIO_RESET(_gpio_,_pin_)        (_gpio_)->BSRR = ((_pin_)<<16)
#endif //GPIO_TRACE_ENABLE


/** TRACE_GPIO
  * @}
  */



#if defined(HAL_MODULE_ENABLED) && defined(RTT_TRACE_ENABLE)

/** 
  * @addtogroup TRACE_RTT_FLASH    Flash RTT Buffer
  * @ingroup    TRACE
  * @brief      Макросы и функции для сохранения/чтения RTT буфера в Flash
  * @details    Модуль позволяет сохранять данные RTT буфера во Flash и читать их обратно по тегам.  
  * Теги работают следующим образом:  
  *  - Базовый тег (младший байт = 0): модуль сам выбирает первый свободный слот во Flash;  
  *    новые записи получают автоинкрементированный младший байт тега (от 0x00 до 0xFF).  
  *  - Конкретный тег (младший байт != 0): запись или чтение происходит строго с указанным тегом;  
  *    если слот с таким тегом уже занят, запись не выполняется.  
  *  - Автоинкремент позволяет хранить несколько последовательных записей в пределах одного базового тега,  
  *    без необходимости вручную отслеживать адреса Flash или позиции буферов.  
  * 
  * Параметры:
  * - @ref RTT_FLASH_BUFFER_SIZE    - Размер буфера RTT в Flash
  * - @ref RTT_FLASH_SECTOR         - Сектор FLASH куда положится RTT буфер
  * - @ref RTT_FLASH_SECTOR_START   - Начало сектора RTT_FLASH_SECTOR
  * - @ref RTT_FLASH_SECTOR_END     - Конец сектора RTT_FLASH_SECTOR
  * 
  * @{
  */

  
/**
  * @brief Структура RTT, которая будет положена в Flash
  */
typedef struct {
  uint32_t tag;                   ///< Уникальный идентификатор буфера
  uint32_t size;                  ///< Размер данных
  char     data[RTT_FLASH_BUFFER_SIZE];  ///< Буфер RTT
} RTT_FlashHeader_t;

/**
  * @brief Подготовка Flash к записи
  * @details Сбрасывает ошибки Flash и ожидает готовности перед записью
  */
__STATIC_FORCEINLINE void RTT_FlashPrepare(void)
{
  HAL_FLASH_Unlock();
  __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_PGSERR | FLASH_FLAG_WRPERR | FLASH_FLAG_OPERR);
  while (__HAL_FLASH_GET_FLAG(FLASH_FLAG_BSY)) { 
  __NOP(); 
  }
}

/**
  * @brief Сохраняет последние символы RTT-буфера в Flash по тегу
  * @param tag        Базовый или конкретный идентификатор буфера.
  * @param tail_size  Количество последних символов RTT для копирования
  * @param buf_num    Указатель на переменную в которую запишется номер буфера для конкретного тега
  * @return >=0 — номер буфера (тег) для записи, <0 — ошибка (нет места, тег уже занят, ошибка записи в флеш)
  *
  * @details  Автоматически копирует последние tail_size символов из RTT-буфера
  *           и записывает их во Flash.
  *           Тег может быть базовым или конкретным:
  *             - Если базовый (младший байт == 0) — будет выбран первый свободный слот с автоинкрементом.
  *               Автоинкремент формируется в пределах от 0x1 до 0xFF
  *             - Если конкретный (младший байт != 0) — запись выполняется только с этим тегом, иначе ошибка.
  * 
  */
__STATIC_FORCEINLINE int RTT_SaveToFlash(uint32_t tag, uint32_t tail_size)
{
  if (tag == 0xFFFFFFFF)
    return -1;  // Неверный тег

  SEGGER_RTT_BUFFER_UP *up = &_SEGGER_RTT.aUp[0];
  unsigned buf_size = up->SizeOfBuffer;
  unsigned wr = up->WrOff;

  // Ограничиваем по размеру буфера RTT и RTT_FLASH_BUFFER_SIZE
  unsigned n = (tail_size > buf_size) ? buf_size : tail_size;
  if (n > RTT_FLASH_BUFFER_SIZE)
    n = RTT_FLASH_BUFFER_SIZE;

  uint32_t addr = RTT_FLASH_SECTOR_START;
  RTT_FlashHeader_t *flash_hdr = NULL;
  uint32_t base_tag = tag & 0xFFFFFF00;
  uint32_t next_tag = (tag & 0xFF) == 0 ? tag + 1 : tag;

  // Ищем первый свободный слот, параллельно автоинкрементируем тег
  while ((addr + sizeof(RTT_FlashHeader_t)) <= RTT_FLASH_SECTOR_END)
  {
    flash_hdr = (RTT_FlashHeader_t *)addr;

    if (flash_hdr->tag == 0xFFFFFFFF)
      break;  // Нашли свободное место

    
    if((flash_hdr->tag & 0xFFFFFF00) == base_tag) // выбраный тег
    {
      if ((tag & 0xFF) == 0) // если он базовый - ищем последний
        next_tag = flash_hdr->tag + 1;  // автоинкремент
      else
        if(flash_hdr->tag == tag) // если он конкретный и уже существует - то ошибка
          return -1;  // конкретный тег уже занят
    }

    
    if(next_tag - tag > 0xFF)
      return -1; // автоинкремент слишком большой
    
    addr += sizeof(RTT_FlashHeader_t);
  }

  if ((addr + sizeof(RTT_FlashHeader_t)) > RTT_FLASH_SECTOR_END)
    return -1;  // Нет свободного места

  // Копируем последние n символов из RTT
  char temp[RTT_FLASH_BUFFER_SIZE];
  unsigned valid_count = 0;

  for (unsigned i = 0; i < n; i++)
  {
    unsigned idx = (wr + buf_size - n + i) % buf_size;
    char c = up->pBuffer[idx];
    if (c != 0)
      temp[valid_count++] = c;
  }

  RTT_FlashPrepare();

  // Формируем структуру в RAM
  RTT_FlashHeader_t flash_data;
  flash_data.tag  = next_tag;
  flash_data.size = valid_count;
  memcpy(flash_data.data, temp, valid_count);

  // Записываем структуру во Flash (по 4 байта)
  const uint32_t *p = (const uint32_t *)&flash_data;
  for (unsigned i = 0; i < sizeof(RTT_FlashHeader_t) / 4; i++)
  {
    if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + i * 4, p[i]) != HAL_OK)
      return -1;
  }

  HAL_FLASH_Lock();
  __DSB();
  __ISB();
  

  return (int)(next_tag&0xFF);
}


/**
  * @brief Читает последние символы RTT-буфера из Flash по тегу
  * @param tag        Базовый или конкретный идентификатор буфера.
  * @param Buffer     Буфер назначения для копирования данных
  * @param tail_size  Количество последних символов, которые нужно прочитать
  * @param read_size  Количество считанных символов
  * @return >=0 — номер буфера (тег) для записи, <0 — ошибка (тег не найден или структура повреждена)
  *
  * @details  Копирует последние tail_size символов из найденной записи Flash в Buffer.
  *           Тег может быть базовым или конкретным:
  *             - Если базовый (младший байт == 0) — будет прочитана последняя запись из группы.
  *             - Если конкретный (младший байт != 0) — прочитывается именно эта запись.
  */
__STATIC_FORCEINLINE int RTT_ReadFromFlash(uint32_t tag, char *Buffer, uint32_t tail_size, uint32_t *read_size)
{
  if (!Buffer || tail_size == 0)
    return -1;  // Неверные параметры

  if (tag == 0xFFFFFFFF)
    return -1;  // Недопустимый тег

  uint32_t addr = RTT_FLASH_SECTOR_START;
  RTT_FlashHeader_t *flash_hdr = NULL;
  RTT_FlashHeader_t *target_hdr = NULL;
  uint32_t base_tag = tag & 0xFFFFFF00;

  // Поиск записи по тегу
  while ((addr + sizeof(RTT_FlashHeader_t)) <= RTT_FLASH_SECTOR_END)
  {
    flash_hdr = (RTT_FlashHeader_t *)addr;

    if (flash_hdr->tag == 0xFFFFFFFF)
      break;  // Достигнут конец записанных структур

    // выбраный тег
    if((flash_hdr->tag & 0xFFFFFF00) == base_tag) 
    {
      if ((tag & 0xFF) == 0) // если он базовый - ищем последний          
        target_hdr = flash_hdr;  // сохраняем последний в группе
      else
        if(flash_hdr->tag == tag) // если он конкретный и найден - берем его
        {
          target_hdr = flash_hdr;
          break;  // конкретный тег найден
        }
    }
    
    addr += sizeof(RTT_FlashHeader_t);
  }

  if (!target_hdr) return -1; // Тег не найден
  
  // Проверка корректности размера
  if (target_hdr->size > RTT_FLASH_BUFFER_SIZE)
    return -1;  // Повреждённая запись  

  // Определяем количество читаемых символов
  uint32_t n = (tail_size > target_hdr->size) ? target_hdr->size : tail_size;
  // Начальная позиция для чтения последних tail_size символов
  uint32_t start = target_hdr->size - n;
  // Копируем данные из Flash в RAM
  memcpy(Buffer, &target_hdr->data[start], n);
  
  if(read_size != NULL)
  {
    *read_size = n;
  }

  __DSB();
  __ISB();

  return (int)(target_hdr->tag & 0xFF);
}

/**
  * @brief Стирание сектора Flash с RTT-буфером
  */
__STATIC_FORCEINLINE int RTT_EraseFlash(void)
{
  FLASH_EraseInitTypeDef eraseInit;
  uint32_t pageError = 0;

  RTT_FlashPrepare();

  eraseInit.TypeErase   = FLASH_TYPEERASE_SECTORS;
  eraseInit.Sector      = RTT_FLASH_SECTOR;
  eraseInit.NbSectors   = 1;

  if (HAL_FLASHEx_Erase(&eraseInit, &pageError) != HAL_OK)
  {
    return -1;
  }
  return 0;
  HAL_FLASH_Lock();
}

/** TRACE_RTT_FLASH
  * @}
  */

#else // HAL_MODULE_ENABLED && RTT_TRACE_ENABLE
#define RTT_FlashPrepare(...)
#define RTT_EraseFlash(...)    0
#define RTT_SaveToFlash(...)   0
#define RTT_ReadFromFlash(...) 0
#endif // HAL_MODULE_ENABLED && RTT_TRACE_ENABLE




/** 
  * @addtogroup  TRACE_HARDFAULT    Hardfault trace defines
  * @ingroup     TRACE
  * @brief       Модуль трассировки HardFault с возможностью сохранения RTT буфера во Flash
  * @details
  * Этот модуль позволяет сохранять контекст процессора и последние символы RTT буфера при возникновении HardFault.
  *
  * Механизм работы:
  *   - При срабатывании HardFault вызывается HF_HandleFault(), который:
  *       1. Получает указатель на стек, где произошёл HardFault (MSP или PSP).
  *       2. Выводит значения регистров R0-R3, R12, LR, PC, PSR и системных регистров SCB.
  *       3. Формирует строку с регистрами и копирует последние символы RTT буфера.
  *       4. Сохраняет данные во Flash с базовым тегом HF_RTT_TAG_BASE.
  *   - Для восстановления последнего HardFault используется HF_CheckRecovered(), который:
  *       1. Читает запись во Flash по базовому тегу.
  *       2. Выводит сохранённый RTT буфер и контекст регистров.
  *       3. Опционально стирает Flash после восстановления.
  * 
  * Параметры:
  * - @ref HARDFAULT_SERIAL_TRACE   - Включить обработку и serial трассировку Hardfault
  *   Если отключена то вставляются заглушки, никак не влияющие на параметры и остальную программу
  * - @ref HF_RTT_TAG_BASE          - Базовый тег RTT Flash для HardFault 
  * - @ref HF_RTT_TAIL_SIZE         - Размер буфера RTT, который сохранится при Hardfault
  * - @ref HF_STACK_DUMP_WORDS      - Сколько слов стека будет проанализировано во время Hardfault
  * - @ref HF_FLASH_ADDR            - Адрес FLASH куда положится RTT буфер
  * - @ref HF_RAM_END               - Конец RAM памяти (чтобы во время анализа стека не выйти за пределы)
  * 
  @code
  void Hardfault()
  {
    HF_HandleFault();
    NVIC_SystemReset();
  }

  int main()
  {
    if(HF_CheckRecovered(0))
    {
      //set hardfault error
      RTT_EraseFlash(); // erase rtt flash after message readed
    }
  }  
  @endcode
  * @{
  */
#if defined(HAL_MODULE_ENABLED) && defined(HARDFAULT_SERIAL_TRACE)

#ifndef HF_RTT_TAIL_SIZE
#define HF_RTT_TAIL_SIZE  RTT_FLASH_BUFFER_SIZE ///< Размер буфера RTT, который сохранится при Hardfault
#endif

/**
  * @brief Контекст стек-фрейма процессора при HardFault
  * @details Сохраняет регистры R0-R3, R12, LR, PC, PSR для последующего анализа.
  */
typedef struct {
  uint32_t r0;   ///< Регистр R0
  uint32_t r1;   ///< Регистр R1
  uint32_t r2;   ///< Регистр R2
  uint32_t r3;   ///< Регистр R3
  uint32_t r12;  ///< Регистр R12
  uint32_t lr;   ///< Link Register
  uint32_t pc;   ///< Program Counter
  uint32_t psr;  ///< Program Status Register
} HF_StackFrame_t;

/**
  * @brief Проверка и вывод последнего HardFault-трейса из Flash
  * @details
  * Функция ищет последнюю запись HardFault по базовому тегу HF_RTT_TAG_BASE
  * и выводит её содержимое в консоль. После успешного вывода Flash можно опционально очистить.
  *
  * @return int
  *   - 1 — данные HardFault найдены и выведены
  *   - 0 — данные отсутствуют или тег не найден
  * 
  * @note Вызов рекомендуется при инициализации приложения для анализа предыдущего сбоя.
  */
__STATIC_FORCEINLINE int HF_CheckRecovered(int erase)
{
  char buffer[RTT_FLASH_BUFFER_SIZE];
  uint32_t read_size = 0;
  int n_hardfault = RTT_ReadFromFlash(HF_RTT_TAG_BASE, buffer, HF_RTT_TAIL_SIZE, &read_size);
  if (n_hardfault > 0)
  {
    my_printf("\n--- Recovered HardFault RTT buffer #%u ---\n", n_hardfault);
    for (int i = 0; i < read_size; i++)
    {
      char c = buffer[i];
      if (c == 0 || c == (char)0xFF) break;
      my_printf("%c", c);
    }

    if(erase)
      RTT_EraseFlash();
    my_printf("\n--------- HardFault Dump End ---------\n");
    return 1;
  }
  return 0;
}


static HF_StackFrame_t *stack_frame;
static uint32_t stack_dump[HF_STACK_DUMP_WORDS];
static void *ret_adr[10] = {0};
/**
  * @brief Обработчик HardFault
  * @details
  * Вызывается из прерывания HardFault или в любом месте где понятно что ошибка критическая.
  * Последовательно выполняет:
  *   1. Определяет активный стек (MSP или PSP) на момент сбоя.
  *   2. Сохраняет значения регистров R0-R3, R12, LR, PC, PSR.
  *   3. Выводит системные регистры CFSR, HFSR, DFSR, AFSR, MMFAR, BFAR.
  *   4. Формирует stack trace с 3 уровнями возврата.
  *   5. Копирует последние символы RTT буфера.
  *   6. Сохраняет все данные во Flash через RTT_SaveToFlash с базовым тегом HF_RTT_TAG_BASE.
  *
  * @note Функция защищена, так как вызывается в контексте сбоя — минимизирует использование вызовов HAL.
  */
__STATIC_FORCEINLINE void HF_HandleFault(void)
{
    // Получаем указатель на стек, где произошёл HardFault
    __ASM volatile(
        "TST lr, #4       \n"
        "ITE EQ           \n"
        "MRSEQ %[ptr], MSP\n"
        "MRSNE %[ptr], PSP\n"
        : [ptr] "=r"(stack_frame)
    );

    my_printf("\n===== HardFault occurred! =====\n");
    my_printf("R0  = 0x%08X\n", stack_frame->r0);
    my_printf("R1  = 0x%08X\n", stack_frame->r1);
    my_printf("R2  = 0x%08X\n", stack_frame->r2);
    my_printf("R3  = 0x%08X\n", stack_frame->r3);
    my_printf("R12 = 0x%08X\n", stack_frame->r12);
    my_printf("LR  = 0x%08X\n", stack_frame->lr);
    my_printf("PC  = 0x%08X\n", stack_frame->pc);
    my_printf("PSR = 0x%08X\n", stack_frame->psr);

    my_printf("CFSR  = 0x%08X\n", SCB->CFSR);
    my_printf("HFSR  = 0x%08X\n", SCB->HFSR);
    my_printf("DFSR  = 0x%08X\n", SCB->DFSR);
    my_printf("AFSR  = 0x%08X\n", SCB->AFSR);
    my_printf("MMFAR = 0x%08X\n", SCB->MMFAR);
    my_printf("BFAR  = 0x%08X\n", SCB->BFAR);
    
    // --- Stack trace ---
    my_printf("--- Stack trace ---\n");
    ret_adr[0] = __builtin_return_address(0);
    ret_adr[1] = __builtin_return_address(1);
    ret_adr[2] = __builtin_return_address(2);

    for (int i = 0; i < 3; i++)  // развернуть n уровней
    {
      if(ret_adr[i])
        my_printf("  #%d: 0x%08lX\r\n", i, ret_adr[i]); // -1 для Thumb
    }
    RTT_SaveToFlash(HF_RTT_TAG_BASE, HF_RTT_TAIL_SIZE);
}
#else // HAL_MODULE_ENABLED && HARDFAULT_SERIAL_TRACE
#define HF_CheckRecovered(...) 0
#define HF_HandleFault(...)
#endif // HAL_MODULE_ENABLED && HARDFAULT_SERIAL_TRACE
/** TRACE_HARDFAULT
  * @}
  */



#endif //__TRACE_H_