/*
NMEA0183AISMessages.cpp

Copyright (c) 2019 Ronnie Zeiller

Based on the works of Timo Lappalainen NMEA2000 and NMEA0183 Library
Thanks to  Eric S. Raymond (https://gpsd.gitlab.io/gpsd/AIVDM.html)
and Kurt Schwehr for their informations on AIS encoding.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

#include "NMEA0183AISMessages.h"
#include <N2kTypes.h>
#include <N2kMsg.h>
#include <string.h>
//#include <bitset>
//#include <unordered_map>
#include <sstream>
#include <math.h>
#include "NMEA0183AISMsg.h"

const double pi=3.1415926535897932384626433832795;
const double kmhToms=1000.0/3600.0;
const double knToms=1852.0/3600.0;
const double degToRad=pi/180.0;
const double radToDeg=180.0/pi;
const double msTokmh=3600.0/1000.0;
const double msTokn=3600.0/1852.0;
const double nmTom=1.852*1000;
const double mToFathoms=0.546806649;
const double mToFeet=3.2808398950131;
const double radsToDegMin = 60 * 360.0 / (2 * pi);    // [rad/s -> degree/minute]


// ************************  Helper for AIS  ***********************************
static bool AddMessageType(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageType);
static bool AddRepeat(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t Repeat);
static bool AddUserID(tNMEA0183AISMsg &NMEA0183AISMsg, uint32_t UserID);
static bool AddIMONumber(tNMEA0183AISMsg &NMEA0183AISMsg, uint32_t &IMONumber);
static bool AddText(tNMEA0183AISMsg &NMEA0183AISMsg, char *FieldVal, uint8_t length);
//static bool AddVesselType(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t VesselType);
static bool AddDimensions(tNMEA0183AISMsg &NMEA0183AISMsg, double Length, double Beam, double PosRefStbd, double PosRefBow);
static bool AddNavStatus(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t &NavStatus);
static bool AddROT(tNMEA0183AISMsg &NMEA0183AISMsg, double &rot);
static bool AddSOG (tNMEA0183AISMsg &NMEA0183AISMsg, double &sog);
static bool AddLongitude(tNMEA0183AISMsg &NMEA0183AISMsg, double &Longitude);
static bool AddLatitude(tNMEA0183AISMsg &NMEA0183AISMsg, double &Latitude);
static bool AddHeading (tNMEA0183AISMsg &NMEA0183AISMsg, double &heading);
static bool AddCOG(tNMEA0183AISMsg &NMEA0183AISMsg, double cog);
static bool AddSeconds (tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t &Seconds);
static bool AddEPFDFixType(tNMEA0183AISMsg &NMEA0183AISMsg, tN2kGNSStype &GNSStype);
static bool AddStaticDraught(tNMEA0183AISMsg &NMEA0183AISMsg, double &Draught);
static bool AddETADateTime(tNMEA0183AISMsg &NMEA0183AISMsg, uint16_t &ETAdate, double &ETAtime);

//*****************************************************************************
// Types 1, 2 and 3: Position Report Class A or B  -> https://gpsd.gitlab.io/gpsd/AIVDM.html
// total of 168 bits, occupying one AIVDM sentence
// Example: !AIVDM,1,1,,A,133m@ogP00PD;88MD5MTDww@2D7k,0*46
// Payload: Payload: 133m@ogP00PD;88MD5MTDww@2D7k
// Message type 1 has a payload length of 168 bits.
// because AIS encodes messages using a 6-bits ASCII mechanism and 168 divided by 6 is 28.
//
// Got values from: ParseN2kPGN129038()
bool SetAISClassABMessage1( tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageType, uint8_t Repeat,
			    uint32_t UserID, double Latitude, double Longitude, bool Accuracy, bool RAIM, uint8_t Seconds,
			    double COG, double SOG, double Heading, double ROT, uint8_t NavStatus ) {

  NMEA0183AISMsg.ClearAIS();
  if ( !AddMessageType(NMEA0183AISMsg, MessageType) ) return false;    // 0 - 5    | 6    Message Type -> Constant: 1
  if ( !AddRepeat(NMEA0183AISMsg, Repeat) ) return false;              // 6 - 7    | 2    Repeat Indicator: 0 = default; 3 = do not repeat any more
  if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false;              // 8 - 37   | 30  MMSI
  if ( !AddNavStatus(NMEA0183AISMsg, NavStatus) ) return false;        // 38-41    | 4    Navigational Status  e.g.: "Under way sailing"
  if ( !AddROT(NMEA0183AISMsg, ROT) ) return false;                    // 42-49    | 8    Rate of Turn (ROT)
  if ( !AddSOG(NMEA0183AISMsg, SOG) ) return false;                    // 50-59    | 10   [m/s -> kts]  SOG with one digit  x10, 1023 = N/A
  if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Accuracy) ) return false;// 60       | 1    GPS Accuracy 1 oder 0, Default 0
  if ( !AddLongitude(NMEA0183AISMsg, Longitude) ) return false;        // 61-88    | 28  Longitude in Minutes / 10000
  if ( !AddLatitude(NMEA0183AISMsg, Latitude) ) return false;          // 89-115   | 27  Latitude in Minutes / 10000
  if ( !AddCOG(NMEA0183AISMsg, COG) ) return false;                    // 116-127  | 12  Course over ground will be 3600 (0xE10) if that data is not available.
  if ( !AddHeading (NMEA0183AISMsg, Heading) ) return false;           // 128-136  |  9    True Heading (HDG)
  if ( !AddSeconds(NMEA0183AISMsg, Seconds) ) return false;            // 137-142  | 6    Seconds in UTC timestamp)
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 2) ) return false;        // 143-144  | 2    Maneuver Indicator: 0 (default) 1, 2  (not delivered within this PGN)
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 3) ) return false;        // 145-147  | 3   Spare
  if ( !NMEA0183AISMsg.AddBoolToPayloadBin(RAIM) ) return false;    // 148-148  | 1   RAIM flag 0 = RAIM not in use (default), 1 = RAIM in use
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 19) ) return false;       // 149-167  | 19  Radio Status  (-> 0 NOT SENT WITH THIS PGN!!!!!)
  if ( !NMEA0183AISMsg.InitAis()) return false;        
  int padBits=0;
  if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayloadFix(padBits) ) ) return false;
  if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) return false;           
  return true;
}

// *****************************************************************************
// https://www.navcen.uscg.gov/?pageName=AISMessagesAStatic#
// AIS class A Static and Voyage Related Data
// Values derived from ParseN2kPGN129794();
bool  SetAISClassAMessage5(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat,
                          uint32_t UserID, uint32_t IMONumber, char *Callsign, char *Name,
                          uint8_t VesselType, double Length, double Beam, double PosRefStbd,
                          double PosRefBow, uint16_t ETAdate,  double ETAtime, double Draught,
                          char *Destination, tN2kGNSStype GNSStype, uint8_t DTE,
                          tN2kAISVersion AISversion) {

  // AIS Type 5 Message
  NMEA0183AISMsg.ClearAIS();
  if ( !AddMessageType(NMEA0183AISMsg, 5) ) return false;                // 0 - 5     | 6    Message Type -> Constant: 5
  if ( !AddRepeat(NMEA0183AISMsg, Repeat) ) return false;                // 6 - 7     | 2    Repeat Indicator: 0 = default; 3 = do not repeat any more
  if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false;                // 8 - 37    | 30  MMSI
  if ( !NMEA0183AISMsg.AddIntToPayloadBin((uint32_t)AISversion, 2) ) 
    return false;                                                        // 38 - 39   |  2   AIS Version  -> 0 oder 1  NOT DERIVED FROM N2k, Always 1!!!!
  if ( !AddIMONumber(NMEA0183AISMsg, IMONumber) ) return false;          // 40 - 69   | 30   IMO Number unisgned
  if ( !AddText(NMEA0183AISMsg, Callsign, 42) ) return false;            // 70 - 111  | 42   Call Sign  WDE4178      -> 7  6-bit characters -> Ascii lt. Table)
  if ( !AddText(NMEA0183AISMsg, Name, 120) ) return false;               // 112-231   | 120 Vessel Name  POINT FERMIN  -> 20 6-bit characters -> Ascii lt. Table
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(VesselType, 8) ) return false; // 232-239   |   8 Ship Type  0....255 e.g. 31  Towing
  if ( !AddDimensions(NMEA0183AISMsg, Length, Beam, PosRefStbd, PosRefBow) ) return false;  // 240 - 269 | 30 Dimensions
  if ( !AddEPFDFixType(NMEA0183AISMsg, GNSStype) ) return false;         // 270-273   | 4  Position Fix Type, 0 (default)
  if ( !AddETADateTime(NMEA0183AISMsg, ETAdate, ETAtime) ) return false; // 274 -293  | 20 Estimated time of arrival; MMDDHHMM UTC
  if ( !AddStaticDraught(NMEA0183AISMsg, Draught) ) return false;        // 294-301   | 8  Maximum Present Static Draught
  if ( !AddText(NMEA0183AISMsg, Destination, 120) ) return false;        // 302-421   | 120 | 20  Destination 20 6-bit characters
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(DTE, 1) ) return false;        // 422       | 1   | Data terminal equipment (DTE) ready (0 = available, 1 = not available = default)
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 1) ) return false;          // 423       | 1   | spare

  return true;
}

//  ****************************************************************************
// AIS position report (class B 129039) -> Type 18: Standard Class B CS Position Report
// PGN129039
// ParseN2kAISClassBPosition(const tN2kMsg &N2kMsg, uint8_t &MessageID, tN2kAISRepeat &Repeat, uint32_t &UserID,
//                        double &Latitude, double &Longitude, bool &Accuracy, bool &RAIM,
//                        uint8_t &Seconds, double &COG, double &SOG, tN2kAISTransceiverInformation &AISTransceiverInformation,
//                        double &Heading, tN2kAISUnit &Unit, bool &Display, bool &DSC, bool &Band, bool &Msg22, tN2kAISMode &Mode,
//                        bool &State)
//  VDM, VDO (AIS VHF Data-link message 18)
bool SetAISClassBMessage18(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat, uint32_t UserID,
			   double Latitude, double Longitude, bool Accuracy, bool RAIM,
			   uint8_t Seconds, double COG, double SOG, double Heading, tN2kAISUnit Unit,
			   bool Display, bool DSC, bool Band, bool Msg22, bool Mode, bool State) {
  //
  NMEA0183AISMsg.ClearAIS();
  if ( !AddMessageType(NMEA0183AISMsg, MessageID) ) return false;      // 0 - 5    | 6    Message Type -> Constant: 18
  if ( !AddRepeat(NMEA0183AISMsg, Repeat) ) return false;              // 6 - 7    | 2    Repeat Indicator: 0 = default; 3 = do not repeat any more
  if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false;              // 8 - 37   | 30  MMSI
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 8) ) return false;        // 38-45    | 8   Regional Reserved
  if ( !AddSOG(NMEA0183AISMsg, SOG) ) return false;                    // 46-55    | 10   [m/s -> kts]  SOG with one digit  x10, 1023 = N/A
  if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Accuracy)) return false; // 56       | 1    GPS Accuracy 1 oder 0, Default 0
  if ( !AddLongitude(NMEA0183AISMsg, Longitude) ) return false;        // 57-84    | 28  Longitude in Minutes / 10000
  if ( !AddLatitude(NMEA0183AISMsg, Latitude) ) return false;          // 85-111   | 27  Latitude in Minutes / 10000
  if ( !AddCOG(NMEA0183AISMsg, COG) ) return false;                    // 112-123  | 12  Course over ground will be 3600 (0xE10) if that data is not available.
  if ( !AddHeading (NMEA0183AISMsg, Heading) ) return false;           // 124-132  |  9    True Heading (HDG)
  if ( !AddSeconds(NMEA0183AISMsg, Seconds) ) return false;            // 133-138  | 6    Seconds in UTC timestamp)
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 2) ) return false;        // 139-140  | 2   Regional Reserved
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(Unit, 1) ) return false;     // 141      | 1   0=Class B SOTDMA unit 1=Class B CS (Carrier Sense) unit
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(Display, 1) ) return false;  // 142      | 1    0=No visual display, 1=Has display, (Probably not reliable).
  if ( !NMEA0183AISMsg.AddBoolToPayloadBin(DSC) ) return false;     // 143      | 1    If 1, unit is attached to a VHF voice radio with DSC capability.
  if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Band) ) return false;    // 144      | 1   If this flag is 1, the unit can use any part of the marine channel.
  if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Msg22)) return false;   // 145      | 1   If 1, unit can accept a channel assignment via Message Type 22.
  if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Mode) ) return false;    // 146      | 1   Assigned-mode flag: 0 = autonomous mode (default), 1 = assigned mode
  if ( !NMEA0183AISMsg.AddBoolToPayloadBin(RAIM) ) return false;    // 147      | 1   as for Message Type 1,2,3
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 20) ) return false;       // 148-167  | 20  Radio Status not in PGN 129039
  if ( !NMEA0183AISMsg.InitAis()) return false;        
  int padBits=0;
  if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayloadFix(padBits) ) ) return false;
  if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) return false;    

  return true;
}

//  ****************************************************************************
//  Type 24: Static Data Report
//  Equivalent of a Type 5 message for ships using Class B equipment. Also used to associate an MMSI
//  with a name on either class A or class B equipment.
//
//  A "Type 24" may be in part A or part B format; According to the standard, parts A and B are expected
//  to be broadcast in adjacent pairs; in the real world they may (due to quirks in various aggregation methods)
//  be separated by other sentences or even interleaved with different Type 24 pairs; decoders must cope with this.
//  The interpretation of some fields in Type B format changes depending on the range of the Type B MMSI field.
//
//  160 bits for part A, 168 bits for part B.
//  According to the standard, both the A and B parts are supposed to be 168 bits.
//  However, in the wild, A parts are often transmitted with only 160 bits, omitting the spare 7 bits at the end.
//  Implementers should be permissive about this.
//
//  If the Part Number field is 0, the rest of the message is interpreted as a Part A;
//  If it is 1, the rest of the message is interpreted as a Part B; values 2 and 3 are not allowed.
//
//  PGN 129809 AIS Class B "CS" Static Data Report, Part A -> AIS VHF Data-link message 24
//  PGN 129810 AIS Class B "CS" Static Data Report, Part B -> AIS VHF Data-link message 24
//  ParseN2kPGN129809 (const tN2kMsg &N2kMsg, uint8_t &MessageID, tN2kAISRepeat &Repeat, uint32_t &UserID, char *Name)  -> store to vector
//  ParseN2kPGN129810(const tN2kMsg &N2kMsg, uint8_t &MessageID, tN2kAISRepeat &Repeat, uint32_t &UserID,
//                      uint8_t &VesselType, char *Vendor, char *Callsign, double &Length, double &Beam,
//                      double &PosRefStbd, double &PosRefBow, uint32_t &MothershipID);
//
//  Part A: MessageID, Repeat, UserID, ShipName -> store in vector to call on Part B arrivals!!!
//  Part B: MessageID, Repeat, UserID, VesselType (5), Callsign (5), Length & Beam, PosRefBow,.. (5)
bool SetAISClassBMessage24PartA(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat, uint32_t UserID, char *Name) {
  // AIS Type 24 Message
  NMEA0183AISMsg.ClearAIS();
  // Common for PART A AND Part B Bit 0 - 39 / len 40
  if ( !AddMessageType(NMEA0183AISMsg, 24) ) return false;                // 0 - 5    | 6    Message Type -> Constant: 24
  if ( !AddRepeat(NMEA0183AISMsg, Repeat) ) return false;                 // 6 - 7    | 2    Repeat Indicator: 0 = default; 3 = do not repeat any more
  if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false;                 // 8 - 37   | 30  MMSI
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 2) ) return false;           // 38-39    | 2    Part Number 0-1 ->
  // Part A: 40 + 128 = len 168
  if ( !AddText(NMEA0183AISMsg, Name, 120) ) return false;                // 40-159   | 120 Vessel Name  20 6-bit characters -> Ascii Table
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 8) ) return false;           // 160-167  | 8    Spare
  if ( !NMEA0183AISMsg.InitAis() ) return false;
  int padBits=0;
  if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayloadFix(padBits) ) ) return false;
  if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) return false;    
  return true;
}

// ***************************************************************************************************************
bool  SetAISClassBMessage24PartB(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat,
                          uint32_t UserID, uint8_t VesselType, char *VendorID, char *Callsign,
                          double Length, double Beam, double PosRefStbd,  double PosRefBow, uint32_t MothershipID ) {


  // AIS Type 24 Message
  NMEA0183AISMsg.ClearAIS();
  // Common for PART A AND Part B Bit 0 - 39 / len 40
  if ( !AddMessageType(NMEA0183AISMsg, 24) ) return false;                // 0 - 5    | 6    Message Type -> Constant: 24
  if ( !AddRepeat(NMEA0183AISMsg, Repeat) ) return false;                 // 6 - 7    | 2    Repeat Indicator: 0 = default; 3 = do not repeat any more
  if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false;                 // 8 - 37   | 30  MMSI
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(1, 2) ) return false;      // 38-39    | 2    Part Number 0-1 ->

  // https://www.navcen.uscg.gov/?pageName=AISMessagesB
  // PART B: 40 + 128 = len 168
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(VesselType, 8) ) return false;     // 168-175 | 40-47  |  8    Ship Type 0....99
  if ( !AddText(NMEA0183AISMsg, VendorID, 42) ) return false;                // 176-217 | 48-89  | 42    Vendor ID + Unit Model Code + Serial Number
  if ( !AddText(NMEA0183AISMsg, Callsign, 42) ) return false;                // 218-259 | 90-131 | 42   Call Sign  WDE4178      -> 7  6-bit characters, as in Msg Type 5
  if ( !AddDimensions(NMEA0183AISMsg, Length, Beam, PosRefStbd, PosRefBow) ) return false;  // 260-289 | 132-161 | 30 Dimensions
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 6) ) return false;              // 290-295 | 162-167 | 6    Spare
  if ( !NMEA0183AISMsg.InitAis() ) return false;
  int padBits=0;
  if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayloadFix(padBits) ) ) return false;
  if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) return false;                            
  return true;
}

//  ****************************************************************************
// AIS ATON report (129041) -> Type 21: Position and status report for aids-to-navigation
// PGN129041

bool SetAISMessage21(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t Repeat, uint32_t UserID,
			   double Latitude, double Longitude, bool Accuracy, bool RAIM,
			   uint8_t Seconds, double Length, double Beam, double PositionReferenceStarboard,
         double PositionReferenceTrueNord, tN2kAISAtoNType Type, bool OffPositionIndicator,
         bool VirtualAtoNFlag, bool AssignedModeFlag, tN2kGNSStype GNSSType, uint8_t AtoNStatus,
         char * atonName ) {
  //
  NMEA0183AISMsg.ClearAIS();
  if ( !AddMessageType(NMEA0183AISMsg, 21) ) return false;      // 0 - 5    | 6    Message Type -> Constant: 18
  if ( !AddRepeat(NMEA0183AISMsg, Repeat) ) return false;              // 6 - 7    | 2    Repeat Indicator: 0 = default; 3 = do not repeat any more
  if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false;              // 8 - 37   | 30  MMSI
  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(Type,5)) return false;      //          | 5 aid type
  //the name must be split:
  //if it's > 120 bits the rest goes to the last parameter
  if ( !NMEA0183AISMsg.AddEncodedCharToPayloadBin(atonName,120)) 
    return false;                                                       //          | 120 name  
  if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Accuracy) ) return false; //          | 1 accuracy
  if ( !AddLongitude(NMEA0183AISMsg,Longitude)) return false;          //          | 28 lon
  if ( !AddLatitude(NMEA0183AISMsg,Latitude)) return false;            //          | 27 lat
  if ( !AddDimensions(NMEA0183AISMsg, Length, Beam, 
    PositionReferenceStarboard, PositionReferenceTrueNord)) return false; //       | 30 dim
  if ( !AddEPFDFixType(NMEA0183AISMsg,GNSSType)) return false;         //          | 4 fix type
  if ( !AddSeconds(NMEA0183AISMsg,Seconds)) return false;              //          | 6 second
  if ( !NMEA0183AISMsg.AddBoolToPayloadBin(OffPositionIndicator)) 
      return false;                                                    //          | 1 off
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(0,8)) return false;     //          | 8 reserverd
  if ( !NMEA0183AISMsg.AddBoolToPayloadBin(RAIM)) return false;      //          | 1 raim
  if ( !NMEA0183AISMsg.AddBoolToPayloadBin(VirtualAtoNFlag)) 
      return false;                                                    //          | 1 virt
  if ( !NMEA0183AISMsg.AddBoolToPayloadBin(AssignedModeFlag))
      return false;                                                    //          | 1 assigned
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(0,1)) return false;          //          | 1 spare
  size_t l=strlen(atonName);
  if (l >=20){
    uint8_t bitlen=(l-20)*6;
    if (bitlen > 88) bitlen=88;
    if ( !NMEA0183AISMsg.AddEncodedCharToPayloadBin(atonName+20,bitlen)) return false;    //          | name
  }
  if ( !NMEA0183AISMsg.InitAis() ) return false;
  int padBits=0;
  if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayload(padBits) ) ) return false;
  if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) return false;   

  return true;
}

//******************************************************************************
//                 Validations and Unit Transformations
//******************************************************************************

// *****************************************************************************
// 6bit    Message Type -> Constant: 1 or 3, 5, 24 etc.
bool AddMessageType(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageType) {

  if (MessageType < 0 || MessageType > 24  ) MessageType = 1;
  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(MessageType, 6) ) return false;
  return true;
}

// *****************************************************************************
// 2bit    Repeat Indicator: 0 = default; 3 = do not repeat any more
bool AddRepeat(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t Repeat) {

  if (Repeat < 0 || Repeat > 3) Repeat = 0;
  if ( !NMEA0183AISMsg.AddIntToPayloadBin(Repeat, 2) ) return false;
  return true;
}

// *****************************************************************************
// 30bit UserID = MMSI        (9 decimal digits)
bool AddUserID(tNMEA0183AISMsg &NMEA0183AISMsg, uint32_t UserID) {

  if (UserID < 0||UserID > 999999999) UserID = 0;
   if ( !NMEA0183AISMsg.AddIntToPayloadBin(UserID, 30) ) return false;
  return true;
}

// *****************************************************************************
//   30 bit   IMO Number
//   0 = not available = default – Not applicable to SAR aircraft
//  0000000001-0000999999 not used
//  0001000000-0009999999 = valid IMO number;
//  0010000000-1073741823 = official flag state number.
bool AddIMONumber(tNMEA0183AISMsg &NMEA0183AISMsg, uint32_t &IMONumber) {
  uint32_t iTemp;
  ( (IMONumber >= 999999 && IMONumber <= 9999999)||(IMONumber >= 10000000 && IMONumber <= 1073741823) )? iTemp = IMONumber : iTemp = 0;
  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(iTemp, 30) ) return false;
  return true;
}

// *****************************************************************************
// 42bit Callsign alphanumeric value, max 7 six-bit characters
// 120bit Name or Destination
bool AddText(tNMEA0183AISMsg &NMEA0183AISMsg, char *FieldVal, uint8_t length) {
  uint8_t len = length/6;
  if ( strlen(FieldVal) > len ) FieldVal[len] = 0;
  if ( !NMEA0183AISMsg.AddEncodedCharToPayloadBin(FieldVal, length) ) return false;
  return true;
}

//  *****************************************************************************
//  Calculate Dimension A, B, C, D
//  double PosRefBow      240-248   |   9 [m] Dimension to Bow, reference for pos. A
//  Length -  PosRefBow    249-257  |   9 [m] Dimension to Stern, reference for pos. B
//  Beam - PosRefStbd      258-263  |   6 [m] Dimension to Port, reference for pos. C
//  PosRefStbd             264-269  |   6 [m] Dimension to Starboard, reference for pos. D
//  Ship dimensions will be 0 if not available. For the dimensions to bow and stern,
//  the special value 511 indicates 511 meters or greater;
//  for the dimensions to port and starboard, the special value 63 indicates 63 meters or greater.
// 30 Bit
bool AddDimensions(tNMEA0183AISMsg &NMEA0183AISMsg, double Length, double Beam, double PosRefStbd, double PosRefBow) {
  uint16_t _PosRefBow = 0;
  uint16_t _PosRefStern = 0;
  uint16_t _PosRefStbd = 0;
  uint16_t _PosRefPort = 0;

  if ( PosRefBow >= 0.0 && PosRefBow <= 511.0 ) {
    _PosRefBow = ceil(PosRefBow);
  } else {
    _PosRefBow = 511;
  }

  if ( PosRefStbd >= 0.0 && PosRefStbd <= 63.0 ) {
    _PosRefStbd = ceil(PosRefStbd);
  } else {
    _PosRefStbd = 63;
  }

  if ( !N2kIsNA(Length) ) {
    _PosRefStern = ceil( Length ) - _PosRefBow;
    if ( _PosRefStern < 0 ) _PosRefStern = 0;
    if ( _PosRefStern > 511 ) _PosRefStern = 511;
  }
  if ( !N2kIsNA(Beam) ) {
    _PosRefPort = ceil( Beam ) - _PosRefStbd;
    if ( _PosRefPort < 0 ) _PosRefPort = 0;
    if ( _PosRefPort > 63 ) _PosRefPort = 63;
  }

  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(_PosRefBow, 9) ) return false;
  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(_PosRefStern, 9) ) return false;
  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(_PosRefPort, 6) ) return false;
  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(_PosRefStbd, 6) ) return false;
  return true;
}

// *****************************************************************************
// 4 Bit  Navigational Status  e.g.: "Under way sailing"
// Same values used as in tN2kAISNavStatus, so we can use direct numbers
bool AddNavStatus(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t &NavStatus) {
  uint8_t iTemp;
  (NavStatus >= 0 && NavStatus <= 15 )? iTemp = NavStatus : iTemp = 15;
   if ( ! NMEA0183AISMsg.AddIntToPayloadBin(iTemp, 4) ) return false;
  return true;
}

// *****************************************************************************
// 8bit  [rad/s -> degree/minute]  Rate of Turn ROT  128 = N/A
// 0 = not turning
// 1…126 = turning right at up to 708 degrees per minute or higher
//  1…-126 = turning left at up to 708 degrees per minute or higher
//  127 = turning right at more than 5deg/30s (No TI available)
//  -127 = turning left at more than 5deg/30s (No TI available)
//  128 (80 hex) indicates no turn information available (default)
bool AddROT(tNMEA0183AISMsg &NMEA0183AISMsg, double &rot) {
  int8_t iTemp;
  if ( N2kIsNA(rot)) iTemp = 128;
  else {
    rot *= radsToDegMin;
    (rot > -128.0 && rot < 128.0)? iTemp = aRoundToInt(rot) : iTemp = 128;
  }

  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(iTemp, 8) ) return false;
  return true;
}

// *****************************************************************************
// 10 bit [m/s -> kts]  SOG x10, 1023 = N/A
// Speed over ground is in 0.1-knot resolution from 0 to 102 knots.
// Value 1023 indicates speed is not available, value 1022 indicates 102.2 knots or higher.
bool AddSOG (tNMEA0183AISMsg &NMEA0183AISMsg, double &sog) {
  int16_t iTemp;
  if ( sog < 0.0 ) iTemp = 1023;
  else {
    sog *= msTokn;
    if (sog > 102.2) iTemp = 1023;
    else iTemp = aRoundToInt( 10 * sog );
  }

  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(iTemp, 10) ) return false;
  return true;
}

// *****************************************************************************
// 28 bit   @TODO check negative values
// Values up to plus or minus 180 degrees, East = positive, West = negative.
// A value of 181 degrees (0x6791AC0 hex) indicates that longitude is not available and is the default.
// AIS Longitude is given in in 1/10000 min; divide by 600000.0 to obtain degrees.
bool AddLongitude(tNMEA0183AISMsg &NMEA0183AISMsg, double &Longitude) {
  int32_t iTemp;
  (Longitude >= -180.0 && Longitude <= 180.0)? iTemp = (int) (Longitude * 600000) : iTemp = 181 * 600000;
  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(iTemp, 28) ) return false;
  return true;
}

// *****************************************************************************
//   27 bit
//  Values up to plus or minus 90 degrees, North = positive, South = negative.
//   A value of 91 degrees (0x3412140 hex) indicates latitude is not available and is the default.
bool AddLatitude(tNMEA0183AISMsg &NMEA0183AISMsg, double &Latitude) {
  int32_t iTemp;
  (Latitude >= -90.0 && Latitude <= 90.0)? iTemp = (int) (Latitude * 600000) : iTemp = 91 * 600000;
  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(iTemp, 27) ) return false;
  return true;
}

//  ****************************************************************************
// 9 bit True Heading (HDG) 0 to 359 degrees, 511 = not available.
bool AddHeading (tNMEA0183AISMsg &NMEA0183AISMsg, double &heading) {
  uint16_t iTemp;
  if ( N2kIsNA(heading) ) iTemp = 511;
  else {
    heading *= radToDeg;
    (heading >= 0.0 && heading <= 359.0 )? iTemp = aRoundToInt( heading ) : iTemp = 511;
  }
  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(iTemp, 9) ) return false;
  return true;
}

// *****************************************************************************
// 12bit Relative to true north, to 0.1 degree precision
bool AddCOG(tNMEA0183AISMsg &NMEA0183AISMsg, double cog) {
  int16_t iTemp;
  cog *= radToDeg;
  if ( cog >= 0.0 && cog < 360.0 ) { iTemp = aRoundToInt( cog * 10 ); } else { iTemp = 3600; }
  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(iTemp, 12) ) return false;
  return true;
}

// *****************************************************************************
// 6bit   Seconds in UTC timestamp should be 0-59, except for these special values:
// 60 if time stamp is not available (default)
// 61 if positioning system is in manual input mode
// 62 if Electronic Position Fixing System operates in estimated (dead reckoning) mode,
// 63 if the positioning system is inoperative.
bool AddSeconds (tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t &Seconds) {
  uint8_t iTemp;
  (Seconds >= 0 && Seconds <= 63 )? iTemp = Seconds : iTemp = 60;
  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(iTemp, 6) ) return false;
  return true;
}

//  *****************************************************************************
//  4 bit  Position Fix Type, See "EPFD Fix Types" 0 (default)
bool AddEPFDFixType(tNMEA0183AISMsg &NMEA0183AISMsg, tN2kGNSStype &GNSStype) {
  // Translate tN2kGNSStype to AIS conventions
  // 3 & 4 not defined in AIS -> we take 1 for GPS
  uint8_t fixType = 0;
  switch (GNSStype) {
    case 0: // GPS
    case 3: // GPS+SBAS/WAAS
    case 4: // GPS+SBAS/WAAS+GLONASS
      fixType = 1; break;
    case 1: // GLONASS
      fixType = 2; break;
    case 2: // GPS+GLONASS
      fixType = 3; break;
    case 5: // Chayka
      fixType = 5; break;
    case 6: // integrated
      fixType = 6; break;
    case 7: // surveyed
      fixType = 7; break;
    case 8: // Galileo
      fixType = 8; break;
    default:
      fixType = 0;
  }
  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(fixType, 4) ) return false;
  return true;
}

// *****************************************************************************
// 8 bit Maxiumum present static draught
// In 1/10 m, 255 = draught 25.5 m or greater, 0 = not available = default; in accordance with IMO Resolution A.851
bool AddStaticDraught(tNMEA0183AISMsg &NMEA0183AISMsg, double &Draught) {
  uint8_t staticDraught;
  if ( N2kIsNA(Draught) ) staticDraught = 0;
  else if (Draught < 0.0) staticDraught = 0;
  else if (Draught>25.5) staticDraught = 255;
  else staticDraught = (int) ceil( 10.0 * Draught);

  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(staticDraught, 8) ) return false;
  return true;
}

// *****************************************************************************
// 20bit Estimated time of arrival; MMDDHHMM UTC
// 4 Bits 19-16: month; 1-12; 0 = not available = default
// 5 Bits 15-11: day; 1-31; 0 = not available = default
// 5 Bits 10-6: hour; 0-23; 24 = not available = default
// 6 Bits 5-0: minute; 0-59; 60 = not available = default
// N2k Field #7: ETA Time - Seconds since midnight Bits: 32   Units: s
// Type: Time Resolution: 0.0001 Signed: false    e.g. 36000.00
//  N2k Field #8: ETA Date - Days since January 1, 1970 Bits: 16
//  Units: days Type: Date Resolution: 1 Signed: false  e.g. 18184
bool AddETADateTime(tNMEA0183AISMsg &NMEA0183AISMsg, uint16_t &ETAdate, double &ETAtime) {

  uint8_t month = 0;
  uint8_t day = 0;
  uint8_t hour = 24;
  uint8_t minute = 60;

  if (!N2kIsNA(ETAdate) && ETAdate > 0 ) {
    tmElements_t tm;
    #ifndef _Time_h
    time_t t=NMEA0183AISMsg.daysToTime_t(ETAdate);
    #else
    time_t t=ETAdate*86400;
    #endif
    NMEA0183AISMsg.breakTime(t, tm);
    month = (uint8_t) NMEA0183AISMsg.GetMonth(tm);
    day = (uint8_t) NMEA0183AISMsg.GetDay(tm);
  }
  if ( !N2kIsNA(ETAtime) && ETAtime >= 0 ) {
    double temp = ETAtime / 3600;
    hour = (int) temp;
    minute = (int) ((temp - hour) * 60);
  } else {
    hour = 24;
    minute = 60;
  }
  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(month, 4) ) return false;
  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(day, 5) ) return false;
  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(hour, 5) ) return false;
  if ( ! NMEA0183AISMsg.AddIntToPayloadBin(minute, 6) ) return false;
  return true;
}


