using System;
using Microsoft.SPOT;

namespace CPF
{
    public class RawDegrees
    {
        public uint _billionths;
        public uint _deg;
        public bool _negative;

        public RawDegrees()
        {
            _deg = 0;
            _billionths = 0;
            _negative = false;
        }
    }

    public class NMEA_Location
    {
        private const byte  _PERIOD = (byte)'.';
        private const byte  _ZERO   = (byte)'0';

        private bool        _valid;
        private bool        _updated;
        public RawDegrees   _rawLatData;
        public RawDegrees   _rawLngData;
        public RawDegrees   _rawNewLatData;
        public RawDegrees   _rawNewLngData;

        private long        _lastCommitTime;

        public void SetLatitude(ByteString s) 
        { 
            ParseDegrees(s, ref _rawNewLatData); 
        }

        public void SetLongitude(ByteString s) { 
            ParseDegrees(s, ref _rawNewLngData); 
        }

        public void Commit() 
        {
            _rawLatData         = _rawNewLatData;
            _rawLngData         = _rawNewLngData;
            _lastCommitTime     = Utils.MillisecondsSinceLastReboot();
            _valid              = true;
            _updated            = true;
        }

        public bool IsValid() 
        { 
            return _valid; 
        }

        public bool IsUpdated() 
        { 
            return _updated; 
        }

        public long Age() 
        { 
            return _valid ? Utils.MillisecondsSinceLastReboot() - _lastCommitTime : Conversions.MAX_SIGNED; 
        }

        public RawDegrees RawLat() 
        { 
            _updated = false; 
            return _rawLatData; 
        }

        public RawDegrees RawLng() { 
            _updated = false; 
            return _rawLngData; 
        }

        public double Lat() 
        {
            _updated = false;
            double value = _rawLatData._deg + (_rawLatData._billionths / 1000000000.0);
            return _rawLatData._negative ? -value : value;
        }

        public double Lng() 
        {
            _updated = false;
            double value = _rawLngData._deg + (_rawLngData._billionths / 1000000000.0);
            return _rawLngData._negative ? -value : value;
        }

        // Parse degrees in that funny NMEA format DDMM.MMMM
        public void ParseDegrees(ByteString s, ref RawDegrees d)
        {
            uint leftOfDecimal = (uint)Conversions.ByteStringToInteger(s);
            uint minutes = (uint)(leftOfDecimal % 100);
            uint multiplier = 10000000;
            long tenMillionthsOfMinutes = minutes * multiplier;

            d._deg = leftOfDecimal / 100;

            while (Conversions.IsDigit(s.Deref()))
                s.Advance(1);

            if (s.Deref() == _PERIOD)
                while (Conversions.IsDigit(s.DerefPreInc()))
                {
                    multiplier /= 10;
                    tenMillionthsOfMinutes += (s.Deref() - _ZERO) * multiplier;
                }

            d._billionths = (uint)(5 * tenMillionthsOfMinutes + 1) / 3;
            d._negative = false;
        }

        public NMEA_Location()
        {
            _valid = false;
            _updated = false;
            _lastCommitTime = 0;
            _rawLatData = new RawDegrees();
            _rawLngData = new RawDegrees();
            _rawNewLatData = new RawDegrees();
            _rawNewLngData = new RawDegrees();
        }
    }

}
