#include <unistd.h>
 #include <array>
 #include <sys/ioctl.h>
+
+#include <string>
+
 #include <common/mavlink.h>
+#include <spdlog/spdlog.h>
 
-#include "util.h"
 #include "system_def.h"
 
+static const char GNSSRTK_RSP_INIT ='$';
+static const char GNSSRTK_RSP_DELIMITER = ',';
+static const std::string GNSSRTK_RSP_GNGGA = "$GNGGA";
+static const std::string GNSSRTK_RSP_GNRMC = "$GNRMC";   
+
+static const int GNSSRTK_DRV_TX_BUFFER_SIZE = 100;
+static const int GNSSRTK_DRV_RX_BUFFER_SIZE = 300;
+
 static int serial_fd;
 static std::shared_ptr<spdlog::logger> logger;
 
-err_t init_uart(std::shared_ptr<spdlog::logger> logg)
+typedef struct {
+    std::string time;
+    std::string latitude;
+    std::string latitude_hem;
+    std::string longitude;
+    std::string longitude_hem;
+    std::string status; // 0 = no fix, 1 = GPS fix, 2 = DGPS fix, 3 = PPS fix, 4 = RTK fixed, 5 = RTK float
+    std::string number_of_sattelites;
+    std::string h_dilution_of_pos;
+    std::string altitude;
+} gnssrtk_gngga_t;
+
+typedef struct {
+    std::string time;
+    std::string status; 
+    std::string latitude;
+    std::string latitude_hem;
+    std::string longitude;
+    std::string longitude_hem;
+    std::string ground_speed;
+    std::string ground_heading;
+    std::string date;
+    std::string magnetic_declination;
+    std::string magnetic_declination_direction;
+    std::string mode;
+} gnssrtk_gnrmc_t;
+
+static AtomicWrap<gnssrtk_gngga_t> latest_gngga;
+static AtomicWrap<gnssrtk_gnrmc_t> latest_gnrmc;
+
+err_t init_uart(std::shared_ptr<spdlog::logger> logg,
+                const std::string& device,
+                unsigned int baud_rate)
 {
     logger = logg;
 
     struct termios options;
-    serial_fd = open(SERIAL_DEVICE, O_RDONLY | O_NOCTTY);
+    serial_fd = open(device.c_str(), O_RDONLY | O_NOCTTY);
     if (serial_fd == -1)
     {
         logger->error("Failed to open serial port.");
        options.c_cflag |= CREAD; // Enable receiver
        options.c_lflag &= ~(ICANON | ISIG); // Turn off canonical mode and signals
 
-    cfsetspeed(&options, BAUD_RATE);
+    cfsetspeed(&options, baud_rate);
 
     if (tcsetattr(serial_fd, TCSANOW, &options))
     {
     return OK;
 }
 
-void application_task(pipe_fd_t sendpipe_fd)
+static gnssrtk_gngga_t parse_gngga(std::string& NMEA_msg)
+{
+    gnssrtk_gngga_t gngga_msg;
+    std::size_t pos = 0;
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gngga_msg.time = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gngga_msg.latitude = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gngga_msg.latitude_hem = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gngga_msg.longitude = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gngga_msg.longitude_hem = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gngga_msg.status = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gngga_msg.number_of_sattelites = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gngga_msg.h_dilution_of_pos = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gngga_msg.altitude = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    return gngga_msg;
+}
+
+static gnssrtk_gnrmc_t parse_gnrmc(std::string& NMEA_msg)
+{
+    gnssrtk_gnrmc_t gnrmc_msg;
+    std::size_t pos = 0;
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gnrmc_msg.time = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gnrmc_msg.status = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gnrmc_msg.latitude = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gnrmc_msg.latitude_hem= NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gnrmc_msg.longitude = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gnrmc_msg.longitude_hem = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gnrmc_msg.ground_speed = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gnrmc_msg.ground_heading = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gnrmc_msg.date = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gnrmc_msg.magnetic_declination = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gnrmc_msg.magnetic_declination_direction = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
+    gnrmc_msg.mode = NMEA_msg.substr(0, pos);
+    NMEA_msg.erase(0, pos + 1);
+
+    return gnrmc_msg;
+}
+
+[[noreturn]] void uart_task()
 {
     char recv_buffer[GNSSRTK_DRV_RX_BUFFER_SIZE];
     ioctl(serial_fd, TCFLSH, 2); // flush
             }
         }
 
-        auto NMEA_msg = std::string(recv_buffer);
-        logger->debug("GNSS message recieved: {}", NMEA_msg);
+        auto msg = std::string(recv_buffer);
+        
 
         std::size_t pos;
-        pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
-        auto header = NMEA_msg.substr(0, pos);
-        NMEA_msg.erase(0, pos + 1);
-
-        pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
-        auto time = NMEA_msg.substr(0, pos);
-        NMEA_msg.erase(0, pos + 1);
-
-        pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
-        auto lat = NMEA_msg.substr(0, pos);
-        NMEA_msg.erase(0, pos + 1);
-
-        pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
-        auto lat_hem = NMEA_msg.substr(0, pos);
-        NMEA_msg.erase(0, pos + 1);
-
-        pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
-        auto lon = NMEA_msg.substr(0, pos);
-        NMEA_msg.erase(0, pos + 1);
-
-        pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
-        auto lon_hem = NMEA_msg.substr(0, pos);
-        NMEA_msg.erase(0, pos + 1);
-
-        pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
-        auto status = NMEA_msg.substr(0, pos);
-        NMEA_msg.erase(0, pos + 1);
-
-        pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
-        auto num_of_sattelites = NMEA_msg.substr(0, pos);
-        NMEA_msg.erase(0, pos + 1);
-
-        pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
-        auto HDOP = NMEA_msg.substr(0, pos);
-        NMEA_msg.erase(0, pos + 1);
-
-        pos = NMEA_msg.find(GNSSRTK_RSP_DELIMITER);
-        auto alt = NMEA_msg.substr(0, pos);
-        NMEA_msg.erase(0, pos + 1);
-
-        // logger->info("header: {}", header);
-        // logger->info("time: {}", time);
-        // logger->info("lat: {}", lat);
-        // logger->info("lat_hem: {}", lat_hem);
-        // logger->info("lon: {}", lon);
-        // logger->info("lon_hem: {}", lon_hem);
-        // logger->info("status: {}", status);
-        // logger->info("num_of_sattelites: {}", num_of_sattelites);
-        // logger->info("hdop: {}", HDOP);
-        // logger->info("alt: {}", alt);
-
-        logger->debug("Lat: {}, Lon: {}", static_cast<int32_t>((std::stoi(lat.substr(0, 2)) + (std::stod(lat.substr(2)) / 60)) * 1e7), static_cast<int32_t>((std::stoi(lon.substr(0, 3)) + (std::stod(lon.substr(3)) / 60)) * 1e7));
+        pos = msg.find(GNSSRTK_RSP_DELIMITER);
+        auto header = msg.substr(0, pos);
+        msg.erase(0, pos + 1);
+
+        
+
+        if (header == GNSSRTK_RSP_GNGGA)
+        {
+            latest_gngga.set(parse_gngga(msg));
+            logger->debug("GNGGA message recieved and parsed.");
+        }
+        else if (header == GNSSRTK_RSP_GNRMC)
+        {
+            latest_gnrmc.set(parse_gnrmc(msg));
+            logger->debug("GNRMC message recieved and parsed.");
+        }
+        else
+        {
+            logger->warn("Unknown NMEA-header: {}", header);
+        }
+    }
+}
+
+[[noreturn]] void mavlink_task(pipe_fd_t send_pipe)
+{
+    while (true)
+    {
+        THREAD_SLEEP(GNSS_MAVLINK_PERIOD);
+
+        auto gngga = latest_gngga.get();
+        auto gnrmc = latest_gnrmc.get();
+
+        if (gngga.latitude != gnrmc.latitude || gngga.longitude != gnrmc.longitude)
+        {
+            logger->warn("GNGGA and GNRMC position information mismatch.");
+        }
+
+        logger->debug("Constructing MAVLINK messages.");
 
         mavlink_message_t mavlink_msg;
         mavlink_global_position_int_t position;
-        position.time_boot_ms = system_uptime_ms();
-        position.lat = static_cast<int32_t>((std::stoi(lat.substr(0, 2)) + (std::stod(lat.substr(2)) / 60)) * 1e7);
-        position.lon = static_cast<int32_t>((std::stoi(lon.substr(0, 3)) + (std::stod(lon.substr(3)) / 60)) * 1e7);
-        position.alt = static_cast<int32_t>(std::stod(alt) * 1000);
-        position.relative_alt = INT32_MAX;
+        mavlink_gps_raw_int_t raw_gps;
+
+        uint8_t buffer[MAVLINK_MAX_PACKET_LEN];
+        int len;
+        double u_time_ms = system_uptime_ms();
+
+        position.time_boot_ms = static_cast<uint32_t>(u_time_ms);
+        position.lat = static_cast<int32_t>((std::stoi(gngga.latitude.substr(0, 2)) + (std::stod(gngga.latitude.substr(2)) / 60)) * 1e7);
+        position.lon = static_cast<int32_t>((std::stoi(gngga.longitude.substr(0, 3)) + (std::stod(gngga.longitude.substr(3)) / 60)) * 1e7);
+        position.alt = static_cast<int32_t>(std::stod(gngga.altitude) * 1000);
+        position.relative_alt = INT32_MAX; // TODO?
         position.vx = INT16_MAX;
         position.vy = INT16_MAX;
-        position.vx = INT16_MAX;
-        position.hdg = UINT16_MAX;
+        position.vz = INT16_MAX;
+        position.hdg = UINT16_MAX; // TODO
+
+        raw_gps.time_usec = static_cast<uint32_t>(u_time_ms * 1000);
+        raw_gps.fix_type = static_cast<uint8_t>(std::stoi(gngga.status)); // TODO this is wrong
+        raw_gps.lat = position.lat;
+        raw_gps.lon = position.lon;
+        raw_gps.alt = position.alt;
+        raw_gps.eph = static_cast<uint16_t>(std::stod(gngga.h_dilution_of_pos) * 100);
+        raw_gps.epv = UINT16_MAX;
+        raw_gps.vel = static_cast<uint16_t>(std::stod(gnrmc.ground_speed) * 51.44); // knots to 100 m/s
+        raw_gps.cog = UINT16_MAX;
+        raw_gps.satellites_visible = static_cast<uint8_t>(std::stoi(gngga.number_of_sattelites));
 
         mavlink_msg_global_position_int_encode(
             MAV_SYS_ID,
             &position
         );
 
-        uint8_t buffer[MAVLINK_MAX_PACKET_LEN];
-        int len;
+        len = mavlink_msg_to_send_buffer(buffer, &mavlink_msg);
+        write(send_pipe, buffer, len);
+
+        mavlink_msg_gps_raw_int_encode(
+            MAV_SYS_ID,
+            MAV_COMP_ID,
+            &mavlink_msg,
+            &raw_gps
+        );
 
         len = mavlink_msg_to_send_buffer(buffer, &mavlink_msg);
-        write(sendpipe_fd, buffer, len);
-    
+        write(send_pipe, buffer, len);
     }
 }