/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2024, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */
#pragma once

#include "gnss_messages_common.h"

namespace mrpt::obs::gnss
{
// Pragma to ensure we can safely serialize some of these structures
#pragma pack(push, 1)

/** NMEA datum: GGA. \sa mrpt::obs::CObservationGPS  */
struct Message_NMEA_GGA : public gnss_message
{
  GNSS_MESSAGE_BINARY_BLOCK(&fields, sizeof(fields))
  /** Static msg type (member expected by templates) */
  enum
  {
    msg_type = NMEA_GGA
  };
  Message_NMEA_GGA() : gnss_message((gnss_message_type_t)msg_type) {}
  struct content_t
  {
    /** The GPS sensor measured timestamp (in UTC time) */
    UTC_time UTCTime;
    /** The measured latitude, in degrees (North:+ , South:-) */
    double latitude_degrees{0};
    /** The measured longitude, in degrees (East:+ , West:-) */
    double longitude_degrees{0};
    /** NMEA standard values: 0 = invalid, 1 = GPS fix (SPS), 2 = DGPS fix,
     * 3 = PPS fix, 4 = Real Time Kinematic, 5 = Float RTK, 6 = estimated
     * (dead reckoning) (2.3 feature), 7 = Manual input mode, 8 = Simulation
     * mode */
    uint8_t fix_quality{0};
    /** The measured altitude, in meters (A). */
    double altitude_meters{0};
    /** Undulation: Difference between the measured altitude and the geoid,
     * in meters (B). */
    double geoidal_distance{};
    /** The measured orthometric altitude, in meters (A)+(B). */
    double orthometric_altitude{};
    /** The corrected (only for TopCon mmGPS) orthometric altitude, in
     * meters mmGPS(A+B). */
    double corrected_orthometric_altitude{};
    /** The number of satelites used to compute this estimation. */
    uint32_t satellitesUsed{0};
    /** This states whether to take into account the value in the HDOP
     * field. */
    bool thereis_HDOP{false};
    /** The HDOP (Horizontal Dilution of Precision) as returned by the
     * sensor. */
    float HDOP{0};

    content_t();
  };
  /** Message content, accessible by individual fields */
  content_t fields;

  void dumpToStream(std::ostream& out) const override;  // See docs in base

  /**  Return the geodetic coords as a mrpt::topography::TGeodeticCoords
   * structure (requires linking against mrpt-topography)
   *   Call as: getAsStruct<TGeodeticCoords>(); */
  template <class TGEODETICCOORDS>
  inline TGEODETICCOORDS getOrthoAsStruct() const
  {
    return TGEODETICCOORDS(
        fields.latitude_degrees, fields.longitude_degrees, fields.orthometric_altitude);
  }
  /**  Return the corrected geodetic coords as a
   * mrpt::topography::TGeodeticCoords structure (requires linking against
   * mrpt-topography)
   *   Call as: getAsStruct<TGeodeticCoords>(); */
  template <class TGEODETICCOORDS>
  inline TGEODETICCOORDS getCorrectedOrthoAsStruct() const
  {
    return TGEODETICCOORDS(
        fields.latitude_degrees, fields.longitude_degrees, fields.corrected_orthometric_altitude);
  }
  /**  Return the geodetic coords as a mrpt::topography::TGeodeticCoords
   * structure (requires linking against mrpt-topography)
   *   Call as: getAsStruct<TGeodeticCoords>(); */
  template <class TGEODETICCOORDS>
  inline TGEODETICCOORDS getAsStruct() const
  {
    return TGEODETICCOORDS(
        fields.latitude_degrees, fields.longitude_degrees, fields.altitude_meters);
  }

  bool getAllFieldDescriptions(std::ostream& o) const override;
  bool getAllFieldValues(std::ostream& o) const override;
};

/** NMEA datum: GLL. \sa mrpt::obs::CObservationGPS  */
struct Message_NMEA_GLL : public gnss_message
{
  GNSS_MESSAGE_BINARY_BLOCK(&fields, sizeof(fields))
  /** Static msg type (member expected by templates) */
  enum
  {
    msg_type = NMEA_GLL
  };
  Message_NMEA_GLL() : gnss_message((gnss_message_type_t)msg_type) {}
  struct content_t
  {
    /** The GPS sensor measured timestamp (in UTC time) */
    UTC_time UTCTime;
    /** The measured latitude, in degrees (North:+ , South:-) */
    double latitude_degrees{0};
    /** The measured longitude, in degrees (East:+ , West:-) */
    double longitude_degrees{0};
    /** This will be: 'A'=OK or 'V'=void */
    int8_t validity_char{'V'};
    content_t();
  };
  /** Message content, accessible by individual fields */
  content_t fields;
  void dumpToStream(std::ostream& out) const override;  // See docs in base
  bool getAllFieldDescriptions(std::ostream& o) const override;
  bool getAllFieldValues(std::ostream& o) const override;
};

/** NMEA datum: RMC. \sa mrpt::obs::CObservationGPS   */
struct Message_NMEA_RMC : public gnss_message
{
  GNSS_MESSAGE_BINARY_BLOCK(&fields, sizeof(fields))
  /** Static msg type (member expected by templates) */
  enum
  {
    msg_type = NMEA_RMC
  };
  Message_NMEA_RMC() : gnss_message((gnss_message_type_t)msg_type) {}
  struct content_t
  {
    /** The GPS sensor measured timestamp (in UTC time) */
    UTC_time UTCTime;
    /** This will be: 'A'=OK or 'V'=void */
    int8_t validity_char{'V'};
    /** The measured latitude, in degrees (North:+ , South:-) */
    double latitude_degrees{0};
    /** The measured longitude, in degrees (East:+ , West:-) */
    double longitude_degrees{0};
    /** Measured speed (in knots) */
    double speed_knots{0};
    /** Measured speed direction (in degrees) */
    double direction_degrees{0};
    /** Date: day (1-31), month (1-12), two-digits year (00-99) */
    uint8_t date_day{0}, date_month{0}, date_year{0};
    /** Magnetic variation direction (East:+, West:-) */
    double magnetic_dir{};
    /** 'A': Autonomous, 'D': Differential, 'N': Not valid, 'E': Estimated,
     * 'M': Manual */
    char positioning_mode{'N'};
    content_t();
  };
  /** Message content, accessible by individual fields */
  content_t fields;

