/**
  ******************************************************************************
  * @file    gps_driver.c
  * @brief   GPS NMEA数据接收和解析驱动实现
  * @author  Your Name
  * @date    2026-02-07
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "gps_driver.h"

/* Private typedef -----------------------------------------------------------*/

/* Private define ------------------------------------------------------------*/

/* Private macro -------------------------------------------------------------*/

/* Private variables ---------------------------------------------------------*/
static uint8_t gps_rx_buffer[GPS_RX_BUFFER_SIZE];      // DMA接收缓冲区
static uint8_t gps_rx_byte;                             // 单字节接收缓冲
static char gps_nmea_buffer[GPS_NMEA_MAX_LENGTH];      // NMEA语句缓冲区
static uint16_t gps_nmea_index = 0;                     // NMEA缓冲区索引
static GPS_Data_t gps_data;                             // GPS数据

/* Private function prototypes -----------------------------------------------*/
static void GPS_ParseNMEA(char *nmea);
static void GPS_ParseGPGGA(char *nmea);
static void GPS_ParseGPRMC(char *nmea);
static double GPS_ConvertToDecimal(const char *coord, char direction);

/* Exported functions --------------------------------------------------------*/

/**
 * @brief  初始化GPS驱动
 * @retval HAL状态
 */
HAL_StatusTypeDef GPS_Init(void)
{
    HAL_StatusTypeDef status;
    
    // 清空GPS数据
    memset(&gps_data, 0, sizeof(GPS_Data_t));
    gps_data.data_valid = 0;
    
    // 清空缓冲区
    memset(gps_rx_buffer, 0, GPS_RX_BUFFER_SIZE);
    memset(gps_nmea_buffer, 0, GPS_NMEA_MAX_LENGTH);
    gps_nmea_index = 0;
    
    // 启动UART接收（单字节中断接收）
    status = HAL_UART_Receive_IT(&GPS_UART_HANDLE, &gps_rx_byte, 1);
    
    if (status == HAL_OK) {
        // 可选：启用UART空闲中断
        __HAL_UART_ENABLE_IT(&GPS_UART_HANDLE, UART_IT_IDLE);
    }
    
    return status;
}

/**
 * @brief  GPS数据接收处理（在主循环中调用）
 * @retval None
 */
void GPS_Process(void)
{
    // 检查数据是否超时
    if (gps_data.data_valid) {
        uint32_t current_tick = HAL_GetTick();
        if ((current_tick - gps_data.last_update_tick) > GPS_DATA_TIMEOUT_MS) {
            gps_data.data_valid = 0;  // 数据超时，标记为无效
        }
    }
}

/**
 * @brief  获取GPS数据
 * @param  gps_data_out: 指向GPS数据结构体的指针
 * @retval 1=数据有效, 0=数据无效或超时
 */
uint8_t GPS_GetData(GPS_Data_t *gps_data_out)
{
    if (gps_data_out == NULL) {
        return 0;
    }
    
    // 复制GPS数据
    memcpy(gps_data_out, &gps_data, sizeof(GPS_Data_t));
    
    return gps_data.data_valid;
}

/**
 * @brief  检查GPS数据是否有效
 * @retval 1=有效, 0=无效
 */
uint8_t GPS_IsDataValid(void)
{
    return gps_data.data_valid;
}

/**
 * @brief  获取GPS时间字符串
 * @param  buffer: 输出缓冲区
 * @param  size: 缓冲区大小
 * @retval None
 */
void GPS_GetTimeString(char *buffer, uint16_t size)
{
    if (buffer == NULL || size == 0) {
        return;
    }
    
    if (gps_data.data_valid) {
        snprintf(buffer, size, "%02d:%02d:%02d.%03d UTC",
                 gps_data.time.hour,
                 gps_data.time.minute,
                 gps_data.time.second,
                 gps_data.time.millisec);
    } else {
        snprintf(buffer, size, "No GPS Time");
    }
}

/**
 * @brief  获取GPS位置字符串
 * @param  buffer: 输出缓冲区
 * @param  size: 缓冲区大小
 * @retval None
 */
void GPS_GetPositionString(char *buffer, uint16_t size)
{
    if (buffer == NULL || size == 0) {
        return;
    }
    
    if (gps_data.data_valid) {
        snprintf(buffer, size, "Lat: %.6f%c, Lon: %.6f%c, Alt: %.1fm, Sats: %d",
                 gps_data.position.latitude,
                 gps_data.position.lat_direction,
                 gps_data.position.longitude,
                 gps_data.position.lon_direction,
                 gps_data.position.altitude,
                 gps_data.position.satellites);
    } else {
        snprintf(buffer, size, "No GPS Position");
    }
}

/**
 * @brief  获取GPS日期字符串（格式 YYYYMMDD）
 */
