#pragma once

#include "stm32h5xx_nucleo.h"
#include <cstdarg>
#include <cstdint>
#include <cstring>
#include <string>

class SerialPort {
private:
    static constexpr uint32_t kTransmitTimeout = 1000;
    static constexpr auto* kHandle             = &hcom_uart[COM1];

    static inline char buffer[256] = {0};

    friend void HAL_UART_TxCpltCallback(UART_HandleTypeDef* huart);
    static inline volatile bool isTransmitting = false;

    static void WaitTransmit() {
        const auto tick = HAL_GetTick();
        while (isTransmitting) {
            if (HAL_GetTick() - tick > kTransmitTimeout) {
                HAL_UART_AbortTransmit(kHandle);
                isTransmitting = false;
            }
        }
    }

public:
    static uint8_t ReadByte() {
        return kHandle->Instance->RDR;
    }

    static std::string ReadLine() {
        std::string result;
        result.reserve(128);
        char c;
        ReadByte();
        while (true) {
            HAL_UART_Receive(kHandle, reinterpret_cast<uint8_t*>(&c), 1, HAL_MAX_DELAY);
            if (c == '\n') {
                if (!result.empty() && result.back() == '\r') {
                    result.pop_back();
                }
                break;
            }
            result.push_back(c);
        }
        return result;
    }

    static void WriteLineDMA(const char* format, ...) {
        WaitTransmit();
        isTransmitting = true;
        va_list args;
        va_start(args, format);
        int len = vsnprintf(buffer, sizeof(buffer) - 1, format, args);
        va_end(args);
        if (len < 0 || len > static_cast<int>(sizeof(buffer) - 1)) {
            isTransmitting = false;
            return;
        }
        buffer[len] = '\n';
        len++;
        HAL_UART_Transmit_DMA(kHandle, reinterpret_cast<uint8_t*>(buffer), len);
    }

    static void WriteLineBlocking(const char* format, ...) {
        WaitTransmit();
        va_list args;
        va_start(args, format);
        int len = vsnprintf(buffer, sizeof(buffer) - 1, format, args);
        va_end(args);
        if (len < 0 || len > static_cast<int>(sizeof(buffer) - 1)) {
            return;
        }
        buffer[len] = '\n';
        len++;
        HAL_UART_Transmit(kHandle, reinterpret_cast<uint8_t*>(buffer), len, kTransmitTimeout);
    }

    static void WriteDMA(const char* format, ...) {
        WaitTransmit();
        isTransmitting = true;
        va_list args;
        va_start(args, format);
        auto len = vsnprintf(buffer, sizeof(buffer), format, args);
        va_end(args);
        if (len < 0 || len > static_cast<int>(sizeof(buffer))) {
            isTransmitting = false;
            return;
        }
        HAL_UART_Transmit_DMA(kHandle, reinterpret_cast<uint8_t*>(buffer), len);
    }

    static void WriteBlocking(const char* format, ...) {
        WaitTransmit();
        va_list args;
        va_start(args, format);
        auto len = vsnprintf(buffer, sizeof(buffer), format, args);
        va_end(args);
        if (len < 0 || len > static_cast<int>(sizeof(buffer))) {
            return;
        }
        HAL_UART_Transmit(kHandle, reinterpret_cast<uint8_t*>(buffer), strlen(buffer), kTransmitTimeout);
    }

    static void WriteBytesDMA(const uint8_t* data, size_t size) {
        WaitTransmit();
        isTransmitting = true;
        HAL_UART_Transmit_DMA(kHandle, const_cast<uint8_t*>(data), size);
    }

    static void WriteBytesBlocking(const uint8_t* data, size_t size) {
        WaitTransmit();
        HAL_UART_Transmit(kHandle, const_cast<uint8_t*>(data), size, kTransmitTimeout);
    }
};