  /** Build an MRPT timestamp with the year/month/day of this observation. */
  mrpt::system::TTimeStamp getDateAsTimestamp() const;

  void dumpToStream(std::ostream& out) const override;  // See docs in base
  bool getAllFieldDescriptions(std::ostream& o) const override;
  bool getAllFieldValues(std::ostream& o) const override;
};

/** NMEA datum: VTG. \sa mrpt::obs::CObservationGPS  */
struct Message_NMEA_VTG : public gnss_message
{
  GNSS_MESSAGE_BINARY_BLOCK(&fields, sizeof(fields))
  /** Static msg type (member expected by templates) */
  enum
  {
    msg_type = NMEA_VTG
  };
  Message_NMEA_VTG() : gnss_message((gnss_message_type_t)msg_type) {}
  struct content_t
  {
    /** Degrees */
    double true_track{}, magnetic_track{};
    double ground_speed_knots{}, ground_speed_kmh{};
    content_t();
  };
  /** Message content, accessible by individual fields */
  content_t fields;
  void dumpToStream(std::ostream& out) const override;  // See docs in base
  bool getAllFieldDescriptions(std::ostream& o) const override;
  bool getAllFieldValues(std::ostream& o) const override;
};

/** NMEA datum: GSA. \sa mrpt::obs::CObservationGPS   */
struct Message_NMEA_GSA : public gnss_message
{
  GNSS_MESSAGE_BINARY_BLOCK(&fields, sizeof(fields))
  /** Static msg type (member expected by templates) */
  enum
  {
    msg_type = NMEA_GSA
  };
  Message_NMEA_GSA() : gnss_message((gnss_message_type_t)msg_type) {}
  struct content_t
  {
    char auto_selection_fix = 'M';
    char fix_2D_3D = '1';  //!< 1: no fix, 2: 2D fix, 3: 3D fix
    char PRNs[12][2];      //!< Satellite IDs used
    double PDOP = 0, HDOP = 0, VDOP = 0;
    content_t();
  };
  /** Message content, accessible by individual fields */
  content_t fields;

  void dumpToStream(std::ostream& out) const override;  // See docs in base
  bool getAllFieldDescriptions(std::ostream& o) const override;
  bool getAllFieldValues(std::ostream& o) const override;
};

/** NMEA datum: ZDA. \sa mrpt::obs::CObservationGPS   */
struct Message_NMEA_ZDA : public gnss_message
{
  GNSS_MESSAGE_BINARY_BLOCK(&fields, sizeof(fields))
  /** Static msg type (member expected by templates) */
  enum
  {
    msg_type = NMEA_ZDA
  };
  Message_NMEA_ZDA() : gnss_message((gnss_message_type_t)msg_type) {}
  struct content_t
  {
    /** The GPS sensor measured timestamp (in UTC time) */
    UTC_time UTCTime;
    /** 1-31 */
    uint8_t date_day{};
    /** 1-12 */
    uint8_t date_month{};
    /** 2000-... */
    uint16_t date_year{};
    content_t();
  };
  /** Message content, accessible by individual fields */
  content_t fields;

  /** Build an MRPT UTC timestamp with the year/month/day + hour/minute/sec of
   * this observation. */
  mrpt::system::TTimeStamp getDateTimeAsTimestamp() const;
  /** Build an MRPT timestamp with the year/month/day of this observation. */
  mrpt::system::TTimeStamp getDateAsTimestamp() const;

  void dumpToStream(std::ostream& out) const override;  // See docs in base
  bool getAllFieldDescriptions(std::ostream& o) const override;
  bool getAllFieldValues(std::ostream& o) const override;
};
#pragma pack(pop)  // End of pack = 1
}  // namespace mrpt::obs::gnss
