// *** SendandReceiveBinaryArguments ***

// This example expands the previous SendandReceiveArguments example. The PC will 
//  send and receive multiple Binary values, demonstrating that this is more compact and faster. Since the output is not human readable any more, 
//  the logging is disabled and the NewLines are removed
//
// It adds a demonstration of how to:
// - Receive multiple binary parameters,
// - Send multiple binary parameters
// - Callback events being handled while the main program waits
// - How to calculate milliseconds, similar to Arduino function Millis()

using System;
using System.Threading;
using CommandMessenger;
using CommandMessenger.Transport.Serial;

namespace SendAndReceiveBinaryArguments
{
    // This is the list of recognized commands. These can be commands that can either be sent or received. 
    // In order to receive, attach a callback function to these events
    enum Command
    {
        RequestPlainTextFloatSeries , // Command Request to send series in plain text
        ReceivePlainTextFloatSeries , // Command to send an item in plain text
        RequestBinaryFloatSeries    , // Command Request to send series in binary form
        ReceiveBinaryFloatSeries    , // Command to send an item in binary form
    };

    public class SendAndReceiveBinaryArguments
    {
        public bool RunLoop { get; set; }
        private SerialTransport _serialTransport;
        private CmdMessenger _cmdMessenger;
        private int _receivedItemsCount;                        // Counter of number of plain text items received
        private int _receivedBytesCount;                        // Counter of number of plain text bytes received
        long _beginTime;                                        // Start time, 1st item of sequence received 
        long _endTime;                                          // End time, last item of sequence received 
        private bool _receivePlainTextFloatSeriesFinished;      // Indicates if plain text float series has been fully received
        private bool _receiveBinaryFloatSeriesFinished;         // Indicates if binary float series has been fully received
        const int SeriesLength = 2000;                          // Number of items we like to receive from the Arduino
        private const float SeriesBase = 1111111.111111F;       // Base of values to return: SeriesBase * (0..SeriesLength-1)

        // ------------------ M A I N  ----------------------

        // Setup function
        public void Setup()
        {
            // Create Serial Port object
            _serialTransport = new SerialTransport
            {
                CurrentSerialSettings = { PortName = "COM15", BaudRate = 115200 } // object initializer
            };

            // Initialize the command messenger with the Serial Port transport layer
            // Set if it is communicating with a 16- or 32-bit Arduino board
            _cmdMessenger = new CmdMessenger(_serialTransport, BoardType.Bit16);

            // Attach the callbacks to the Command Messenger
            AttachCommandCallBacks();                

            // Start listening
            _cmdMessenger.Connect();

            _receivedItemsCount = 0;
            _receivedBytesCount = 0;

            // Clear queues 
            _cmdMessenger.ClearReceiveQueue();
            _cmdMessenger.ClearSendQueue();

            Thread.Sleep(100);

            // Send command requesting a series of 100 float values send in plain text form
            var commandPlainText = new SendCommand((int)Command.RequestPlainTextFloatSeries);
            commandPlainText.AddArgument((UInt16)SeriesLength);
            commandPlainText.AddArgument((float)SeriesBase);
            // Send command 
            _cmdMessenger.SendCommand(commandPlainText);

            // Now wait until all values have arrived
            while (!_receivePlainTextFloatSeriesFinished)
            {
                Thread.Sleep(100);
            }


            // Clear queues 
            _cmdMessenger.ClearReceiveQueue();
            _cmdMessenger.ClearSendQueue();

            _receivedItemsCount = 0;
            _receivedBytesCount = 0;
            // Send command requesting a series of 100 float values send in binary form
            var commandBinary = new SendCommand((int)Command.RequestBinaryFloatSeries);
            commandBinary.AddBinArgument((UInt16)SeriesLength);
            commandBinary.AddBinArgument((float)SeriesBase); 
            
            // Send command 
            _cmdMessenger.SendCommand(commandBinary);

            // Now wait until all values have arrived
            while (!_receiveBinaryFloatSeriesFinished)
            {
                Thread.Sleep(100);
            }
        }

        // Loop function
        public void Loop()
        {
            RunLoop = false;
        }

        // Exit function
        public void Exit()
        {
            // Stop listening
            _cmdMessenger.Disconnect();

            // Dispose Command Messenger
            _cmdMessenger.Dispose();

            // Dispose Serial Port object
            _serialTransport.Dispose();

            // Pause before stop
            Console.WriteLine("Press any key to stop...");
            Console.ReadKey();
        }

