#ifndef UBXMSG_H
#define UBXMSG_H

//  Copyright (C) 2014-2017, SlashDevin
//
//  This file is part of NeoGPS
//
//  NeoGPS is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  NeoGPS is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with NeoGPS.  If not, see <http://www.gnu.org/licenses/>.

#include "NMEAGPS_cfg.h"

// Disable the entire file if derived types are not allowed.
#ifdef NMEAGPS_DERIVED_TYPES

#include "NMEAGPS.h"
class ubloxGPS;

namespace ublox {

    enum msg_class_t
      { UBX_NAV  = 0x01,  // Navigation results
        UBX_RXM  = 0x02,  // Receiver Manager messages
        UBX_INF  = 0x04,  // Informational messages
        UBX_ACK  = 0x05,  // ACK/NAK replies to CFG messages
        UBX_CFG  = 0x06,  // Configuration input messages
        UBX_MON  = 0x0A,  // Monitoring messages
        UBX_AID  = 0x0B,  // Assist Now aiding messages
        UBX_TIM  = 0x0D,  // Timing messages
        UBX_HNR  = 0x28,  // High rate navigation results
        UBX_NMEA = 0xF0,  // NMEA Standard messages
        UBX_PUBX = 0xF1,  // NMEA proprietary messages (PUBX)
        UBX_UNK  = 0xFF
      }  __attribute__((packed));

    enum msg_id_t
      {
        UBX_ACK_NAK      = 0x00, // Reply to CFG messages
        UBX_ACK_ACK      = 0x01, // Reply to CFG messages
        UBX_CFG_MSG      = 0x01, // Configure which messages to send
        UBX_CFG_RST      = 0x04, // Reset command
        UBX_CFG_RATE     = 0x08, // Configure message rate
        UBX_CFG_NMEA     = 0x17, // Configure NMEA protocol
        UBX_CFG_NAV5     = 0x24, // Configure navigation engine settings
        UBX_MON_VER      = 0x04, // Monitor Receiver/Software version
        UBX_NAV_POSLLH   = 0x02, // Current Position
        UBX_NAV_STATUS   = 0x03, // Receiver Navigation Status
        UBX_NAV_DOP      = 0x04, // Dilutions of Precision
        UBX_NAV_ODO      = 0x09, // Odometer Solution (NEO-M8 only)
        UBX_NAV_RESETODO = 0x10, // Reset Odometer (NEO-M8 only)
        UBX_NAV_VELNED   = 0x12, // Current Velocity
        UBX_NAV_TIMEGPS  = 0x20, // Current GPS Time
        UBX_NAV_TIMEUTC  = 0x21, // Current UTC Time
        UBX_NAV_SVINFO   = 0x30, // Space Vehicle Information
        UBX_HNR_PVT      = 0x00, // High rate Position, Velocity and Time
        UBX_ID_UNK   = 0xFF
      }  __attribute__((packed));

      struct msg_hdr_t {
          msg_class_t msg_class;
          msg_id_t    msg_id;
          bool same_kind( const msg_hdr_t & msg ) const
            { return (msg_class == msg.msg_class) && (msg_id == msg.msg_id); }
      }  __attribute__((packed));

      struct msg_t : msg_hdr_t {
          uint16_t length;  // should be sizeof(this)-sizeof(msg_hdr_t)
          #define UBX_MSG_LEN(msg) (sizeof(msg) - sizeof(ublox::msg_t))

          msg_t()
            {
              length    = 0;
            };
          msg_t( enum msg_class_t m, enum msg_id_t i, uint16_t l = 0 )
            {
                msg_class = m;
                msg_id    = i;
                length    = l;
            }
          void init()
            {
              uint8_t *mem = (uint8_t *) this;
              memset( &mem[ sizeof(msg_t) ], 0, length );
            }
      } __attribute__((packed));

    /**
      * Configure message intervals.
      */

    enum ubx_nmea_msg_t { // msg_id's for UBX_NMEA msg_class
        UBX_GPGGA = 0x00,
        UBX_GPGLL = 0x01,
        UBX_GPGSA = 0x02,
        UBX_GPGSV = 0x03,
        UBX_GPRMC = 0x04,
        UBX_GPVTG = 0x05,
        UBX_GPGST = 0x07,
        UBX_GPZDA = 0x08
    } __attribute__((packed));

