/*
 *
 *   Copyright (C) 2016 Texas Instruments Incorporated
 *
 *   All rights reserved. Property of Texas Instruments Incorporated.
 *   Restricted rights to use, duplicate or disclose this code are
 *   granted through contract.
 *
 *   The program may not be used without the written permission of
 *   Texas Instruments Incorporated or against the terms and conditions
 *   stipulated in the agreement under which this program has been supplied,
 *   and under no circumstances can it be used with non-TI connectivity device.
 *
 */
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Runtime.InteropServices;

namespace RadioToolCLI
{
    class Program
    {
        //Testing tasks on target device
        private class TestingType
        {
            public Boolean TestingMac;
            public Boolean TestingFirmware;
            public Boolean TestingTX;
            public Boolean TestingRX;
            public Boolean TestingDUPLICATED;

            public TestingType()
            {
                TestingMac = TestingFirmware = TestingTX = TestingRX = TestingDUPLICATED = false;
            }
        };

        const SByte MIN_TARGET = 0;
        const SByte MAX_TARGET = 2;

        const UInt32 MIN_COM_PORT = 1;
        const UInt32 MAX_COM_PORT = 255;
        const UInt32 MIN_BAUD_RATE = 1;

        const UInt32 MAX_BAUD_RATE = Int32.MaxValue;
        const UInt32 DEFAULT_BAUD_RATE = 115200;

        const UInt32 MIN_DURATION = 1;
        const UInt32 MAX_DURATION = 65535;

        const UInt32 MIN_CHANNEL = (UInt32)Channel_e.CHANNEL_1;
        const UInt32 MAX_CHANNEL = (UInt32)Channel_e.CHANNEL_13;
        const UInt32 DEFAULT_CHANNEL = (UInt32)Channel_e.CHANNEL_1;

        // TX fields
        const RadioTxMode_e MIN_TX_TYPE = RadioTxMode_e.RADIO_TX_CONTINUOUS;
        const RadioTxMode_e MAX_TX_TYPE = RadioTxMode_e.RADIO_TX_CW;
        const RadioTxMode_e DEFAULT_TX_TYPE = RadioTxMode_e.RADIO_TX_CONTINUOUS;

        const SByte MIN_POWER = 0;
        const SByte MAX_POWER = 15;
        const SByte DEFAULT_POWER = 0;

        const SByte MIN_TONE = -25;
        const SByte MAX_TONE = 25;
        const SByte DEFAULT_TONE = 0;

        const RateIndex_e MIN_MODULATION = RateIndex_e.RATE_1M;
        const RateIndex_e MAX_MODULATION = RateIndex_e.RATE_MCS_7;
        const RateIndex_e DEFAULT_MODULATION = RateIndex_e.RATE_1M;

        const Byte MIN_PREAMBLE = (Byte)RadioPreamble_e.LONG_PREAMBLE_MODE;
        const Byte MAX_PREAMBLE = (Byte)RadioPreamble_e.SHORT_PREAMBLE_MODE;
        const Byte DEFAULT_PREAMBLE = (Byte)RadioPreamble_e.LONG_PREAMBLE_MODE;

        const RadioDataPattern_e MIN_PATTERN = RadioDataPattern_e.ALL_0_PATTERN;
        const RadioDataPattern_e MAX_PATTERN = RadioDataPattern_e.DECREMENTAL_PATTERN;
        const RadioDataPattern_e DEFAULT_PATTERN = RadioDataPattern_e.ALL_0_PATTERN;

        const UInt16 MIN_PACKET_SIZE = 24;
        const UInt16 MAX_PACKET_SIZE = 1400;
        const UInt16 DEFAULT_PACKET_SIZE = 1400;

        const UInt32 MIN_DELAY = 100;
        const UInt32 MAX_DELAY = 1000000;
        const UInt32 DEFAULT_DELAY = 100;        

        const UInt32 MIN_AMOUNT = 0;
        const UInt32 MAX_AMOUNT = 1000000;
        const UInt32 DEFAULT_AMOUNT = 0;

