TensionDev_AutomaticIdentificationSystem/AIS/AISMessage09.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace TensionDev.Maritime.AIS
{
    /// <summary>
    /// Message 9: Standard search and rescue aircraft position report
    /// </summary>
    public class AISMessage09 : AISMessage
    {
        private UInt64 _bitVector0_59;
        private UInt64 _bitVector60_119;
        private UInt64 _bitVector120_167;

        protected UInt32 userId30;
        protected UInt16 altitude12;
        protected UInt16 speedOverGround10;
        protected Boolean positionAccuracy1;
        protected Int32 longitude28;
        protected Int32 latitude27;
        protected UInt16 courseOverGround12;
        protected UInt16 timestamp6;
        protected UInt16 altitudeSensor1;
        protected UInt16 spare7;
        protected UInt16 dataTerminalEquipment1;
        protected UInt16 spare3;
        protected UInt16 assignedModeFlag1;
        protected Boolean raim1;
        protected Boolean communicationStateSelector1;

        /// <summary>
        /// Unique identifier such as MMSI number
        /// </summary>
        public String UserId { get => userId30.ToString("D9"); set => userId30 = UInt32.Parse(value); }

        /// <summary>
        /// <para>Altitude (derived from GNSS or barometric (see altitude sensor parameter below)) (m) (0 - 4094 m)</para>
        /// 4095 = not available, 4094 = 4094 m or higher
        /// </summary>
        public UInt16 Altitude { get => altitude12; set => altitude12 = (UInt16)Math.Min((UInt16)4095, value); }

        /// <summary>
        /// Is the Speed Over Ground Available?
        /// </summary>
        public Boolean AltitudeAvailable { get => Altitude < 4095; }

        /// <summary>
        /// Speed Over Ground in Knots
        /// </summary>
        public Decimal SpeedOverGroundKnots { get => speedOverGround10; set => speedOverGround10 = (UInt16)Math.Min(1023, Math.Abs(value)); }

        /// <summary>
        /// Is the Speed Over Ground Available?
        /// </summary>
        public Boolean SpeedOverGroundAvailable { get => speedOverGround10 < 1023; }

        /// <summary>
        /// The position accuracy (PA) flag should be determined in accordance with Table 50
        /// <para>True = high (Less Than or Equal to 10 m)</para>
        /// <para>False = low (Greater Than 10 m)</para>
        /// <para>False = default</para>
        /// </summary>
        public Boolean PositionAccuracy { get => positionAccuracy1; set => positionAccuracy1 = value; }

        /// <summary>
        /// <para>Longitude in Decimal Degrees</para>
        /// Longitude stored in 1/10 000 min (+-180°, East = positive (as per 2’s complement),
        /// West = negative(as per 2’s complement).
        /// 181 = (0x6791AC0) = not available = default)
        /// </summary>
        public Decimal LongitudeDecimalDegrees { get => (longitude28 / 10000.0M) / 60.0M; set => longitude28 = (Int32)((value * 60.0M) * 10000.0M); }

        /// <summary>
        /// Is the Longitude Available
        /// </summary>
        public Boolean LongitudeAvailable { get => longitude28 != 0x6791AC0; }

        /// <summary>
        /// <para>Latitude in Decimal Degrees</para>
        /// Latitude stored in 1/10 000 min (+-90°, North = positive (as per 2’s complement),
        /// South = negative(as per 2’s complement).
        /// 91 = (0x3412140) = not available = default)
        /// </summary>
        public Decimal LatitudeDecimalDegrees { get => (latitude27 / 10000.0M) / 60.0M; set => latitude27 = (Int32)((value * 60.0M) * 10000.0M); }

        /// <summary>
        /// Is the Latitude Available
        /// </summary>
        public Boolean LatitudeAvailable { get => latitude27 != 0x3412140; }

        /// <summary>
        /// Course Over Ground
        /// </summary>
        public Decimal CourseOverGroundDegrees { get => courseOverGround12 / 10.0M; set { if (Math.Abs(value) < 360.0M) { courseOverGround12 = (UInt16)Math.Abs(value * 10); } else { courseOverGround12 = 3600; } } }

        /// <summary>
        /// Is the Course Over Ground Available
        /// </summary>
        public Boolean CourseOverGroundAvailable { get => courseOverGround12 < 3600; }

        /// <summary>
        /// <para>UTC second when the report was generated by the electronic position system (EPFS)</para>
        /// (0-59, or 60 if time stamp is not available, which should also be the
        /// default value, or 61 if positioning system is in manual input mode, or 62 if
        /// electronic position fixing system operates in estimated(dead reckoning)
        /// mode, or 63 if the positioning system is inoperative)
        /// </summary>
        public UInt16 Timestamp { get => timestamp6; set => timestamp6 = value; }

        /// <summary>
        /// <para>0 = GNSS</para>
        /// <para>1 = barometric source</para>
        /// </summary>
        public AltitudeSensorEnum AltitudeSensor { get => (AltitudeSensorEnum)(altitudeSensor1); set => altitudeSensor1 = (UInt16)value; }

        /// <summary>
        /// Data terminal equipment (DTE) ready (0 = available, 1 = not available = default) (see § 3.3.1)
        /// </summary>
        public Boolean DTE { get => dataTerminalEquipment1 == 0; set { if (value) { dataTerminalEquipment1 = 0; } else { dataTerminalEquipment1 = 1; } } }

        /// <summary>
        /// <para>0 = Station operating in autonomous and continuous mode = default</para>
        /// <para>1 = Station operating in assigned mode</para>
        /// </summary>
        public AssignedModeEnum AssignedModeFlag { get => (AssignedModeEnum)(assignedModeFlag1); set => assignedModeFlag1 = (UInt16)value; }

        /// <summary>
        /// Receiver autonomous integrity monitoring (RAIM) flag of electronic
        /// position fixing device; 
        /// <para>False = RAIM not in use = default;</para>
        /// <para>True = RAIM in use.</para>
        /// </summary>
        public Boolean RAIMFlag { get => raim1; set => raim1 = value; }

        /// <summary>
        /// <para>False = SOTDMA communication state follows</para>
        /// <para>True = ITDMA communication state follows</para>
        /// </summary>
        public Boolean CommunicationStateSelectorFlag { get => communicationStateSelector1; set => communicationStateSelector1 = value; }

        /// <summary>
        /// SOTDMA communication state (see ß 3.3.7.2.2), if communication
        /// state selector flag is set to 0, or ITDMA communication state(see
        /// ß 3.3.7.2.3), if communication state selector flag is set to 1
        /// </summary>
        public AISCommunicationState CommunicationState { get; set; }

        public AISMessage09()
        {
            MessageId = 9;
            RepeatIndicator = 0;
            altitude12 = 4095;
            speedOverGround10 = 1023;
            positionAccuracy1 = false;
            longitude28 = 0x6791AC0;
            latitude27 = 0x3412140;
            courseOverGround12 = 3600;
            timestamp6 = 60;
            altitudeSensor1 = 0;
            dataTerminalEquipment1 = 1;
            assignedModeFlag1 = 0;
            raim1 = false;
        }

        /// <summary>
        /// Speed over ground in 1/10 knot steps (0-102.2 knots)
        /// 1 023 = not available, 1 022 = 102.2 knots or higher
        /// </summary>
        /// <param name="speedOverGround">Speed Over Ground at up to 102.2 knots or higher</param>
        /// <param name="SOGAvailable">Is the Speed Over Ground value available?</param>
        public void SetSpeedOverGround(Decimal speedOverGround, Boolean SOGAvailable = true)
        {
            if (SOGAvailable)
            {
                speedOverGround10 = Math.Min((UInt16)1022, (UInt16)Math.Abs(speedOverGround * 10.0M));
            }
            else
            {
                speedOverGround10 = 1023;
            }
        }

        /// <summary>
        /// Longitude in 1/10 000 min (+-180°, East = positive (as per 2’s complement),
        /// West = negative(as per 2’s complement).
        /// 181 = (0x6791AC0) = not available = default)
        /// </summary>
        /// <param name="longitudeDD">Logitude in Decimal Degrees</param>
        /// <param name="LongitudeAvailable">Is Longitude value available?</param>
        public void SetLongitude(Decimal longitudeDD, Boolean LongitudeAvailable = true)
        {
            if (LongitudeAvailable)
            {
                longitude28 = (Int32)(longitudeDD * 60.0M * 10000.0M);
            }
            else
            {
                longitude28 = 0x6791AC0;
            }
        }

        /// <summary>
        /// Latitude in 1/10 000 min (±90°, North = positive (as per 2’s complement),
        /// South = negative(as per 2’s complement).
        /// 91° (0x3412140) = not available = default)
        /// </summary>
        /// <param name="latitudeDD">Latitude in Decimal Degrees</param>
        /// <param name="LatitudeAvailable">Is Latitude value available?</param>
        public void SetLatitude(Decimal latitudeDD, Boolean LatitudeAvailable = true)
        {
            if (LatitudeAvailable)
            {
                latitude27 = (Int32)(latitudeDD * 60.0M * 10000.0M);
            }
            else
            {
                latitude27 = 0x3412140;
            }
        }

        /// <summary>
        /// Course over ground in 1/10 = (0-3 599). 3 600 (E10h) = not available =
        /// default. 3 601-4 095 should not be used
        /// </summary>
        /// <param name="courseOverGround">Course Over Ground</param>
        /// <param name="COGAvailable">Is the Course Over Ground value available?</param>
        public void SetCourseOverGround(Decimal courseOverGround, Boolean COGAvailable = true)
        {
            if (COGAvailable)
            {
                courseOverGround12 = Math.Min((UInt16)3599, (UInt16)Math.Abs(courseOverGround * 10.0M));
            }
            else
            {
                courseOverGround12 = 3600;
            }
        }

        public override IList<String> EncodeSentences()
        {
            IList<String> sentences = new List<String>();

            StringBuilder stringBuilder = new StringBuilder();
            IList<String> payload = EncodePayloads();

            stringBuilder.AppendFormat("!AI{0},1,1,,A,{1},0", SentenceFormatter.ToString(), payload[0]);

            Byte checksum = CalculateChecksum(stringBuilder.ToString());

            stringBuilder.AppendFormat("*{0}\r\n", checksum.ToString("X2"));

            sentences.Add(stringBuilder.ToString());

            return sentences;
        }

        protected override void DecodePayloads(IList<String> payloads)
        {
            if (payloads.Count != 1)
                throw new ArgumentOutOfRangeException(nameof(payloads));

            String payload = payloads[0];

            _bitVector0_59 = DecodePayload(payload, 0, 10);
            _bitVector60_119 = DecodePayload(payload, 10, 10);
            _bitVector120_167 = DecodePayload(payload, 20, 8);

            SetBitVector0_59();
            SetBitVector60_119();
            SetBitVector120_167();
        }

        protected override IList<String> EncodePayloads()
        {
            IList<String> payloads = new List<String>();

            StringBuilder payload = new StringBuilder();

            GetBitVector0_59();
            GetBitVector60_119();
            GetBitVector120_167();

            payload.Append(EncodePayload(_bitVector0_59, 60));
            payload.Append(EncodePayload(_bitVector60_119, 60));
            payload.Append(EncodePayload(_bitVector120_167, 48));

            payloads.Add(payload.ToString());

            return payloads;
        }

        private void GetBitVector0_59()
        {
            _bitVector0_59 = 0;

            _bitVector0_59 = messageId6;

            _bitVector0_59 <<= 2;
            _bitVector0_59 |= repeatIndicator2;

            _bitVector0_59 <<= 30;
            _bitVector0_59 |= userId30;

            _bitVector0_59 <<= 12;
            _bitVector0_59 |= altitude12;

            _bitVector0_59 <<= 10;
            _bitVector0_59 |= speedOverGround10;
        }

        private void GetBitVector60_119()
        {
            _bitVector60_119 = 0;

            if (positionAccuracy1)
            {
                _bitVector60_119 |= 1;
            }

            if (longitude28 < 0)
            {
                UInt64 tempLongitude = (UInt64)(longitude28 + (Int32)0xFFFFFFF);

                _bitVector60_119 <<= 28;
                _bitVector60_119 |= tempLongitude;
            }
            else
            {
                _bitVector60_119 <<= 28;
                _bitVector60_119 |= (UInt64)((UInt32)longitude28);
            }

            if (latitude27 < 0)
            {
                UInt64 tempLatitude = (UInt64)(latitude27 + (Int32)0x7FFFFFF);

                _bitVector60_119 <<= 27;
                _bitVector60_119 |= tempLatitude;
            }
            else
            {
                _bitVector60_119 <<= 27;
                _bitVector60_119 |= (UInt64)((UInt32)latitude27);
            }

            _bitVector60_119 <<= 4;
            _bitVector60_119 |= GetBitVector((UInt64)courseOverGround12, 12, 8);
        }

        private void GetBitVector120_167()
        {
            _bitVector120_167 = 0;

            _bitVector120_167 = GetBitVector((UInt64)courseOverGround12, 8);

            _bitVector120_167 <<= 6;
            _bitVector120_167 |= timestamp6;

            _bitVector120_167 <<= 1;
            _bitVector120_167 |= altitudeSensor1;

            _bitVector120_167 <<= 7;
            _bitVector120_167 |= spare7;

            _bitVector120_167 <<= 1;
            _bitVector120_167 |= dataTerminalEquipment1;

            _bitVector120_167 <<= 3;
            _bitVector120_167 |= spare3;

            _bitVector120_167 <<= 1;
            _bitVector120_167 |= assignedModeFlag1;

            _bitVector120_167 <<= 1;
            if (raim1)
            {
                _bitVector120_167 |= 1;
            }

            _bitVector120_167 <<= 1;
            if (communicationStateSelector1)
            {
                _bitVector120_167 |= 1;
            }

            _bitVector120_167 <<= 19;
            _bitVector120_167 |= CommunicationState.CommunicationState;
        }

        private void SetBitVector0_59()
        {
            speedOverGround10 = (UInt16)(_bitVector0_59 & 0x3FF);
            _bitVector0_59 >>= 10;

            altitude12 = (UInt16)(_bitVector0_59 & 0xFFF);
            _bitVector0_59 >>= 12;

            userId30 = (UInt32)(_bitVector0_59 & 0x3FFFFFFF);
            _bitVector0_59 >>= 30;

            repeatIndicator2 = (UInt16)(_bitVector0_59 & 0x3);
            _bitVector0_59 >>= 2;

            messageId6 = (UInt16)(_bitVector0_59 & 0x3F);
            _bitVector0_59 >>= 6;
        }

        private void SetBitVector60_119()
        {
            courseOverGround12 = (UInt16)(_bitVector60_119 & 0xF);
            courseOverGround12 <<= 8;
            _bitVector60_119 >>= 4;

            latitude27 = (Int32)(_bitVector60_119 & 0x7FFFFFF);
            if (latitude27 > 0x3FFFFFF)
            {
                latitude27 -= 0x7FFFFFF;
            }
            _bitVector60_119 >>= 27;

            longitude28 = (Int32)(_bitVector60_119 & 0xFFFFFFF);
            if (longitude28 > 0x7FFFFFF)
            {
                longitude28 -= 0xFFFFFFF;
            }
            _bitVector60_119 >>= 28;

            positionAccuracy1 = ((_bitVector60_119 & 0x1) != 0);
            _bitVector60_119 >>= 1;
        }

        private void SetBitVector120_167()
        {
            UInt32 communicationState = (UInt32)(_bitVector120_167 & 0x7FFFF);
            _bitVector120_167 >>= 19;
            CommunicationState = new SOTDMACommunicationState()
            {
                CommunicationState = communicationState
            };

            communicationStateSelector1 = ((_bitVector120_167 & 0x1) != 0);
            _bitVector120_167 >>= 1;

            raim1 = ((_bitVector120_167 & 0x1) != 0);
            _bitVector120_167 >>= 1;

            assignedModeFlag1 = (UInt16)(_bitVector120_167 & 0x1);
            _bitVector120_167 >>= 1;

            spare3 = (UInt16)(_bitVector120_167 & 0x7);
            _bitVector120_167 >>= 3;

            dataTerminalEquipment1 = (UInt16)(_bitVector120_167 & 0x1);
            _bitVector120_167 >>= 1;

            spare7 = (UInt16)(_bitVector120_167 & 0x7F);
            _bitVector120_167 >>= 7;

            altitudeSensor1 = (UInt16)(_bitVector120_167 & 0x1);
            _bitVector120_167 >>= 1;

            timestamp6 = (UInt16)(_bitVector120_167 & 0x3F);
            _bitVector120_167 >>= 6;

            courseOverGround12 |= (UInt16)(_bitVector120_167 & 0xFF);
            _bitVector120_167 >>= 8;
        }

        public enum AltitudeSensorEnum
        {
            /// <summary>
            /// 0 = GNSS
            /// </summary>
            GNSS = 0,
            /// <summary>
            /// 1 = barometric source
            /// </summary>
            BarometricSource = 1,
        }

        public enum AssignedModeEnum
        {
            /// <summary>
            /// 0 = Station operating in autonomous and continuous mode
            /// </summary>
            AutonomousContinuousMode = 0,
            /// <summary>
            /// 1 = Station operating in assigned mode
            /// </summary>
            AssignedMode = 1,
        }
    }
}
