/**
 * @file uart.cpp
 * @brief UART wrapper component implementation
 * @author Mahmoud Elmohtady
 * @company Nabd solutions - ASF
 * @copyright Copyright (c) 2025
 */

#include "uart.hpp"
#include "logger.hpp"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <cstring>
#include <inttypes.h>

static const char* TAG = "UART_WRAPPER";

/**
 * @brief Constructor - Initialize UART wrapper instance
 * @details Initializes all UART ports as not initialized
 */
Uart::Uart()
{
    m_isInitialized_[0] = false;
    m_isInitialized_[1] = false;
    m_isInitialized_[2] = false;
    ASF_LOGI(TAG, 2600, asf::logger::Criticality::LOW, "UART wrapper initialized");
}

/**
 * @brief Destructor - Clean up UART wrapper resources
 * @details Deinitializes all active UART ports
 */
Uart::~Uart()
{
    // Deinitialize all ports
    for (int i = 0; i < 3; i++) {
        if (m_isInitialized_[i]) {
            deinitialize(static_cast<UartPort>(i));
        }
    }
    ASF_LOGI(TAG, 2601, asf::logger::Criticality::LOW, "UART wrapper destroyed");
}

bool Uart::initialize(UartPort port, const UartConfig& config)
{
    uint8_t portIdx = static_cast<uint8_t>(port);
    uart_port_t uartPort = convertPort(port);
    
    if (m_isInitialized_[portIdx]) {
        ASF_LOGW(TAG, 2602, asf::logger::Criticality::MEDIUM, "UART port %d already initialized", portIdx);
        return true;
    }

    // Configure UART parameters
    uart_config_t uartConfig = convertConfig(config);
    
    esp_err_t ret = uart_param_config(uartPort, &uartConfig);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2603, asf::logger::Criticality::HIGH, "Failed to configure UART port %d: %s", portIdx, esp_err_to_name(ret));
        return false;
    }

    // Set UART pins
    ret = uart_set_pin(uartPort, config.txPin, config.rxPin, 
                       config.rtsPin, config.ctsPin);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2604, asf::logger::Criticality::HIGH, "Failed to set UART pins for port %d: %s", portIdx, esp_err_to_name(ret));
        return false;
    }

    // Install UART driver
    ret = uart_driver_install(uartPort, config.rxBufferSize, config.txBufferSize, 
                               config.eventQueueSize, nullptr, 0);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2605, asf::logger::Criticality::HIGH, "Failed to install UART driver for port %d: %s", portIdx, esp_err_to_name(ret));
        return false;
    }

    m_isInitialized_[portIdx] = true;
    ASF_LOGI(TAG, 2606, asf::logger::Criticality::LOW, "UART initialized successfully");
    return true;
}

bool Uart::deinitialize(UartPort port)
{
    uint8_t portIdx = static_cast<uint8_t>(port);
    uart_port_t uartPort = convertPort(port);
    
    if (!m_isInitialized_[portIdx]) {
        ASF_LOGW(TAG, 2607, asf::logger::Criticality::MEDIUM, "UART port %d not initialized", portIdx);
        return true;
    }

    esp_err_t ret = uart_driver_delete(uartPort);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2608, asf::logger::Criticality::HIGH, "Failed to delete UART driver for port %d: %s", portIdx, esp_err_to_name(ret));
        return false;
    }

    m_isInitialized_[portIdx] = false;
    ASF_LOGI(TAG, 2609, asf::logger::Criticality::LOW, "UART port %d deinitialized", portIdx);
    return true;
}

int32_t Uart::transmit(UartPort port, const uint8_t* data, size_t length, uint32_t timeoutMs)
{
    uint8_t portIdx = static_cast<uint8_t>(port);
    uart_port_t uartPort = convertPort(port);
    
    if (!m_isInitialized_[portIdx]) {
        ASF_LOGE(TAG, 2610, asf::logger::Criticality::HIGH, "UART port %d not initialized", portIdx);
        return -1;
    }
    
    if (data == nullptr || length == 0) {
        ASF_LOGE(TAG, 2611, asf::logger::Criticality::HIGH, "Invalid data or length");
        return -1;
    }

    TickType_t timeout = pdMS_TO_TICKS(timeoutMs);
    int bytesWritten = uart_write_bytes(uartPort, data, length);
    
    if (bytesWritten < 0) {
        ASF_LOGE(TAG, 2612, asf::logger::Criticality::HIGH, "Failed to write to UART port %d", portIdx);
        return -1;
    }

    // Wait for transmission to complete
    esp_err_t ret = uart_wait_tx_done(uartPort, timeout);
    if (ret != ESP_OK) {
        ASF_LOGW(TAG, 2613, asf::logger::Criticality::MEDIUM, "TX timeout for UART port %d", portIdx);
    }

    return bytesWritten;
}