        const Boolean DEFAULT_OVERRIDE_CCA = false;

        const RadioCCAThreshold_e MIN_CCA_THRESHOLD = RadioCCAThreshold_e.SL_TX_INHIBIT_THRESHOLD_MIN;
        const RadioCCAThreshold_e MAX_CCA_THRESHOLD = RadioCCAThreshold_e.SL_TX_INHIBIT_THRESHOLD_MAX;
        const RadioCCAThreshold_e DEFAULT_CCA_THRESHOLD = RadioCCAThreshold_e.SL_TX_INHIBIT_THRESHOLD_DEFAULT;

        const Boolean DEFAULT_ENABLE_ACKS = false;

        const String DEFAULT_MAC_ADDRESS = "0123456789AB";

        // RX fields
        const UInt32 MIN_REPORT_PERIOD = 0;
        const UInt32 MAX_REPORT_PERIOD = 65535;
        const UInt32 DEFAULT_REPORT_PERIOD = 0;

        /// <summary>
        /// Verbosity level.
        /// </summary>
        static int verbosity;

        static Boolean report_percent;

        // TX Thread
        static BackgroundWorker RadioToolThread;

        static AutoResetEvent doneEvent;

        public static void Main(string[] args)
        {
            verbosity = 0;

            //Basic
            bool show_help = false;
            bool show_info = false;

            //General information default values
            SByte mTarget = -1;
            UInt32 mPort = 0;
            UInt32 mBaud = DEFAULT_BAUD_RATE;
            TestingType mTestingType = new TestingType();

            //Common default values
            UInt32 duration = 1;
            UInt32 channel = DEFAULT_CHANNEL;

            //TX testing default values
            RadioTxMode_e tx_type = DEFAULT_TX_TYPE;
            SByte power = DEFAULT_POWER;
            SByte tone = DEFAULT_TONE;
            RateIndex_e modulation = DEFAULT_MODULATION;
            Byte preamble = DEFAULT_PREAMBLE;
            RadioDataPattern_e pattern = DEFAULT_PATTERN;
            UInt16 packet_size = DEFAULT_PACKET_SIZE;
            UInt32 delay = DEFAULT_DELAY;
            UInt32 amount = DEFAULT_AMOUNT;
            Boolean cca_override = DEFAULT_OVERRIDE_CCA;
            RadioCCAThreshold_e cca_threshold = DEFAULT_CCA_THRESHOLD;
            Boolean enable_acks = DEFAULT_ENABLE_ACKS;
            String dest_mac = DEFAULT_MAC_ADDRESS;
            

            //RX testing default values
            Int32 report_period = 0;
            report_percent = false;

            var p = new Mono.Options.OptionSet() {
                #region Create Options
                /*===================================================================*/
                {"Miscellaneous:"},
                { "h|help",
                  "Shows this message", 
                  v => show_help = (v != null) },
                { "i|info",
                  "Shows RadioToolCLI and radio tool library version and information.", 
                  v => show_info = (v != null) },
                { "v",
                  "increase debug message verbosity",
                  v => { if (v != null) ++verbosity; } },

                {"\nDevice Connection:"},
                { "X|target=",
                  "The {TARGET} testing device. 0:CC3120 SPI, 1:CC3120 UART, 2:CC3220 UART. REQUIRED. Range: [0, 2]. Default: 1",
                  (SByte v) => mTarget = v },
                { "P|port=",
                  "The port number of the target testing platform.\n"
                  + "Required for UART connections. Range: [0, 255].",
                  (UInt32 v) => mPort = v },
                { "B|baud_rate=",
                  "The {BAUD RATE} of the target COM port.\n"
                  + "Required for UART connections. Range: [0, Int32.MaxValue].",
                  (UInt32 v) => mBaud = v },
                { "M|mac",
                  "Returns the MAC address",
                  v => mTestingType.TestingMac = (v != null) },
                { "F|firmware",
                  "Returns the firmware version and CC3220 application version (if applicable) in the following order:\n"
                  + "Chip ID, ROM version, Firmware version, Host Driver version, CC3220 App version, MAC address.",
                  v => mTestingType.TestingFirmware = (v != null) },

                {"\nCommon for TX and RX:"},
                { "t|duration=",
                  "RX/TX Testing {DURATION} in seconds for -T and -R options. Range: [1, 65535]. Default: 1",
                  (UInt32 v) => duration = v
                },
                { "c|channel=",
                  "RX/TX Testing {CHANNEL}  for -T and -R options. Range: [1, 13]. Default: 1",
                  (UInt32 v) => channel = v
                },

                {"\nTX (Transmission):"},
                { "T|tx",
                  "TX testing for Continuous or Packetized.",
                  v => {
                      if (mTestingType.TestingTX)
                      {
                          mTestingType.TestingDUPLICATED = true;
                      }
                      mTestingType.TestingTX = true;
                  }
                },
                { "z|tx_type=",
                  "TX Testing {TX_TYPE}. 1:Continuous, 2:Packetized, 3:CW. 'tone_offset' option can be used if and only if CW is chosen. Range: [1, 3]. Default: 1.",
                  (RadioTxMode_e v) => tx_type = v
                },
                { "w|power=",
                  "TX {POWER} attenuation for Continuous and Packetized testing. 0 being the maximum power and 15 being the minimum power.Range: [0, 15]. Default: 0",
                  (SByte v) => power = v
                },
                { "f|tone_offset=",
                  "TX {TONE_OFFSET} for CW testing only. A value of N means tone at offset N*312.5kHz.\nRange: [-25, 25]. Default: 0",
                  (SByte v) => tone = v
                },
                { "m|rate=|modulation=",
                  "TX Testing rate (with the corresponding {MODULATION}). Range: [1, 20]. Default: 1 (1Mbps DSSS).\n"
                  + "1: 1 Mbps (DSSS)\n"
                  + "2: 2 Mbps (DSSS)\n"
                  + "3: 5.5 Mbps (CCK)\n"
                  + "4: 11 Mbps (CCK)\n"
                  + "5: NOT SUPPORTED\n"
                  + "6: 6 Mbps (OFDM)\n"
                  + "7: 9 Mbps (OFDM)\n"
                  + "8: 12 Mbps (OFDM)\n"
                  + "9: 18 Mbps (OFDM)\n"
                  + "10: 24 Mbps (OFDM)\n"
                  + "11: 36 Mbps (OFDM)\n"
                  + "12: 48 Mbps (OFDM)\n"
                  + "13: 54 Mbps (OFDM)\n"
                  + "14: MCS 0\n"
                  + "15: MCS 1\n"
                  + "16: MCS 2\n"
                  + "17: MCS 3\n"
                  + "18: MCS 4\n"
                  + "19: MCS 5\n"
                  + "20: MCS 6\n"
                  + "21: MCS 7\n",
                  (RateIndex_e v) => modulation = v
                },
                { "e|preamble=",
                  "TX preamble for 802.11 rates ONLY. Long:0, Short:1. Default: Long",
                  (Byte v) => preamble = v },
                { "r|pattern=",
                  "TX data pattern. See the following for the complete list. Default: 0 (All 0)\n"
                  + "0: All 0\n"
                  + "1: All 1\n"
                  + "2: Incremental\n"
                  + "3: Decremental\n",
                  (RadioDataPattern_e v) => pattern = v },
                { "l|packet_size=",
                  "TX Testing packet {SIZE}. Range: [24, 1400]. Default: 1400\n",
                  (UInt16 v) => packet_size = v
                },
                { "g|delay=",
                  "TX delay in between packets in milliseconds. Packetized TX only. Range:[100, 1,000,000]. Default: 100",
                  (UInt32 v) => delay = v },
                { "n|amount=",
                  "TX maximum number of packets. Continuous & Packetized only. Range:[0, 1,000,000]. 0 for infinite amount. Default: 0",
                  (UInt32 v) => amount = v },
                { "o|cca_override",
                  "TX CCA override enable. Default: non-overriding.",
                  v => cca_override = (v != null) },
                { "s|cca_threshold=",
                  "TX CCA threshold value. The channel is considered as occupied when signal strength is above this setting. Default: 3 (MED, -68dBm)\n"
                  + "0: MIN (-88dBm)"
                  + "1: LOW (-78dBm)"
                  + "2: DEFAULT (-68dBm)"
                  + "3: MED (-58dBm)"
                  + "4: HIGH (-48dBm)"
                  + "5: MAX (-38dBm)",
                  v => cca_override = (v != null) },
                { "a|dest_mac=",
                  "TX destination {MAC} address WITHOUT colons. For example, if the MAC address is 01:23:45:67:89:AB, enter 0123456789AB instead. Case insensitive. Default: 01:23:45:67:89:AB",
                  (String v) => dest_mac = v },
                { "k|enable_acks",
                  "TX ACKs enabling. Default: disabled.",
                  v => cca_override = (v != null) },
                {"\nRX (Reception):"},
                { "R|rx",
                  "RX testing and retrieves statistics. The statistics will be shown as:\n"
                  + "1st line: <# valid packets>, <# FCS error packets>, <# address mismatch packets>\n"
                  + "2nd line: <Average RSSI in management frame>, <Average RSSI in other frames>\n"
                  + "3rd line: RSSI historgram. <greater than -48dBm>, <-48dBm to -55dBm>, <-56dBm to -63dBm>, <-64dBm to -71dBm>, <-72dBm to -79dBm>, <less than -79dBm>\n"
                  + "4th line: Rate historgram, lowest to highest. The list order is identical to the rate/modulation option.\n"
                  + "5th line: Timestamps in micro-seconds: Starting time, Stats collecting time, Elapse",
                  v => {
                      if (mTestingType.TestingRX)
                      {
                          mTestingType.TestingDUPLICATED = true;
                      }
                      mTestingType.TestingRX = true;
                  }
                },
                { "d|report_period=",
                  "RX statistics reporting period, every {N} seconds. If set to 0, RX statistics will only report at the end.\n"
                  + "Range: [0, 65535]. Default: 0",
                  (int v) => report_period = v 
                },
                { "p|report_percent",
                  "Report RX histogram in percentage format. If not set, default is in amount of packets.",
                  v => report_percent = (v != null) 
                },
                /*===================================================================*/
                #endregion /* Create Options */
            };

            #region Argument validity checking
            /*===================================================================*/
            //List of arguments
            List<string> extra;
            try
            {
                extra = p.Parse(args);
            }
            catch (Mono.Options.OptionException e)
            {
                Console.Write("ERROR: ");
                Console.WriteLine(e.Message);
                Console.WriteLine("Try `RadioToolCLI --help' for more information.");
                return;
            }

            if (extra.Count > 0) //Invalid arguments
            {
                if (extra.Count > 1)
                    Console.Write("ERROR: Unknown arguments");
                else
                    Console.Write("ERROR: Unknown argument");

                bool has_more_char = false;
                foreach (string argument in extra)
                {
                    if (has_more_char)
                        Console.Write(", ");

                    Console.Write(argument);
                    has_more_char = true;
                }
                Console.WriteLine(".");
                Console.WriteLine("Try `RadioToolCLI --help' for more information.");
                return;
            }
            /*===================================================================*/
            #endregion /* Argument validity checking */

            //Help menu
            if (show_help)
            {
                ShowHelp(p);
                return;
            }

            //RadioTool Information
            if (show_info)
            {
                ShowInfo();
                return;
            }

            //================================ Starts arguments range checking ===========================

            //Testing types checks
            if (mTestingType.TestingTX && mTestingType.TestingRX)
            {
                Console.WriteLine("ERROR: Conflicted TX and RX testing type selected. Testing abort.");
                return;
            }
            else if (mTestingType.TestingDUPLICATED)
            {
                Console.WriteLine("ERROR: Duplicated TX or RX testing type selected. Testing abort.");
                return;
            }
            else if (!mTestingType.TestingTX
                && !mTestingType.TestingRX
                && !mTestingType.TestingFirmware
                && !mTestingType.TestingMac)
            {
                //No option selected, throws error
                Console.WriteLine("ERROR: No testing type selected. Testing abort.");
                return;
            }

            #region DLL checking
            /*===================================================================*/
            //Checking target device and DLL file
            DLLInfo info = GlobalReference.CheckDLLFile();
            Boolean DllError = false;
            String DllErrorMessage = "";
            switch (mTarget)
            {
                case 0:
                    GlobalReference.MyCurrentDevice.product = Product.CC3120SPI;
                    if (!info.Lib3120SPIExist)
                    {
                        DllError = true;
                        DllErrorMessage = "ERROR: Cannot find CC3120LibSPI.dll";
                    }
                    break;
                case 1:
                    GlobalReference.MyCurrentDevice.product = Product.CC3120UART;
                    if (!info.Lib3120UARTExist)
                    {
                        DllError = true;
                        DllErrorMessage = "ERROR: Cannot find CC3120LibUART.dll";
                    }
                    break;
                case 2:
                    GlobalReference.MyCurrentDevice.product = Product.CC3220;
                    if (!info.Lib3220Exist)
                    {
                        DllError = true;
                        DllErrorMessage = "ERROR: Cannot find CC3220Lib.dll";
                    }
                    break;
                default: //Target device out of range
                    DllError = true;
                    DllErrorMessage = "ERROR: Invalid target device selection.\nTry `RadioToolCLI --help' for more information.";
                    break;
            }
            if (DllError)
            {
                Console.WriteLine(DllErrorMessage);
                return;
            }
            /*===================================================================*/
            #endregion /* DLL checking */

            //================================ Device Connection ===========================
            //No need to check COM port or baud rate if using CC3120 SPI connection.
            if (mTarget != (SByte)Product.CC3120SPI)
            {
                //Check connection
                try{
                    CheckNumberField(mPort, "port", MIN_COM_PORT, MAX_COM_PORT);
                    CheckNumberField(mBaud, "baud rate", MIN_BAUD_RATE, MAX_BAUD_RATE);
                } catch (Exception e){
                    Console.WriteLine(e.Message);
                    return;
                }
            }

            //Connect to device
            GlobalReference.MyCurrentDevice.port = (Int32) mPort;
            GlobalReference.MyCurrentDevice.baud = (Int32) mBaud;

            if (verbosity > 0)
                Console.WriteLine("Connecting...");

            Int32 RetConnect = GlobalReference.ConnectDevice();
            if (RetConnect != 0)
            {
                GlobalReference.DisconnectDevice();
                Console.WriteLine("ERROR: Cannot connect to device. Please check the port number and your connection.");
                return;
            }

            if(verbosity > 0)
                Console.WriteLine("Connected");

            //Actual connection tasks
            if (mTestingType.TestingFirmware)
            {
                GlobalReference.getDeviceVersion();
                if (verbosity > 0)
                {
                    String indent = "";
                    if (mTarget == (SByte)Product.CC3220)
                        indent = "       ";

                    Console.WriteLine("Chip ID: {6}{0}\nROM Ver: {6}{1}\nFW Ver:  {6}{2}.{3}.{4}\nHost Driver Ver:{5}",
                        GlobalReference.MyCurrentDevice.ChipId,
                        GlobalReference.MyCurrentDevice.RomVer,
                        GlobalReference.MyCurrentDevice.NwpVer,
                        GlobalReference.MyCurrentDevice.FwVer,
                        GlobalReference.MyCurrentDevice.PhyVer,
                        GlobalReference.MyCurrentDevice.HostDriverVer,
                        indent);
                }
                else
                {
                    Console.WriteLine("{0}\n{1}\n{2}.{3}.{4}\n{5}",
                        GlobalReference.MyCurrentDevice.ChipId,
                        GlobalReference.MyCurrentDevice.RomVer,
                        GlobalReference.MyCurrentDevice.NwpVer,
                        GlobalReference.MyCurrentDevice.FwVer,
                        GlobalReference.MyCurrentDevice.PhyVer,
                        GlobalReference.MyCurrentDevice.HostDriverVer);
                }

                if (mTarget == (SByte)Product.CC3220)
                {
                    if (verbosity > 0)
                        Console.Write("CC3220 App Ver: ");
                    Console.WriteLine(GlobalReference.MyCurrentDevice.CC3220AppVer);
                }

                //Disable MAC address
                mTestingType.TestingMac = true;
            }

            if (mTestingType.TestingMac)
            {
                if (verbosity > 0)
                    Console.Write("MAC Address: \t");

                Console.WriteLine(GlobalReference.getMacAddress());
            }

            //================================ TX/RX Checing ===========================

            Channel_e       argChannel = (Channel_e) channel;
            RadioTxMode_e   argTxMode = (RadioTxMode_e) tx_type;
            SByte           argPowerOrTone = 0;
            RateIndex_e     argRateIndex = (RateIndex_e) modulation;
            RadioPreamble_e argPreamble = (RadioPreamble_e) preamble;
            RadioDataPattern_e argPattern = (RadioDataPattern_e) pattern;
            //size already defined in primitive
            UInt32          argDelay = 0;
            UInt32          argAmount = 0;
            Byte            argOverrideCCA = (Byte)(cca_override? 0x01:0x00);
            Byte            argEnableACKs = (Byte)(enable_acks ? 0x01:0x00);
            Byte[]          argByteMAC = new Byte[6];

            try //Any field check fails will throw exception with messages. Program prints these messages, performs clean-up, and exits.
            {
                CheckNumberField(duration, "duration", MIN_DURATION, MAX_DURATION);
                CheckNumberField(channel, "channel", MIN_CHANNEL, MAX_CHANNEL);

                if (mTestingType.TestingTX)
                {
                    CheckNumberField((Int32)tx_type, "tx_type", (Int32)MIN_TX_TYPE, (Int32)MAX_TX_TYPE);
                    CheckNumberField((Int32)modulation, "modulation", (Int32)MIN_MODULATION, (Int32)MAX_MODULATION);

                    if (tx_type == RadioTxMode_e.RADIO_TX_CW) //CW testing type, use tone range
                    {
                        CheckNumberField(tone, "tone_offset", MIN_TONE, MAX_TONE);
                        argPowerOrTone = tone;
                    }
                    else //Contimuous or Packetized testing type, use power range
                    {
                        CheckNumberField(power, "power", MIN_POWER, MAX_POWER);
                        argPowerOrTone = power;
                    }

                    CheckNumberField(packet_size, "packet_size", MIN_PACKET_SIZE, MAX_PACKET_SIZE);
                    CheckNumberField(preamble, "preamble", MIN_PREAMBLE, MAX_PREAMBLE);
                    CheckNumberField((Int32)pattern, "pattern", (Int32) MIN_PATTERN, (Int32) MAX_PATTERN);
                    CheckNumberField(amount, "amount", MIN_AMOUNT, MAX_AMOUNT);
                    CheckNumberField(delay, "delay", MIN_DELAY, MAX_DELAY);

                    //Check MAC address
                    Regex r = new Regex("^[a-fA-F0-9]*$");
                    if (!r.IsMatch(dest_mac) || dest_mac.Length != 12)
                    {
                        //MAC Address incorrect
                        throw new Exception("ERROR: Incorrect MAC address format");
                    }
                    argByteMAC = GlobalReference.StringToByteArray(dest_mac);
                }
                else if (mTestingType.TestingRX)
                {
                    //Empty
                }

                //================================ TX/RX Execution ===========================
                if (mTestingType.TestingTX)
                {
                    TxArgPack pack = new TxArgPack(
                        argTxMode,
                        argPowerOrTone,
                        argChannel,
                        argRateIndex,
                        argPreamble,
                        argPattern,
                        packet_size,
                        argDelay,
                        argAmount,
                        argOverrideCCA,
                        cca_threshold,
                        argEnableACKs,
                        argByteMAC);

                    doneEvent = new AutoResetEvent(false);
                    RadioToolThread = new BackgroundWorker();
                    RadioToolThread.WorkerReportsProgress = true;
                    RadioToolThread.WorkerSupportsCancellation = true;
                    RadioToolThread.DoWork += new DoWorkEventHandler(t_Tx_DoWork);
                    RadioToolThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(t_Tx_RunWorkerCompleted);

                    if (verbosity > 0)
                        Console.WriteLine("TX Testing Started. The program will last " + duration + " second(s) as specified in the duration field.");

                    //Thread execution: Start TX
                    RadioToolThread.RunWorkerAsync(pack);
                    
                    Thread.Sleep((Int32) duration * 1000);

                    //Stop TX
                    GlobalReference.StopTX(argTxMode);
                    doneEvent.WaitOne();

                    Console.WriteLine("TX Finished: SUCCESS");
                }
                else if (mTestingType.TestingRX)
                {
                    if (verbosity > 0)
                        Console.WriteLine("RX Testing Started");

                    Int32 rx_ret = GlobalReference.StartRX(argChannel);
                    if (rx_ret != 0)
                        throw new Exception("ERROR: Cannot start RX. Code: " + rx_ret);

                    //Timer for reporting interval
                    System.Timers.Timer myRxTimer = null;

                    if (report_period != 0)
                    {
                        if (verbosity > 0)
                            Console.WriteLine("RX Testing Started. The program will last " + duration + " second(s) and report every " + report_period + " second(s).");
                        
                        myRxTimer = new System.Timers.Timer(report_period * 1000);
                        myRxTimer.Elapsed += OnRxTickEvent;
                        myRxTimer.Start();

                        Thread.Sleep((Int32) duration * 1000);
                    }
                    else
                    {
                        if (verbosity > 0)
                            Console.WriteLine("RX Testing Started. The program will last " + duration + " second(s) and report after time's up.");

                        // If reporting period is 0, only report at the end of duration.
                        Thread.Sleep((Int32) duration * 1000);

                        //Final data gathering
                        reportRx();
                    }

                    if (report_period != 0)
                    {
                        Thread.Sleep(500); //Extra sleep to gather the last data
                        myRxTimer.Stop();
                        Thread.Sleep(500); //Extra sleep to let timer finish
                    }

                    GlobalReference.StopRX();

                    Console.WriteLine("RX Finished: SUCCESS");
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            //=================== cleanup, ends connection ===================
            GlobalReference.DisconnectDevice();

            //Program ends
            return;
        }

        static void ShowHelp(Mono.Options.OptionSet p)
        {
            Console.WriteLine("Usage: RadioToolCLI -X [-P=port] [-B=baud] [-M] [-F] [-T|-R]");
            Console.WriteLine("       RadioToolCLI [-h|--help] [-i|--info]");
            Console.WriteLine();
            Console.WriteLine("Options:");
            p.WriteOptionDescriptions(Console.Out, Console.WindowWidth-1);
            Console.WriteLine();
            Console.WriteLine("Please contact TI for any question with regards to this program.");
        }

        private static void ShowInfo()
        {
            DLLInfo info = GlobalReference.CheckDLLFile();
            Console.WriteLine("RadioToolCLI  version:\t" + GlobalReference.GetAssemblyVersion());
            Console.WriteLine("CC3120LibSPI  version:\t" + info.Lib3120SPIVersion);
            Console.WriteLine("CC3120LibUART version:\t" + info.Lib3120UARTVersion);
            Console.WriteLine("CC3220Lib     version:\t" + info.Lib3220Version);
        }

        /// <summary>
        /// Singed types checking
        /// </summary>
        /// <param name="field"></param>
        /// <param name="name"></param>
        /// <param name="min"></param>
        /// <param name="max"></param>
        /// <returns>true if successful. An Error with message will be thrown if error occurs.</returns>
        private static Boolean CheckNumberField(int field, String name, int min, int max)
        {
            if (field >= min && field <= max)
                return true;

            throw new Exception("ERROR: " + name + " argument is not set or out of range.\nTry `RadioToolCLI --help' for more information.");
        }

        /// <summary>
        /// Unsinged types checking
        /// </summary>
        /// <param name="field"></param>
        /// <param name="name"></param>
        /// <param name="min"></param>
        /// <param name="max"></param>
        /// <returns>true if successful. An Error with message will be thrown if error occurs.</returns>
        private static Boolean CheckNumberField(uint field, String name, uint min, uint max)
        {
            if (field >= min && field <= max)
                return false;

            throw new Exception("ERROR: " + name + " argument is not set or out of range.\nTry `RadioToolCLI --help' for more information.");
        }

        private static void t_Tx_DoWork(object sender, DoWorkEventArgs e)
        {
            e.Result = GlobalReference.StartTX((TxArgPack)e.Argument);
        }

        private static void t_Tx_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            Int32 workerResult = (Int32)e.Result;
            if (workerResult != 0)
            {
                //An error occurred in the StartTx, notify user and reset everthing.
                String error = String.Format("ERROR: Cannot start TX. Code: {0}", workerResult);
                Console.WriteLine(error);
            }

            //Notify the main thread that this thread is complete
            doneEvent.Set();
        }

        private static void OnRxTickEvent(Object source, System.Timers.ElapsedEventArgs e)
        {
            reportRx();
        }

        private static void reportRx()
        {
            RxStopResult resp = new RxStopResult();
            resp.errorCode = GlobalReference.GetStats(resp.RxStats);

            Console.WriteLine("{0}, {1}, {2}", resp.RxStats.ReceivedValidPacketsNumber, resp.RxStats.ReceivedFcsErrorPacketsNumber, resp.RxStats.ReceivedAddressMismatchPacketsNumber);
            Console.WriteLine("{0}, {1}", resp.RxStats.AvarageMgMntRssi, resp.RxStats.AvarageDataCtrlRssi);

            if (report_percent)
            {
                //A pre-defined denominator in case NumberPacketsValid is zero.
                UInt32 denominator = 1;

                //Extra step to consider if denominator is 0.
                if (resp.RxStats.ReceivedValidPacketsNumber != 0)
                {
                    //Valid number of packets, use this a denominator
                    denominator = resp.RxStats.ReceivedValidPacketsNumber;
                }

                //RSSI
                Console.Write((((float)resp.RxStats.RssiHistogram[0] / denominator) * 100).ToString("n2"));
                for (int i = 0; i < resp.RxStats.RssiHistogram.Length; i++)
                {
                    Console.Write(", " + (((float)resp.RxStats.RssiHistogram[i] / denominator) * 100).ToString("n2"));
                }
                Console.WriteLine();

                //Rate
                Console.Write((((float)resp.RxStats.RateHistogram[0] / denominator) * 100).ToString("n2"));
                for (int i = 0; i < resp.RxStats.RateHistogram.Length; i++)
                {
                    Console.Write(", " + (((float)resp.RxStats.RateHistogram[i] / denominator) * 100).ToString("n2"));
                }
                Console.WriteLine();
            }
            else
            {
                //RSSI
                Console.Write(resp.RxStats.RssiHistogram[0]);
                for (int i = 0; i < resp.RxStats.RssiHistogram.Length; i++)
                {
                    Console.Write(", " + resp.RxStats.RssiHistogram[i]);
                }
                Console.WriteLine();

                //Rate
                Console.Write(resp.RxStats.RateHistogram[0]);
                for (int i = 0; i < resp.RxStats.RateHistogram.Length; i++)
                {
                    Console.Write(", " + resp.RxStats.RateHistogram[i]);
                }
                Console.WriteLine();
            }

            Console.WriteLine("{0}, {1}, {2}", resp.RxStats.StartTimeStamp, resp.RxStats.GetTimeStamp, resp.RxStats.GetTimeStamp - resp.RxStats.StartTimeStamp);
        }
    }
}