    struct cfg_msg_t : msg_t {
        msg_class_t  cfg_msg_class;
        msg_id_t     cfg_msg;
        uint8_t      rate;

        cfg_msg_t( msg_class_t m, msg_id_t i, uint8_t r )
          : msg_t( UBX_CFG, UBX_CFG_MSG, UBX_MSG_LEN(*this) )
        {
          cfg_msg_class = m;
          cfg_msg       = i;
          rate          = r;
        };
    } __attribute__((packed));

    extern bool configNMEA( ubloxGPS &gps, NMEAGPS::nmea_msg_t msgType, uint8_t rate );
    
    // Reset command
    struct cfg_reset_t : msg_t {

      struct bbr_section_t
        {
          bool ephermeris            :1;
          bool almanac               :1;
          bool health                :1;
          bool klobuchard            :1;
          bool position              :1;
          bool clock_drift           :1;
          bool osc_param             :1;
          bool utc_param             :1;
          bool rtc                   :1;
          bool reserved1             :2;
          bool sfdr_param            :1;
          bool sfdr_veh_mon_param    :1;
          bool tct_param             :1;
          bool reserved2             :1;
          bool autonomous_orbit_param:1;
        } __attribute__((packed));

      enum reset_mode_t
        {
          HW_RESET                     = 0x00,
          CONTROLLED_SW_RESET          = 0x01,
          CONTROLLED_SW_RESET_GPS_ONLY = 0x02,
          HW_RESET_AFTER_SHUTDOWN      = 0x04,
          CONTROLLED_GPS_STOP          = 0x08,
          CONTROLLED_GPS_START         = 0x09
        } __attribute__((packed));

      bbr_section_t clear_bbr_section;
      reset_mode_t  reset_mode : 8;
      uint8_t       reserved   : 8;

      cfg_reset_t()
        : msg_t( UBX_CFG, UBX_CFG_RST, UBX_MSG_LEN(*this) )
          { init(); }

    }  __attribute__((packed));

    // Configure navigation rate
    enum time_ref_t {
      UBX_TIME_REF_UTC=0,
      UBX_TIME_REF_GPS=1
    } __attribute__((packed));

    struct cfg_rate_t : msg_t {
        uint16_t        GPS_meas_rate;
        uint16_t        nav_rate;
        enum time_ref_t time_ref:16;

        cfg_rate_t( uint16_t gr, uint16_t nr, enum time_ref_t tr )
          : msg_t( UBX_CFG, UBX_CFG_RATE, UBX_MSG_LEN(*this) )
        {
          GPS_meas_rate = gr;
          nav_rate      = nr;
          time_ref      = tr;
        }
    }  __attribute__((packed));

    //  Navigation Engine Expert Settings
    enum dyn_model_t {
        UBX_DYN_MODEL_PORTABLE   = 0,
        UBX_DYN_MODEL_STATIONARY = 2,
        UBX_DYN_MODEL_PEDESTRIAN = 3,
        UBX_DYN_MODEL_AUTOMOTIVE = 4,
        UBX_DYN_MODEL_SEA        = 5,
        UBX_DYN_MODEL_AIR_1G     = 6,
        UBX_DYN_MODEL_AIR_2G     = 7,
        UBX_DYN_MODEL_AIR_4G     = 8
    } __attribute__((packed));

    enum position_fix_t {
        UBX_POS_FIX_2D_ONLY = 1,
        UBX_POS_FIX_3D_ONLY = 2,
        UBX_POS_FIX_AUTO    = 3
    } __attribute__((packed));

    struct cfg_nav5_t : msg_t {
        struct parameter_mask_t {
            bool dyn_model            :1;
            bool min_elev             :1;
            bool fix                  :1;
            bool dr_limit             :1;
            bool pos_mask             :1;
            bool time_mask            :1;
            bool static_hold_thr      :1;
            bool dgps_timeout         :1;
            int  _unused_             :8;
        } __attribute__((packed));

        union {
          struct parameter_mask_t apply;
          uint16_t                apply_word;
        } __attribute__((packed));
                
