/**
 * @file rs485_driver.c
 * @brief Implementation file for RS-485 communication driver.
 *
 * This driver provides functions to configure and control RS-485 communication
 * using USART with RTS pin as TX enable. Each device has an 8-bit address
 * defined by 4 bits on PB12-PB15 (upper nibble) and 4 bits on PA2-PA5 (lower nibble),
 * all with pull-up resistors enabled.
 *
 * @note Based on STM32F103C8T6 microcontroller and USART configuration
 */

#include "rs485_driver.h"

/* --- Local Variables --- */
static rs485_address_t device_address;
static UART_HandleTypeDef huart1; /*!< Handle for USART1 */

/* --- Local Function Prototypes --- */
static void rs485_configure_gpio_address(void);
static void rs485_configure_usart1(void);

/**
 * @brief Initializes the RS-485 driver.
 * @note This function configures GPIOs for address detection and USART1 for communication.
 */
void rs485_init(void)
{
    /* Configure GPIOs for address detection */
    rs485_configure_gpio_address();

    /* Configure USART1 for RS-485 communication */
    rs485_configure_usart1();

    /* Get the current device address from GPIOs */
    device_address.full_address = rs485_get_address();
}

/**
 * @brief Gets the current device address from GPIOs.
 * @return The 8-bit device address.
 */
uint8_t rs485_get_address(void)
{
    return ((uint8_t) ((((LL_GPIO_ReadInputPort(GPIOB) & RS485_ADDR_MASK_HIGH)>>RS485_ADDR_BIT_SET_HIGH) | (LL_GPIO_ReadInputPort(GPIOA) & RS485_ADDR_MASK_LOW)>>RS485_ADDR_BIT_SET_LOW)));
}

/**
 * @brief Sends data to a specific RS-485 address.
 * @param address Target device address.
 * @param data Pointer to the data buffer.
 * @param length Number of bytes to send.
 * @retval HAL_OK on success, otherwise error code.
 */
HAL_StatusTypeDef rs485_send_to_address(uint8_t address, uint8_t* data, uint16_t length)
{
    /* Enable transmission mode */
    rs485_enable_tx();

    /* Send the address first */
    HAL_UART_Transmit(&huart1, &address, 1, HAL_MAX_DELAY);

    /* Send the data */
    HAL_StatusTypeDef status = HAL_UART_Transmit(&huart1, data, length, HAL_MAX_DELAY);

    /* Disable transmission mode after sending */
    rs485_disable_tx();

    return status;
}

/**
 * @brief Sends data to all devices (broadcast).
 * @param data Pointer to the data buffer.
 * @param length Number of bytes to send.
 * @retval HAL_OK on success, otherwise error code.
 */
HAL_StatusTypeDef rs485_send_broadcast(uint8_t* data, uint16_t length)
{
    /* Enable transmission mode */
    rs485_enable_tx();

    /* Send the broadcast address (0x00) first */
    uint8_t broadcast_address = 0x00;
    HAL_UART_Transmit(&huart1, &broadcast_address, 1, HAL_MAX_DELAY);

    /* Send the data */
    HAL_StatusTypeDef status = HAL_UART_Transmit(&huart1, data, length, HAL_MAX_DELAY);

    /* Disable transmission mode after sending */
    rs485_disable_tx();

    return status;
}

/**
 * @brief Receives data from RS-485 bus.
 * @param data Pointer to the receive buffer.
 * @param length Maximum number of bytes to receive.
 * @retval Number of bytes received, or 0 on error.
 */
uint16_t rs485_receive(uint8_t* data, uint16_t length)
{
    HAL_StatusTypeDef status;
    uint16_t bytes_received = 0;

    /* Enable reception mode (transmission disabled) */
    rs485_disable_tx();

    /* Receive data with timeout */
    status = HAL_UART_Receive(&huart1, data, length, HAL_MAX_DELAY);
    if (status == HAL_OK)
    {
        bytes_received = length;
    }

    return bytes_received;
}

/**
 * @brief Enables transmission mode on RS-485 transceiver.
 */
void rs485_enable_tx(void)
{
    /* Set TX EN pin high to enable transmitter */
    HAL_GPIO_WritePin(RS485_TX_EN_PORT, RS485_TX_EN_PIN, GPIO_PIN_SET);
}

/**
 * @brief Disables transmission mode on RS-485 transceiver.
 */
void rs485_disable_tx(void)
{
    /* Set TX EN pin low to disable transmitter */
    HAL_GPIO_WritePin(RS485_TX_EN_PORT, RS485_TX_EN_PIN, GPIO_PIN_RESET);
}

/**
 * @brief Configures GPIOs for address detection.
 */
static void rs485_configure_gpio_address(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    /* Enable clock for GPIOA and GPIOB */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();

    /* Configure PA2-PA5 as input with pull-up */
    GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* Configure PB12-PB15 as input with pull-up */
    GPIO_InitStruct.Pin = GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}

/**
 * @brief Configures USART1 for RS-485 communication.
 */
static void rs485_configure_usart1(void)
{
    /* Enable clock for USART1 */
    __HAL_RCC_USART1_CLK_ENABLE();

    huart1.Instance = USART1;
    huart1.Init.BaudRate = 9600;           /*!< Default baud rate for RS-485 */
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; /*!< Use NONE as TX enable */
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;

    if (HAL_UART_Init(&huart1) != HAL_OK)
    {
        /* Initialization Error */
        while(1);
    }
}
