/* =============================================================================
 * applog.c - UART 디버그 로그 (RPi setup_logger() 대응)
 *
 * USART3(PD8 TX / PD9 RX, APP_LOG_UART_BAUD) 로 printf 스타일
 * 라인 로그를 출력한다. 라인은 다음 형식으로 찍힌다:
 *
 *   [   12345 ms] INFO  메시지...
 *
 * 직렬화:
 *   FreeRTOS 스케줄러 동작 중이면 뮤텍스로 라인 단위 직렬화한다.
 *   부팅 초기(스케줄러 시작 전, 또는 ISR 문맥)에는 뮤텍스 없이 그대로 출력한다.
 *
 * USART3 HAL 핸들과 MspInit(핀/클럭) 은 이 파일이 소유한다.
 * ===========================================================================*/
#include "applog.h"
#include "app_config.h"

#include "stm32f4xx_hal.h"

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

#include <stdarg.h>
#include <stdio.h>
#include <string.h>

/* ── 설정 상수 ───────────────────────────────────────────────────────────── */
/* 한 줄 로그 최대 길이(접두부 포함). 스택 버퍼 크기. */
#define LOG_LINE_MAX        160
/* HAL_UART_Transmit 블로킹 타임아웃(ms). */
#define LOG_TX_TIMEOUT_MS   100u
/* 뮤텍스 획득 대기(ms). 초과 시 직렬화 포기하고 그대로 출력(로그는 best-effort). */
#define LOG_MUTEX_WAIT_MS   50u

/* ── USART3 HAL 핸들 (이 모듈 소유) ───────────────────────────────────────── */
static UART_HandleTypeDef huart_log;

/* ── 직렬화용 뮤텍스 + 초기화 플래그 ─────────────────────────────────────── */
static SemaphoreHandle_t s_logMutex = NULL;
static volatile int      s_inited   = 0;

/* =============================================================================
 * USART3 MspInit — PD8(TX)/PD9(RX), AF7, 클럭 게이팅
 * HAL_UART_Init() 내부에서 HAL_UART_MspInit() 약함수 형태로 호출된다.
 * (여러 UART 를 쓰는 프로젝트라면 instance 분기. 여기선 USART3 만 처리.)
 * ===========================================================================*/
void HAL_UART_MspInit(UART_HandleTypeDef *uartHandle)
{
    GPIO_InitTypeDef g = {0};

    if (uartHandle->Instance != USART3) {
        return; /* 다른 UART 는 해당 모듈이 처리 */
    }

    __HAL_RCC_GPIOD_CLK_ENABLE();
    __HAL_RCC_USART3_CLK_ENABLE();

    /* PD8=USART3_TX, PD9=USART3_RX, AF7.
     * (PA2/PA3 는 LAN8720 RMII 의 ETH_MDIO/미사용과 충돌하므로 사용 불가 →
     *  로그 UART 는 USART3 PD8/PD9 로 배치한다. HARDWARE.md 핀맵 참조.) */
    g.Pin       = GPIO_PIN_8 | GPIO_PIN_9;
    g.Mode      = GPIO_MODE_AF_PP;
    g.Pull      = GPIO_PULLUP;
    g.Speed     = GPIO_SPEED_FREQ_VERY_HIGH;
    g.Alternate = GPIO_AF7_USART3;
    HAL_GPIO_Init(GPIOD, &g);
}

/* =============================================================================
 * applog_init — USART3 초기화 + 로그 뮤텍스 생성
 * ===========================================================================*/
void applog_init(void)
{
    huart_log.Instance          = USART3;
    huart_log.Init.BaudRate     = APP_LOG_UART_BAUD;
    huart_log.Init.WordLength   = UART_WORDLENGTH_8B;
    huart_log.Init.StopBits     = UART_STOPBITS_1;
    huart_log.Init.Parity       = UART_PARITY_NONE;
    huart_log.Init.Mode         = UART_MODE_TX_RX;
    huart_log.Init.HwFlowCtl    = UART_HWCONTROL_NONE;
    huart_log.Init.OverSampling = UART_OVERSAMPLING_16;

    if (HAL_UART_Init(&huart_log) != HAL_OK) {
        /* UART 를 못 켜면 로그는 무음 처리(디바이스 동작 자체는 계속). */
        s_inited = 0;
        return;
    }

    /* 뮤텍스 생성 시도. 실패해도(메모리 부족 등) 로그는 비직렬화로 동작. */
    s_logMutex = xSemaphoreCreateMutex();
    s_inited   = 1;
}

/* ── 스케줄러가 돌고 있고 ISR 문맥이 아닐 때만 뮤텍스를 쓴다 ──────────────── */
static int log_can_use_mutex(void)
{
    if (s_logMutex == NULL) {
        return 0;
    }
    if (xTaskGetSchedulerState() != taskSCHEDULER_RUNNING) {
        return 0; /* 스케줄러 시작 전: 뮤텍스 블로킹 불가 */
    }
    if (__get_IPSR() != 0u) {
        return 0; /* ISR 문맥: 블로킹 뮤텍스 호출 금지 */
    }
    return 1;
}

/* ── 원시 바이트 전송(블로킹) ────────────────────────────────────────────── */
static void log_tx(const char *buf, uint16_t len)
{
    if (len == 0u) {
        return;
    }
    (void)HAL_UART_Transmit(&huart_log, (uint8_t *)buf, len, LOG_TX_TIMEOUT_MS);
}

/* =============================================================================
 * applog — printf 스타일 라인 로그
 *   형식: "[%8lu ms] %-5s <msg>\r\n"
 * ===========================================================================*/
void applog(const char *level, const char *fmt, ...)
{
    char    line[LOG_LINE_MAX];
    int     n;
    int     pos = 0;
    va_list ap;
    int     locked = 0;

    if (!s_inited) {
        return; /* UART 미초기화: 무음 */
    }
    if (level == NULL) {
        level = "?";
    }

    /* 업타임 스탬프(ms). 스케줄러 전/후 모두 HAL_GetTick() 으로 일관 — TIM6 기반. */
    n = snprintf(line, sizeof(line), "[%8lu ms] %-5s ",
                 (unsigned long)HAL_GetTick(), level);
    if (n < 0) {
        return;
    }
    pos = (n < (int)sizeof(line)) ? n : (int)sizeof(line) - 1;

    /* 사용자 메시지 */
    va_start(ap, fmt);
    n = vsnprintf(line + pos, sizeof(line) - (size_t)pos, fmt, ap);
    va_end(ap);
    if (n > 0) {
        pos += n;
        if (pos > (int)sizeof(line) - 1) {
            pos = (int)sizeof(line) - 1; /* vsnprintf 가 잘라낸 경우 */
        }
    }

    /* 줄바꿈(\r\n). 공간이 모자라면 마지막 2바이트를 덮어쓴다. */
    if (pos > (int)sizeof(line) - 3) {
        pos = (int)sizeof(line) - 3;
    }
    line[pos++] = '\r';
    line[pos++] = '\n';

    /* 직렬화 후 전송 */
    if (log_can_use_mutex()) {
        if (xSemaphoreTake(s_logMutex, pdMS_TO_TICKS(LOG_MUTEX_WAIT_MS)) == pdTRUE) {
            locked = 1;
        }
    }

    log_tx(line, (uint16_t)pos);

    if (locked) {
        (void)xSemaphoreGive(s_logMutex);
    }
}