void GPS_GetDateString(char *buffer, uint16_t size)
{
    if (buffer == NULL || size == 0) return;

    if (gps_data.date_valid) {
        snprintf(buffer, size, "%04u%02u%02u",
                 gps_data.date.year,
                 gps_data.date.month,
                 gps_data.date.day);
    } else {
        buffer[0] = '\0';
    }
}

/**
 * @brief  UART接收完成回调函数
 * @param  huart: UART句柄
 * @retval None
 */
void GPS_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == GPS_UART_HANDLE.Instance) {
        // 处理接收到的字节
        char received_char = (char)gps_rx_byte;
        
        // 检测NMEA语句开始标志 '$'
        if (received_char == '$') {
            gps_nmea_index = 0;
            gps_nmea_buffer[gps_nmea_index++] = received_char;
        }
        // 检测NMEA语句结束标志 '\n'
        else if (received_char == '\n') {
            if (gps_nmea_index > 0 && gps_nmea_index < GPS_NMEA_MAX_LENGTH) {
                gps_nmea_buffer[gps_nmea_index] = '\0';  // 字符串结束符
                GPS_ParseNMEA(gps_nmea_buffer);          // 解析NMEA语句
                gps_nmea_index = 0;                      // 重置索引
            }
        }
        // 累积NMEA语句字符
        else if (gps_nmea_index > 0 && gps_nmea_index < (GPS_NMEA_MAX_LENGTH - 1)) {
            if (received_char != '\r') {  // 忽略回车符
                gps_nmea_buffer[gps_nmea_index++] = received_char;
            }
        }
        
        // 继续接收下一个字节
        HAL_UART_Receive_IT(&GPS_UART_HANDLE, &gps_rx_byte, 1);
    }
}

/**
 * @brief  UART空闲中断回调函数
 * @param  huart: UART句柄
 * @retval None
 */
void GPS_UART_IdleCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == GPS_UART_HANDLE.Instance) {
        // 可以在这里处理空闲中断
        // 目前使用单字节接收，不需要特殊处理
    }
}

/* Private functions ---------------------------------------------------------*/

/**
 * @brief  解析NMEA语句
 * @param  nmea: NMEA语句字符串
 * @retval None
 */
static void GPS_ParseNMEA(char *nmea)
{
    if (nmea == NULL || nmea[0] != '$') {
        return;
    }
    
    if (strncmp(nmea, "$GPGGA", 6) == 0 || strncmp(nmea, "$GNGGA", 6) == 0) {
        GPS_ParseGPGGA(nmea);
    } else if (strncmp(nmea, "$GPRMC", 6) == 0 || strncmp(nmea, "$GNRMC", 6) == 0) {
        GPS_ParseGPRMC(nmea);
    }
}

/**
 * @brief  解析GPGGA语句
 * @param  nmea: GPGGA语句字符串
 * @retval None
 * 
 * GPGGA格式示例:
 * $GPGGA,123519.00,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
 * 
 * 字段说明:
 * 0: $GPGGA
 * 1: UTC时间 (hhmmss.ss)
 * 2: 纬度 (ddmm.mmmm)
 * 3: 纬度方向 (N/S)
 * 4: 经度 (dddmm.mmmm)
 * 5: 经度方向 (E/W)
 * 6: 定位质量 (0=无效, 1=GPS, 2=DGPS, etc.)
 * 7: 使用的卫星数量
 * 8: HDOP水平精度因子
 * 9: 海拔高度
 * 10: 高度单位 (M)
 * 11: 大地水准面高度
 * 12: 高度单位 (M)
 * 13: 差分GPS数据年龄
 * 14: 差分参考站ID
 * 15: 校验和
 */