        /// Attach command call backs. 
        private void AttachCommandCallBacks()
        {
            _cmdMessenger.Attach(OnUnknownCommand);
            _cmdMessenger.Attach((int)Command.ReceivePlainTextFloatSeries, OnReceivePlainTextFloatSeries);
            _cmdMessenger.Attach((int)Command.ReceiveBinaryFloatSeries, OnReceiveBinaryFloatSeries);
        }

        // ------------------  C A L L B A C K S ---------------------

        // Called when a received command has no attached function.
        void OnUnknownCommand(ReceivedCommand arguments)
        {
            Console.WriteLine("Command without attached callback received");
        }


        // Callback function To receive the plain text float series from the Arduino
        void OnReceivePlainTextFloatSeries(ReceivedCommand arguments)
        {
            _receivedBytesCount += CountBytesInCommand(arguments, true);

            var count         = arguments.ReadInt16Arg();
            var receivedValue = arguments.ReadFloatArg();


            if (count != _receivedItemsCount)
            {
                Console.WriteLine("Values not matching: received {0} expected {1}", count, _receivedItemsCount);
            }
            if (_receivedItemsCount % (SeriesLength/10) == 0)
                Console.WriteLine("Received value: {0}", receivedValue);
            if (_receivedItemsCount == 0)
            {
                // Received first value, start stopwatch
                _beginTime = Millis;
            }
            else if (count == SeriesLength - 1)
            {
                // Received all values, stop stopwatch
                _endTime = Millis;
                var deltaTime = (_endTime - _beginTime);
                Console.WriteLine("{0} milliseconds per {1} items = is {2} ms/item, {3} Hz",
                    deltaTime, 
                    SeriesLength, 
                    (float)deltaTime / (float)SeriesLength,
                    (float)1000 * SeriesLength / (float)deltaTime
                    );
                Console.WriteLine("{0} milliseconds per {1} bytes = is {2} ms/byte,  {3} bytes/sec, {4} bps",
                    deltaTime,
                    _receivedBytesCount,
                    (float)deltaTime / (float)_receivedBytesCount,
                    (float)1000 * _receivedBytesCount / (float)deltaTime,
                    (float)8 * 1000 * _receivedBytesCount / (float)deltaTime
                    );
                _receivePlainTextFloatSeriesFinished = true;
           }            
            _receivedItemsCount++;     
        }

        private int CountBytesInCommand(CommandMessenger.Command command, bool printLfCr)
        {
            var bytes = command.CommandString().Length; // Command + command separator
            //var bytes = _cmdMessenger.CommandToString(command).Length + 1; // Command + command separator
            if (printLfCr) bytes += 2; // Add  bytes for carriage return ('\r') and /or a newline  ('\n')
            return bytes;
        }

        // Callback function To receive the binary float series from the Arduino
        void OnReceiveBinaryFloatSeries(ReceivedCommand arguments)
        {
            var count         = arguments.ReadBinUInt16Arg();
            var receivedValue = arguments.ReadBinFloatArg();

            _receivedBytesCount += CountBytesInCommand(arguments, false);

            if (count != _receivedItemsCount)
            {
                Console.WriteLine("Values not matching: received {0} expected {1}", count, _receivedItemsCount);
            }

            if (_receivedItemsCount % (SeriesLength / 10) == 0)
                Console.WriteLine("Received value: {0}", receivedValue);
            if (_receivedItemsCount == 0)
            {
                // Received first value, start stopwatch
                _beginTime = Millis;
            }
            else if (count == SeriesLength - 1)
            {
                // Received all values, stop stopwatch
                _endTime = Millis;
                var deltaTime = (_endTime - _beginTime);
                Console.WriteLine("{0} milliseconds per {1} items = is {2} ms/item, {3} Hz",
                    deltaTime,
                    SeriesLength,
                    (float)deltaTime / (float)SeriesLength,
                    (float)1000 * SeriesLength / (float)deltaTime
                    );
                Console.WriteLine("{0} milliseconds per {1} bytes = is {2} ms/byte,  {3} bytes/sec, {4} bps",
                    deltaTime,
                    _receivedBytesCount,
                    (float)deltaTime / (float)_receivedBytesCount,
                    (float)1000 * _receivedBytesCount / (float)deltaTime,
                    (float)8 * 1000 * _receivedBytesCount / (float)deltaTime
                    );
                _receiveBinaryFloatSeriesFinished = true;
            }
            _receivedItemsCount++;
        }

        // Return Milliseconds since 1970
        public static long Millis { get { return (long)((DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds); } }
    }
}
