#include "air530z.h"
#include "../swapi/subjects/serial/serial.h"
#include "./nmealib/include/nmea/nmea.h"

// 模块名称
static const char MODULE_NAME[] = "Air530Z";

// 设置输出频率
static const char *PCAS02 = "$PCAS02,1000*2E\r\n";
// 设置输出格式
static const char *PCAS03 = "$PCAS03,1,0,0,1,1,0,0,0,0,0,,,0,0*03\r\n";
// 设置卫星组合
static const char *PCAS04 = "$PCAS04,13,13,11*29\r\n";
// 设置接收模式: "静态模式"
static const char *PCAS11 = "$PCAS11,1*1C\r\n";

// 定义关键控制符号
#define LF '\n' // 换行

// 输出控制精度阈值
#define HDOP_MAX               2.0 // 水平精度判定阈值
#define MIN_SATELLITES         5   // 最小可见卫星(个)
#define STATIONARY_RADIUS      3.0 // 静止判定半径(米)
#define CONT_STATIONARY_FRAMES 4   // 累计连续静止帧数
#define POS_SMOOTHING_NORMAL   0.6 // 正常定位滑动系数
#define POS_SMOOTHING_WEAK     0.2 // 弱定位时滑动系数
#define CONT_WEAK_FRAMES       8   // 连续弱定位的帧数

// 定义无效的坐标值
#define COORD_NAN NAN
#define IS_COORD_NAN(x) isnan(x)

// 与模块的串口通讯
typedef struct
{
  void *h; // 打开的串口句柄

  nmeaINFO info;     // decoded NMEA data
  nmeaPARSER parser; // NMEA decoder解码器
  struct
  { // 存储经过平滑处理的经纬度坐标(滤波轨迹)
    double lat;
    double lon;
    int stationary_cnt; // 连续静止帧数统计
    int weak_cnt; // 连续弱定位帧数统计
  } filter;

  struct
  {
    bool bOK; // 定位是否成功
    char lat[MAX_LINE_CHARS]; // 纬度字符串
    char lon[MAX_LINE_CHARS]; // 经度字符串
  } outPos2d;
  void *out_rwlock;
} SAir530ZCom;

static SAir530ZCom s_myCom;

// 接收、处理来自Air530Z模块的数据报文帧, 串口-线程回调
static int comio_data_recv_proc(unsigned long wParam/*传递打开的串口句柄*/, unsigned long lParam/*保留暂未使用*/)
{
  SAir530ZCom *pComIO = &s_myCom; void *pSerial = pComIO->h;
  const unsigned char *pRecvBuf = serial_get_recv_buffer(pSerial); int nRecvBytes = serial_get_recv_buffer_bytes(pSerial);
  const char *log_prefix = serial_get_log_prefix(pSerial); int ret; bool bWeakPos;

  if (nRecvBytes <= 0 || pRecvBuf[nRecvBytes - 1] != LF) goto ret_p;

  sw_log_trace("[%s] %s received a frame(%d bytes): %s", MODULE_NAME, log_prefix, nRecvBytes, pRecvBuf);

  ret = nmea_parse(&pComIO->parser, (const char *)pRecvBuf, nRecvBytes, &pComIO->info);
  serial_clear_recv_buffer(pSerial); // 当前数据帧已解析, 清空接收缓存区, 等待下一数据帧到来
  if(ret <= 0) { /*sw_log_error("[%s] failed to parse a NMEA frame!!", MODULE_NAME);*/ goto ret_p; }

  if(pComIO->info.sig > 0)
  {
    if(pComIO->info.HDOP <= HDOP_MAX && pComIO->info.satinfo.inview >= MIN_SATELLITES) { bWeakPos = false; pComIO->filter.weak_cnt = 0; }
    else { bWeakPos = true; pComIO->filter.weak_cnt++; }

    double lat = pComIO->info.lat, lon = pComIO->info.lon;

    if(IS_COORD_NAN(pComIO->filter.lat) || IS_COORD_NAN(pComIO->filter.lon))
    {
      pComIO->filter.lat = lat;
      pComIO->filter.lon = lon;
    }

    nmeaPOS pos1, pos2;
    nmea_info2pos(&pComIO->info, &pos1);
    pos2.lat = nmea_ndeg2radian(pComIO->filter.lat);
    pos2.lon = nmea_ndeg2radian(pComIO->filter.lon);

    double dist = nmea_distance(&pos2, &pos1);
    if(dist < STATIONARY_RADIUS) { pComIO->filter.stationary_cnt++; }
    else
    {
      pComIO->filter.stationary_cnt = 0;
      if(bWeakPos) pComIO->filter.lat = pComIO->filter.lon = COORD_NAN;
      else { pComIO->filter.lat = lat; pComIO->filter.lon = lon; }
    }

    if(pComIO->filter.stationary_cnt >= CONT_STATIONARY_FRAMES)
    { /////静止状态处理
      // 1-应用滑动平均
      double smoothing = bWeakPos ? POS_SMOOTHING_WEAK : POS_SMOOTHING_NORMAL;
      pComIO->filter.lat = smoothing * lat + (1 - smoothing) * pComIO->filter.lat;
      pComIO->filter.lon = smoothing * lon + (1 - smoothing) * pComIO->filter.lon;

      // 2-输出成字符串
      double out_lat = nmea_ndeg2degree(pComIO->filter.lat); double out_lon = nmea_ndeg2degree(pComIO->filter.lon);

      sw_rwlock_wrlock(pComIO->out_rwlock, WAIT_FOREVER);
      sprintf(pComIO->outPos2d.lat, "%.6f", out_lat);
      sprintf(pComIO->outPos2d.lon, "%.6f", out_lon);
      pComIO->outPos2d.bOK = true;
      sw_rwlock_unlock(pComIO->out_rwlock);

      // 3-打印定位结果
      sw_log_trace("[%s] 已定位: lat=%.6f, lon=%.6f, HDOP=%.1f, Sats=%d/%d, DeltaMeters=%.1fm, IsWeakPos=%d", MODULE_NAME,
                   out_lat, out_lon, pComIO->info.HDOP,
                   pComIO->info.satinfo.inuse, pComIO->info.satinfo.inview, dist, bWeakPos);
    }

    if(pComIO->filter.weak_cnt >= CONT_WEAK_FRAMES)
    {
      pComIO->filter.weak_cnt = pComIO->filter.stationary_cnt = 0;
      pComIO->filter.lat = pComIO->filter.lon = COORD_NAN;
    }
  }

ret_p: return 1; // 持续累加的接收新数据
}