        enum dyn_model_t       dyn_model:8;
        enum position_fix_t    fix_mode:8;
        int32_t                fixed_alt;          // m MSL x0.01
        uint32_t               fixed_alt_variance; // m^2 x0.0001
        int8_t                 min_elev;           // deg
        uint8_t                dr_limit;           // s
        uint16_t               pos_dop_mask;       // x0.1
        uint16_t               time_dop_mask;      // x0.1
        uint16_t               pos_acc_mask;       // m
        uint16_t               time_acc_mask;      // m
        uint8_t                static_hold_thr;    // cm/s
        uint8_t                dgps_timeout;       // s
        uint32_t always_zero_1;
        uint32_t always_zero_2;
        uint32_t always_zero_3;

        cfg_nav5_t() : msg_t( UBX_CFG, UBX_CFG_NAV5, UBX_MSG_LEN(*this) )
          {
            apply_word = 0xFF00;
            always_zero_1 =
            always_zero_2 =
            always_zero_3 = 0;
          }

    }  __attribute__((packed));

    // Geodetic Position Solution
    struct nav_posllh_t : msg_t {
        uint32_t time_of_week; // mS
        int32_t  lon; // deg * 1e7
        int32_t  lat; // deg * 1e7
        int32_t  height_above_ellipsoid; // mm
        int32_t  height_MSL; // mm
        uint32_t horiz_acc; // mm
        uint32_t vert_acc; // mm

        nav_posllh_t() : msg_t( UBX_NAV, UBX_NAV_POSLLH, UBX_MSG_LEN(*this) ) {};
    } __attribute__((packed));

    // Receiver Navigation Status
    struct nav_status_t : msg_t {
        uint32_t time_of_week; // mS
        enum status_t {
          NAV_STAT_NONE,
          NAV_STAT_DR_ONLY,
          NAV_STAT_2D,
          NAV_STAT_3D,
          NAV_STAT_GPS_DR,
          NAV_STAT_TIME_ONLY
        } __attribute__((packed))
            status;

        struct flags_t {
          bool gps_fix:1;
          bool diff_soln:1;
          bool week:1;
          bool time_of_week:1;
        } __attribute__((packed))
          flags;
        
        static gps_fix::status_t to_status( enum gps_fix::status_t status, flags_t flags )
        {
          if (!flags.gps_fix)
            return gps_fix::STATUS_NONE;
          if (flags.diff_soln)
            return gps_fix::STATUS_DGPS;
          return status;
        }
        
        struct {
          bool dgps_input:1;
          bool _skip_:6;
          bool map_matching:1;
        }  __attribute__((packed))
          fix_status;

        enum {
          PSM_ACQUISITION,
          PSM_TRACKING,
          PSM_POWER_OPTIMIZED_TRACKING,
          PSM_INACTIVE
        }
          power_safe:2; // FW > v7.01

        uint32_t time_to_first_fix; // ms time tag
        uint32_t uptime; // ms since startup/reset

        nav_status_t() : msg_t( UBX_NAV, UBX_NAV_STATUS, UBX_MSG_LEN(*this) ) {};
    }  __attribute__((packed));

    // Dilutions of Precision
    struct nav_dop_t : msg_t {
        uint32_t time_of_week; // mS

        uint16_t gdop; // Geometric
        uint16_t pdop; // Position
        uint16_t tdop; // Time
        uint16_t vdop; // Vertical
        uint16_t hdop; // Horizontal
        uint16_t ndop; // Northing
        uint16_t edop; // Easting

        nav_dop_t() : msg_t( UBX_NAV, UBX_NAV_DOP, UBX_MSG_LEN(*this) ) {};
    }  __attribute__((packed));

    // Odometer Solution (NEO-M8 only)
    struct nav_odo_t : msg_t {
        uint8_t   version;
        uint8_t   reserved[3];
        uint32_t  time_of_week;   // mS
        uint32_t  distance;       // m
        uint32_t  total_distance; // m
        uint32_t  distanceSTD;    // m (1-sigma)

        nav_odo_t() : msg_t( UBX_NAV, UBX_NAV_ODO, UBX_MSG_LEN(*this) ) {};
    }  __attribute__((packed));

    // Reset Odometer (NEO-M8 only)
    struct nav_resetodo_t : msg_t {
        // no payload, it's just a command

        nav_resetodo_t() : msg_t( UBX_NAV, UBX_NAV_RESETODO, UBX_MSG_LEN(*this) ) {};
    }  __attribute__((packed));