int32_t Uart::receive(UartPort port, uint8_t* data, size_t maxLength, uint32_t timeoutMs)
{
    uint8_t portIdx = static_cast<uint8_t>(port);
    uart_port_t uartPort = convertPort(port);
    
    if (!m_isInitialized_[portIdx]) {
        ASF_LOGE(TAG, 2614, asf::logger::Criticality::HIGH, "UART port %d not initialized", portIdx);
        return -1;
    }
    
    if (data == nullptr || maxLength == 0) {
        ASF_LOGE(TAG, 2615, asf::logger::Criticality::HIGH, "Invalid data buffer or length");
        return -1;
    }

    TickType_t timeout = pdMS_TO_TICKS(timeoutMs);
    int bytesRead = uart_read_bytes(uartPort, data, maxLength, timeout);
    
    if (bytesRead < 0) {
        ASF_LOGE(TAG, 2616, asf::logger::Criticality::HIGH, "Failed to read from UART port %d", portIdx);
        return -1;
    }

    return bytesRead;
}

int32_t Uart::getBytesAvailable(UartPort port)
{
    uint8_t portIdx = static_cast<uint8_t>(port);
    uart_port_t uartPort = convertPort(port);
    
    if (!m_isInitialized_[portIdx]) {
        ASF_LOGE(TAG, 2617, asf::logger::Criticality::HIGH, "UART port %d not initialized", portIdx);
        return -1;
    }

    size_t bytesAvailable = 0;
    esp_err_t ret = uart_get_buffered_data_len(uartPort, &bytesAvailable);
    
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2618, asf::logger::Criticality::HIGH, "Failed to get buffered data length for port %d: %s", 
                 portIdx, esp_err_to_name(ret));
        return -1;
    }

    return static_cast<int32_t>(bytesAvailable);
}

bool Uart::flushTx(UartPort port)
{
    uint8_t portIdx = static_cast<uint8_t>(port);
    uart_port_t uartPort = convertPort(port);
    
    if (!m_isInitialized_[portIdx]) {
        ASF_LOGE(TAG, 2619, asf::logger::Criticality::HIGH, "UART port %d not initialized", portIdx);
        return false;
    }

    esp_err_t ret = uart_flush_input(uartPort);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2620, asf::logger::Criticality::HIGH, "Failed to flush TX for port %d: %s", portIdx, esp_err_to_name(ret));
        return false;
    }

    return true;
}

bool Uart::flushRx(UartPort port)
{
    uint8_t portIdx = static_cast<uint8_t>(port);
    uart_port_t uartPort = convertPort(port);
    
    if (!m_isInitialized_[portIdx]) {
        ASF_LOGE(TAG, 2621, asf::logger::Criticality::HIGH, "UART port %d not initialized", portIdx);
        return false;
    }

    esp_err_t ret = uart_flush_input(uartPort);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2622, asf::logger::Criticality::HIGH, "Failed to flush RX for port %d: %s", portIdx, esp_err_to_name(ret));
        return false;
    }

    return true;
}

bool Uart::setBaudrate(UartPort port, UartBaudrate baudrate)
{
    uint8_t portIdx = static_cast<uint8_t>(port);
    uart_port_t uartPort = convertPort(port);
    
    if (!m_isInitialized_[portIdx]) {
        ASF_LOGE(TAG, 2623, asf::logger::Criticality::HIGH, "UART port %d not initialized", portIdx);
        return false;
    }

    esp_err_t ret = uart_set_baudrate(uartPort, static_cast<uint32_t>(baudrate));
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2624, asf::logger::Criticality::HIGH, "Failed to set baudrate for port %d: %s", portIdx, esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2625, asf::logger::Criticality::LOW, "Baudrate set to %" PRIu32 " for port %d", static_cast<uint32_t>(baudrate), portIdx);
    return true;
}

bool Uart::isInitialized(UartPort port) const
{
    uint8_t portIdx = static_cast<uint8_t>(port);
    return m_isInitialized_[portIdx];
}

UartConfig Uart::getDefaultConfig()
{
    UartConfig config = {};
    config.baudrate = UartBaudrate::BAUD_115200;
    config.txPin = UART_PIN_NO_CHANGE;
    config.rxPin = UART_PIN_NO_CHANGE;
    config.rtsPin = UART_PIN_NO_CHANGE;
    config.ctsPin = UART_PIN_NO_CHANGE;
    config.dataBits = UartDataBits::DATA_8_BITS;
    config.parity = UartParity::PARITY_DISABLE;
    config.stopBits = UartStopBits::STOP_BITS_1;
    config.flowControl = UartFlowControl::FLOW_CTRL_DISABLE;
    config.rxBufferSize = 1024;
    config.txBufferSize = 1024;
    config.eventQueueSize = 10;
    return config;
}

uart_port_t Uart::convertPort(UartPort port)
{
    switch (port) {
        case UartPort::PORT_0:
            return UART_NUM_0;
        case UartPort::PORT_1:
            return UART_NUM_1;
        case UartPort::PORT_2:
            return UART_NUM_2;
        default:
            return UART_NUM_0;
    }
}

uart_config_t Uart::convertConfig(const UartConfig& config)
{
    uart_config_t uartConfig = {};
    uartConfig.baud_rate = static_cast<int>(config.baudrate);
    uartConfig.data_bits = static_cast<uart_word_length_t>(config.dataBits);
    uartConfig.parity = static_cast<uart_parity_t>(config.parity);
    uartConfig.stop_bits = static_cast<uart_stop_bits_t>(config.stopBits);
    uartConfig.flow_ctrl = static_cast<uart_hw_flowcontrol_t>(config.flowControl);
    uartConfig.rx_flow_ctrl_thresh = 122;
    uartConfig.source_clk = UART_SCLK_DEFAULT;
    return uartConfig;
}