// 打开与模块的通讯, 返回: 0成功, <0时失败
int Air530Z_ComInit()
{
  nmea_zero_INFO(&s_myCom.info);
  s_myCom.filter.lat = s_myCom.filter.lon = COORD_NAN;
  nmea_parser_init(&s_myCom.parser);

  s_myCom.out_rwlock = sw_rwlock_create();
  if(!s_myCom.out_rwlock)
  {
    sw_log_error("[%s] failed to create the \"outPos2d\" rwlock!!", MODULE_NAME);
    Air530Z_ComExit(); return -1;
  }

#ifdef _DEBUG // 上位机单元测试时使用
  const char *serialName = "/dev/ttyS0";   int baudrate = 9600;
#else
  const char *serialName = "/dev/ttymxc6"; int baudrate = 9600;
#endif
  const char *parityCheck = "none"; // 无校检
  s_myCom.h = serial_open(serialName, baudrate, parityCheck,
                          comio_data_recv_proc, comio_data_recv_proc, NULL);
  if(!s_myCom.h)
  {
    sw_log_error("[%s] failed to open the \"%s:%d(%s parity)\" device!!",
                 MODULE_NAME, serialName, baudrate, parityCheck);
    Air530Z_ComExit(); return -2;
  }

  serial_send_data(s_myCom.h, (unsigned char *)PCAS02, strlen(PCAS02));
  serial_send_data(s_myCom.h, (unsigned char *)PCAS03, strlen(PCAS03));
  serial_send_data(s_myCom.h, (unsigned char *)PCAS04, strlen(PCAS04));
  serial_send_data(s_myCom.h, (unsigned char *)PCAS11, strlen(PCAS11));

  return 0;
}

// 关闭与模块的通讯, 返回: 0成功, <0时失败
int Air530Z_ComExit()
{
  if(s_myCom.h) serial_close(s_myCom.h, WAITTHRD_SAFEEXIT_TIMEOUT);
  if(s_myCom.out_rwlock) sw_rwlock_destroy(s_myCom.out_rwlock);
  if(s_myCom.parser.buffer) nmea_parser_destroy(&s_myCom.parser);
  memset(&s_myCom, 0, sizeof(s_myCom));
  return 0;
}

// 获取当前的2D位置, 返回: 0成功, -1未定位
int Air530Z_GetPos2D(char lat[MAX_LINE_CHARS], char lon[MAX_LINE_CHARS])
{
  bool bOK = false;
  if(!s_myCom.h || !s_myCom.out_rwlock) return -1; // 防御检查, 加强健壮性
  sw_rwlock_rdlock(s_myCom.out_rwlock, WAIT_FOREVER);
  bOK = s_myCom.outPos2d.bOK;
  if(bOK) {
    strcpy(lat, s_myCom.outPos2d.lat);
    strcpy(lon, s_myCom.outPos2d.lon);
  }
  sw_rwlock_unlock(s_myCom.out_rwlock);
  return bOK ? 0 : -1;
}

#ifdef _DEBUG
void *Air530Z_GetSerialHandle()
{
  return s_myCom.h;
}
#endif