    // Velocity Solution in North/East/Down
    struct nav_velned_t : msg_t {
        uint32_t time_of_week; // mS
        int32_t  vel_north;    // cm/s
        int32_t  vel_east;     // cm/s
        int32_t  vel_down;     // cm/s
        uint32_t speed_3D;     // cm/s
        uint32_t speed_2D;     // cm/s
        int32_t  heading;      // degrees * 1e5
        uint32_t speed_acc;    // cm/s
        uint32_t heading_acc;  // degrees * 1e5
        
        nav_velned_t() : msg_t( UBX_NAV, UBX_NAV_VELNED, UBX_MSG_LEN(*this) ) {};
    }  __attribute__((packed));

    // GPS Time Solution
    struct nav_timegps_t : msg_t {
        uint32_t time_of_week;   // mS
        int32_t  fractional_ToW; // nS
        int16_t  week;
        int8_t   leap_seconds;   // GPS-UTC
        struct valid_t {
          bool time_of_week:1;
          bool week:1;
          bool leap_seconds:1;
        } __attribute__((packed))
          valid;

        nav_timegps_t() : msg_t( UBX_NAV, UBX_NAV_TIMEGPS, UBX_MSG_LEN(*this) ) {};
    }  __attribute__((packed));

    // UTC Time Solution
    struct nav_timeutc_t : msg_t {
        uint32_t time_of_week;   // mS
        uint32_t time_accuracy;  // nS
        int32_t  fractional_ToW; // nS
        uint16_t year;           // 1999..2099
        uint8_t  month;          // 1..12
        uint8_t  day;            // 1..31
        uint8_t  hour;           // 0..23
        uint8_t  minute;         // 0..59
        uint8_t  second;         // 0..59
        struct valid_t {
          bool time_of_week:1;
          bool week_number:1;
          bool UTC:1;
        } __attribute__((packed))
          valid;

        nav_timeutc_t() : msg_t( UBX_NAV, UBX_NAV_TIMEUTC, UBX_MSG_LEN(*this) ) {};
    }  __attribute__((packed));

    // Space Vehicle Information
    struct nav_svinfo_t : msg_t {
        uint32_t time_of_week;   // mS
        uint8_t  num_channels;
        enum { ANTARIS_OR_4, UBLOX_5, UBLOX_6 } chipgen:8;
        uint16_t reserved2;
        struct sv_t {
          uint8_t channel; // 255 = no channel
          uint8_t id;      // Satellite ID
          bool    used_for_nav:1;
          bool    diff_corr   :1; // differential correction available
          bool    orbit_avail :1; // orbit info available
          bool    orbit_eph   :1; // orbit info is ephemeris
          bool    unhealthy   :1; // SV should not be used
          bool    orbit_alm   :1; // orbit info is Almanac Plus
          bool    orbit_AOP   :1; // orbit info is AssistNow Autonomous
          bool    smoothed    :1; // Carrier smoothed pseudorange used
          enum {
              IDLE, 
              SEARCHING, 
              ACQUIRED, 
              UNUSABLE, 
              CODE_LOCK, 
              CODE_AND_CARRIER_LOCK_1,
              CODE_AND_CARRIER_LOCK_2,
              CODE_AND_CARRIER_LOCK_3
            }
              quality:8;
          uint8_t  snr;           // dbHz
          uint8_t  elevation;     // degrees
          uint16_t azimuth;       // degrees
          uint32_t pr_res;        // pseudo range residual in cm
        };

        //  Calculate the number of bytes required to hold the
        //  specified number of channels.
        static uint16_t size_for( uint8_t channels )
          { return sizeof(nav_svinfo_t) + (uint16_t)channels * sizeof(sv_t); }

        // Initialze the msg_hdr for the specified number of channels
        void init( uint8_t max_channels )
        {
          msg_class = UBX_NAV;
          msg_id    = UBX_NAV_SVINFO;
          length    = size_for( max_channels ) - sizeof(ublox::msg_t);
        }

      }  __attribute__((packed));

    // High Rate PVT
    struct hnr_pvt_t : msg_t {
        uint32_t time_of_week;   // mS
        uint16_t year;           // 1999..2099
        uint8_t  month;          // 1..12
        uint8_t  day;            // 1..31
        uint8_t  hour;           // 0..23
        uint8_t  minute;         // 0..59
        uint8_t  second;         // 0..59
        struct valid_t {
            bool date:1;
            bool time:1;
            bool fully_resolved:1;
        } __attribute__((packed))
        valid;
        int32_t  fractional_ToW; // nS