static void GPS_ParseGPGGA(char *nmea)
{
    char *token;
    char *saveptr;
    int field_index = 0;
    char temp_buffer[32];
    
    // 使用strtok_r进行字符串分割（线程安全）
    token = strtok_r(nmea, ",", &saveptr);
    
    while (token != NULL) {
        switch (field_index) {
            case 1:  // UTC时间
                if (strlen(token) >= 6) {
                    // 解析时间 hhmmss.ss
                    char hour_str[3] = {token[0], token[1], '\0'};
                    char min_str[3] = {token[2], token[3], '\0'};
                    char sec_str[3] = {token[4], token[5], '\0'};
                    
                    gps_data.time.hour = (uint8_t)atoi(hour_str);
                    gps_data.time.minute = (uint8_t)atoi(min_str);
                    gps_data.time.second = (uint8_t)atoi(sec_str);
                    
                    // 解析毫秒（如果有）
                    if (strlen(token) > 7 && token[6] == '.') {
                        char ms_str[4] = {0};
                        strncpy(ms_str, &token[7], 3);
                        gps_data.time.millisec = (uint16_t)atoi(ms_str);
                    } else {
                        gps_data.time.millisec = 0;
                    }
                }
                break;
                
            case 2:  // 纬度
                if (strlen(token) > 0) {
                    strncpy(temp_buffer, token, sizeof(temp_buffer) - 1);
                    temp_buffer[sizeof(temp_buffer) - 1] = '\0';
                }
                break;
                
            case 3:  // 纬度方向
                if (strlen(token) > 0) {
                    gps_data.position.lat_direction = token[0];
                    // 转换纬度为十进制度
                    gps_data.position.latitude = GPS_ConvertToDecimal(temp_buffer, token[0]);
                }
                break;
                
            case 4:  // 经度
                if (strlen(token) > 0) {
                    strncpy(temp_buffer, token, sizeof(temp_buffer) - 1);
                    temp_buffer[sizeof(temp_buffer) - 1] = '\0';
                }
                break;
                
            case 5:  // 经度方向
                if (strlen(token) > 0) {
                    gps_data.position.lon_direction = token[0];
                    // 转换经度为十进制度
                    gps_data.position.longitude = GPS_ConvertToDecimal(temp_buffer, token[0]);
                }
                break;
                
            case 6:  // 定位质量
                if (strlen(token) > 0) {
                    gps_data.position.fix_status = (GPS_FixStatus_t)atoi(token);
                }
                break;
                
            case 7:  // 卫星数量
                if (strlen(token) > 0) {
                    gps_data.position.satellites = (uint8_t)atoi(token);
                }
                break;
                
            case 8:  // HDOP
                if (strlen(token) > 0) {
                    gps_data.position.hdop = (float)atof(token);
                }
                break;
                
            case 9:  // 海拔高度
                if (strlen(token) > 0) {
                    gps_data.position.altitude = atof(token);
                }
                break;
                
            default:
                break;
        }
        
        token = strtok_r(NULL, ",", &saveptr);
        field_index++;
    }
    
    // 更新数据有效标志和时间戳
    if (gps_data.position.fix_status > GPS_FIX_INVALID) {
        gps_data.data_valid = 1;
        gps_data.last_update_tick = HAL_GetTick();
    } else {
        gps_data.data_valid = 0;
    }
}

/**
 * @brief  解析 GPRMC / GNRMC 语句，提取日期字段
 *
 * GPRMC 格式:
 *   $GPRMC,HHMMSS.ss,A,lat,N,lon,E,speed,course,DDMMYY,mv,mvE*CS
 *   field 0: $GPRMC
 *   field 1: 时间 HHMMSS.ss
 *   field 2: 状态 A=有效 V=无效
 *   field 9: 日期 DDMMYY  ← 目标字段
 */
static void GPS_ParseGPRMC(char *nmea)
{
    char *token;
    char *saveptr;
    int   field_index = 0;
    uint8_t status_valid = 0;

    token = strtok_r(nmea, ",", &saveptr);
    while (token != NULL) {
        switch (field_index) {
            case 2:  // 状态
                status_valid = (token[0] == 'A') ? 1 : 0;
                break;

            case 9:  // 日期 DDMMYY
                if (status_valid && strlen(token) >= 6) {
                    char dd[3] = {token[0], token[1], '\0'};
                    char mm[3] = {token[2], token[3], '\0'};
                    char yy[3] = {token[4], token[5], '\0'};
                    gps_data.date.day   = (uint8_t)atoi(dd);
                    gps_data.date.month = (uint8_t)atoi(mm);
                    gps_data.date.year  = 2000u + (uint16_t)atoi(yy);
                    gps_data.date_valid = 1;
                }
                break;

            default:
                break;
        }
        token = strtok_r(NULL, ",", &saveptr);
        field_index++;
    }
}

/**
 * @brief  将GPS坐标格式转换为十进制度
 * @param  coord: GPS坐标字符串 (ddmm.mmmm 或 dddmm.mmmm)
 * @param  direction: 方向字符 (N/S/E/W)
 * @retval 十进制度数
 *
 * 示例:
 * 输入: "4807.038", 'N'
 * 输出: 48.1173 (度)
 */
static double GPS_ConvertToDecimal(const char *coord, char direction)
{
    if (coord == NULL || strlen(coord) < 4) {
        return 0.0;
    }
    
    double value = atof(coord);
    
    // 判断是纬度还是经度（纬度2位度数，经度3位度数）
    int degree_digits = (strlen(coord) >= 5 && coord[4] == '.') ? 2 : 3;
    
    // 提取度数和分钟
    double degrees = (int)(value / 100.0);
    double minutes = value - (degrees * 100.0);
    
    // 转换为十进制度
    double decimal = degrees + (minutes / 60.0);
    
    // 根据方向调整符号
    if (direction == 'S' || direction == 'W') {
        decimal = -decimal;
    }
    
    return decimal;
}
