using System;
using System.Threading;
using System.Text;
using Microsoft.SPOT;

namespace CPF
{
    public static class Iridium
    {
        public enum Status { Idle, Checking };

        private static A3LA                     _modem;
        private static byte[]                   _message;
        private static byte[]                   _response;
        private static int                      _responseLength;
        private static int                      _originatedStatus;
        private static int                      _originatedSeqnum;
        private static int                      _terminatedStatus;
        private static int                      _terminatedSeqnum;
        private static int                      _lengthOfMobileTerminatedMessage;
        private static int                      _numberOfQueuedMessagesAwaitingReception;
        private static StructQueue.qStruct      _qe;
        private static Object                   _iridiumLock;
        private static ByteString               _bs;
        private static Thread                   _pollingThread;
        private static AutoResetEvent           _checkSignalQuality;
        private static int                      _timeBetweenRetriesInMs;
        private static int                      _maximumRetries;
        private static int                      _minimumSignalQuality;
        private static StringBuilder            _signalQualityMessage;
        private static StringBuilder            _retriesMessage;
        private static StringBuilder            _failedMessage;
        private static StringBuilder            _msg;
        private static double                   _voltage;
        private static bool                     _busyCheckingSQ;

        public static void PowerOn(string comPort, int baudRate = 19200)
        {
            if (_modem == null)
            {
                _modem                  = new A3LA(comPort, baudRate);
                _iridiumLock            = new Object();
                _response               = new byte[2048];
                _message                = new byte[2048];
                _bs                     = new ByteString();
                _qe.byteArray           = new Byte[StructQueue.qRecordWidth];
                _modem.PowerOn();
                _modem.ClearMobileOriginatedSequenceNumber();
                _modem.ClearBuffers(A3LA.BuffersToClear.BothOriginatedAndTerminated);
                _pollingThread          = new Thread(new ThreadStart(AwaitSufficientSignaLQuality));
                _checkSignalQuality     = new AutoResetEvent(false);
                _timeBetweenRetriesInMs = 10000;
                _maximumRetries         = 3;
                _minimumSignalQuality   = 1;
                _signalQualityMessage   = new StringBuilder("Signal Quality = ");
                _retriesMessage         = new StringBuilder("       Retries = ");
                _failedMessage          = new StringBuilder("Retries exhausted, insufficient signal quality");
                _msg                    = new StringBuilder(256);
                _voltage                = Double.NaN;
                _busyCheckingSQ         = false;
                _pollingThread.Start();
            }
        }

        private static void AwaitSufficientSignaLQuality()
        {
            int retries;

            while (true)
            {
                _checkSignalQuality.WaitOne();
                for (retries = _maximumRetries; retries > 0; retries--)
                {
                    int sq = _modem.SignalQuality();
                    _msg.Clear();
                    _msg.Append(_signalQualityMessage + sq.ToString() + _retriesMessage + retries.ToString());
                    EngrLogger.write(EngrLogger.ColumnNums.comment, EngrLogger.getPrefix(_msg));
                    if (sq >= _minimumSignalQuality)
                        break;
                    Thread.Sleep(_timeBetweenRetriesInMs);
                }
                if (retries > 0)
                {
                    _modem.Register(); // So that the Iridium network can route MT messages to us...
                    UpdatePosition(_voltage);
                }
                else
                {
                    EngrLogger.write(EngrLogger.ColumnNums.comment, EngrLogger.getPrefix(_failedMessage));
                }
                lock (_iridiumLock) { _busyCheckingSQ = false; }
            }
        }

        public static void ReportPosition(int retries, int timeBetweenRetries, double batteryVoltage)
        {
            lock (_iridiumLock)
            {
                if (!_busyCheckingSQ)
                {
                    _maximumRetries = retries;
                    _timeBetweenRetriesInMs = timeBetweenRetries;
                    _voltage = batteryVoltage;
                    _busyCheckingSQ = true;
                    _checkSignalQuality.Set();
                }
            }
        }

        private static void UpdatePosition(double batteryVoltage)
        {
            _bs.Clear();
            _bs.Append(uBlox.Last_GGA_MessageReceived());
            _bs.Append(IridiumStrings.BVMSG, IridiumStrings.BVMSG.Length);
            String s = batteryVoltage.ToString("F2");
            _bs.Append(s);
            _bs.Append(IridiumStrings.SILLYSUM, IridiumStrings.SILLYSUM.Length);
            Array.Clear(_qe.byteArray, 0, _qe.byteArray.Length);
            Array.Copy(_bs.GetByteArray(), 0, _qe.byteArray, 0, _bs.Length);
            _qe.errorCode = (int)_modem.SendTextMessage(_qe.byteArray, _bs.Length);
            if (_qe.errorCode == (int)A3LA.Response.Match)
            {
                _modem.Status(out _originatedStatus, out _originatedSeqnum,
                              out _terminatedStatus, out _terminatedSeqnum,
                              out _lengthOfMobileTerminatedMessage,
                              out _numberOfQueuedMessagesAwaitingReception);
                _qe.errorCode = _originatedStatus;
                _modem.ClearBuffers(A3LA.BuffersToClear.MobileOriginated);
                if (_qe.errorCode == A3LA.MO_TransferredSuccessfully) 
                {
                    _qe.errorCode = _numberOfQueuedMessagesAwaitingReception + 1; 
                    QueueMessage(StructQueue.QRecordType.PositionReport);
                    if ((_terminatedStatus == 1) && (_lengthOfMobileTerminatedMessage > 0))
                    {
                        Array.Clear(_qe.byteArray, 0, _qe.byteArray.Length);
                        _modem.ReceiveTextMessage(_qe.byteArray,  out _responseLength);
                        QueueMessage(StructQueue.QRecordType.MT_MessageArrived);
                        do
                        {
                            _modem.Status(out _originatedStatus, out _originatedSeqnum,
                                          out _terminatedStatus, out _terminatedSeqnum,
                                          out _lengthOfMobileTerminatedMessage,
                                          out _numberOfQueuedMessagesAwaitingReception);
                            if ((_terminatedStatus == 1) && (_lengthOfMobileTerminatedMessage > 0))
                            {
                                Array.Clear(_qe.byteArray, 0, _qe.byteArray.Length);
                                _modem.ReceiveTextMessage(_qe.byteArray, out _responseLength);
                                QueueMessage(StructQueue.QRecordType.MT_MessageArrived);
                            }
                        } while (_numberOfQueuedMessagesAwaitingReception > 0);
                    }
                }
            }
        }

        private static void QueueMessage(StructQueue.QRecordType eventType)
        {
            _qe.timeStamp = DateTime.Now;
            _qe.state = Program.CPFState;
            _qe.qRecordType = eventType;
            _qe.errorCode = _terminatedStatus + A3LA.MT_Offset;
            lock (_iridiumLock) { Program.sQueue.Enqueue(_qe); }  
        }

        public static void PowerOff()
        {
        }

    }
}
