#pragma once

#include "main.h"
#include "stm32h5xx_hal_uart.h"
#include <cstdarg>
#include <cstdint>
#include <cstring>
#include <memory>
#include <string>

class SerialPort {
private:
    UART_HandleTypeDef* handle;
    uint32_t timeout;
    std::unique_ptr<char[]> buffer;

    uint8_t ReadByteForce() {
        return handle->Instance->RDR;
    }

public:
    SerialPort(UART_HandleTypeDef* uart, uint32_t baudRate, uint32_t length, uint32_t timeout)
        : handle(uart), timeout(timeout), buffer(std::make_unique<char[]>(length)) {
        SetBaudRate(baudRate);
    }

    ~SerialPort() = default;
    SerialPort(const SerialPort&)            = delete;
    SerialPort& operator=(const SerialPort&) = delete;
    SerialPort(SerialPort&&)                 = delete;
    SerialPort& operator=(SerialPort&&)      = delete;

    void SetBaudRate(uint32_t baudRate) {
        HAL_UART_DeInit(handle);
        handle->Init.BaudRate = baudRate;
        HAL_UART_Init(handle);
    }

    std::string ReadLine() {
        std::string result;
        result.reserve(128);
        char c;
        ReadByteForce();
        while (true) {
            auto r = HAL_UART_Receive(handle, reinterpret_cast<uint8_t*>(&c), sizeof(c), timeout);
            if (r != HAL_OK) {
                throw std::runtime_error("UART receive error");
            }
            if (c == '\n') {
                if (!result.empty() && result.back() == '\r') {
                    result.pop_back();
                }
                break;
            }
            result.push_back(c);
        }
        return result;
    }

    void ReadBytesBlocking(uint8_t* data, size_t size) {
        auto r = HAL_UART_Receive(handle, data, size, timeout);
        if (r != HAL_OK) {
            throw std::runtime_error("UART receive error");
        }
    }

    void WriteBytesBlocking(const uint8_t* data, size_t size) {
        HAL_UART_Transmit(handle, const_cast<uint8_t*>(data), size, timeout);
    }

    void WriteBlocking(const char* format, ...) {

        va_list args;
        va_start(args, format);
        auto len = vsnprintf(buffer.get(), sizeof(buffer), format, args);
        va_end(args);
        if (len < 0 || len > static_cast<int>(sizeof(buffer))) {
            return;
        }
        HAL_UART_Transmit(handle, reinterpret_cast<uint8_t*>(buffer.get()), strlen(buffer.get()), timeout);
    }

    void WriteLineBlocking(const char* format, ...) {
        va_list args;
        va_start(args, format);
        int len = vsnprintf(buffer.get(), 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(handle, reinterpret_cast<uint8_t*>(buffer.get()), len, timeout);
    }
};