        nav_status_t::status_t status;

        struct flags_t {
            bool gps_fix:1;
            bool diff_soln:1;
            bool week:1;
            bool time_of_week:1;
            bool heading_valid:1;
        } __attribute__((packed))
        flags;

        static gps_fix::status_t to_status( enum gps_fix::status_t status, flags_t flags )
        {
            if (!flags.gps_fix)
              return gps_fix::STATUS_NONE;
            if (flags.diff_soln)
              return gps_fix::STATUS_DGPS;
            return status;
        }

        uint16_t reserved1;

        int32_t  lon; // deg * 1e7
        int32_t  lat; // deg * 1e7
        int32_t  height_above_ellipsoid; // mm
        int32_t  height_MSL; // mm

        int32_t  speed_2D;        // mm/s
        int32_t  speed_3D;        // mm/s
        int32_t  heading_motion;  // degrees * 1e5
        int32_t  heading_vehicle; // degrees * 1e5

        uint32_t horiz_acc;   // mm
        uint32_t vert_acc;    // mm
        uint32_t speed_acc;   // mm/s
        uint32_t heading_acc; // degrees * 1e5

        uint32_t reserved4;

        hnr_pvt_t() : msg_t( UBX_HNR, UBX_HNR_PVT, UBX_MSG_LEN(*this) ) {};
    } __attribute__((packed));

    struct cfg_nmea_t : msg_t {
        bool  always_output_pos  :1; // invalid or failed
        bool  output_invalid_pos :1;
        bool  output_invalid_time:1;
        bool  output_invalid_date:1;
        bool  use_GPS_only       :1;
        bool  output_heading     :1; // even if frozen
        bool  __not_used__       :2;
        enum {
            NMEA_V_2_1 = 0x21,
            NMEA_V_2_3 = 0x23,
            NMEA_V_4_0 = 0x40,  // Neo M8 family only
            NMEA_V_4_1 = 0x41   // Neo M8 family only
          }
            nmea_version : 8;
        enum {
            SV_PER_TALKERID_UNLIMITED =  0,
            SV_PER_TALKERID_8         =  8,
            SV_PER_TALKERID_12        = 12,
            SV_PER_TALKERID_16        = 16
          }
            num_sats_per_talker_id : 8;
        bool    compatibility_mode:1;
        bool    considering_mode  :1;
        bool    max_line_length_82:1;   // Neo M8 family only
        uint8_t __not_used_1__    :5;

        cfg_nmea_t() : msg_t( UBX_CFG, UBX_CFG_NMEA, UBX_MSG_LEN(*this) ) {};

      }  __attribute__((packed));

    struct cfg_nmea_v1_t : cfg_nmea_t {
        bool     filter_gps    :1;
        bool     filter_sbas   :1;
        uint8_t  __not_used_2__:2;
        bool     filter_qzss   :1;
        bool     filter_glonass:1;
        bool     filter_beidou :1;
        uint32_t __not_used_3__:25;

        bool proprietary_sat_numbering; // for non-NMEA satellites
        enum {
            MAIN_TALKER_ID_COMPUTED,
            MAIN_TALKER_ID_GP,
            MAIN_TALKER_ID_GL,
            MAIN_TALKER_ID_GN,
            MAIN_TALKER_ID_GA,
            MAIN_TALKER_ID_GB
          }
            main_talker_id : 8;
        bool gsv_uses_main_talker_id; // false means COMPUTED
        enum cfg_nmea_version_t {
            CFG_NMEA_V_0,  // length = 12
            CFG_NMEA_V_1   // length = 20 (default)
          }
            version : 8;

        //  Remaining fields are CFG_NMEA_V_1 only!
        char beidou_talker_id[2]; // NULs mean default
        uint8_t __reserved__[6];

        cfg_nmea_v1_t( cfg_nmea_version_t v = CFG_NMEA_V_1 )
          {
            version = v;
            if (version == CFG_NMEA_V_0)
              length = 12;
            else {
              length = 20;
              for (uint8_t i=0; i<8;) // fills 'reserved' too
                beidou_talker_id[i++] = 0;
            }
          };

      }  __attribute__((packed));

};

#endif // NMEAGPS_DERIVED_TYPES enabled

#endif