/*
 *
 * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/
 *
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *    Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *    Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the
 *    distribution.
 *
 *    Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Linq;

namespace RadioToolGUI
{
    internal partial class RxTab : UserControl
    {
        #region Members
        /*===================================================================*/
        private frmMain m_MainForm;
        private BackgroundWorker getStatsThread;

        private Boolean m_TriggerStopRxStats = false;
        private Boolean m_RxStatsRunning = false;

        System.Windows.Forms.Timer myTimer;
        private Int32 m_TimeCounter;
        private Int32 m_duration;

        private Boolean m_DisplayRSSIPercentage;
        private Boolean m_DisplayRatePercentage;
        private UInt16[] rateAmount;
        private UInt16[] rssiAmount;
        private float[] ratePercentage;
        private float[] rssiPercentage;

        private Boolean m_StoppinRx;
        /*===================================================================*/
        #endregion /* Members */

        #region Access
        /*===================================================================*/
        internal bool TriggerStopRxStats
        {
            get { return m_TriggerStopRxStats; }
            set { m_TriggerStopRxStats = value; }
        }

        internal bool RxStatsRunning
        {
            get { return m_RxStatsRunning; }
        }
        /*===================================================================*/
        #endregion /* Access */

        #region LifeCycle
        /*===================================================================*/
        internal RxTab(frmMain f)
        {
            m_MainForm = f;
            InitializeComponent();
            iFillChannelList(0);

            m_TimeCounter = 0;
            m_duration = 0;

            rateAmount = new UInt16[20];
            rssiAmount = new UInt16[6];
            ratePercentage = new float[20];
            rssiPercentage = new float[6];

            //Initialize values
            iDefaultValues();

            m_StoppinRx = false;

            //General tooltips
            ToolTip tt = new ToolTip();
            tt.InitialDelay = 0;
            tt.SetToolTip(m_nudDuration, "0 for inifinite time. You'll need to Stop RX manually");

            //Selet Percentage initially
            m_rdoRSSIPercentage.Checked = true;
            m_rdoRatePercentage.Checked = true;
            m_DisplayRSSIPercentage = true;
            m_DisplayRatePercentage = true;
        }
        /*===================================================================*/
        #endregion /* LifeCycle */

        #region Operations
        /*===================================================================*/
        private void iFillChannelList(Int32 default_selection)
        {
            m_cboChannel.Items.Add(new ComboboxItem("1 (2412MHz)", Channel_e.CHANNEL_1));
            m_cboChannel.Items.Add(new ComboboxItem("2 (2417MHz)", Channel_e.CHANNEL_2));
            m_cboChannel.Items.Add(new ComboboxItem("3 (2422MHz)", Channel_e.CHANNEL_3));
            m_cboChannel.Items.Add(new ComboboxItem("4 (2427MHz)", Channel_e.CHANNEL_4));
            m_cboChannel.Items.Add(new ComboboxItem("5 (2432MHz)", Channel_e.CHANNEL_5));
            m_cboChannel.Items.Add(new ComboboxItem("6 (2437MHz)", Channel_e.CHANNEL_6));
            m_cboChannel.Items.Add(new ComboboxItem("7 (2442MHz)", Channel_e.CHANNEL_7));
            m_cboChannel.Items.Add(new ComboboxItem("8 (2447MHz)", Channel_e.CHANNEL_8));
            m_cboChannel.Items.Add(new ComboboxItem("9 (2452MHz)", Channel_e.CHANNEL_9));
            m_cboChannel.Items.Add(new ComboboxItem("10 (2457MHz)", Channel_e.CHANNEL_10));
            m_cboChannel.Items.Add(new ComboboxItem("11 (2462MHz)", Channel_e.CHANNEL_11));
            m_cboChannel.Items.Add(new ComboboxItem("12 (2467MHz)", Channel_e.CHANNEL_12));
            m_cboChannel.Items.Add(new ComboboxItem("13 (2472MHz)", Channel_e.CHANNEL_13));
            //m_cboChannel.Items.Add(new ComboboxItem("Max Channels", Channel_e.MAX_CHANNELS));

            m_cboChannel.SelectedIndex = default_selection;
        }

        internal void EnableControls(bool en)
        {
            m_btnStartRx.Enabled = en;
            m_btnResetStatistics.Enabled = en;
        }

        private void t_Rx_DoWork(object sender, DoWorkEventArgs e)
        {
            RxDoWorkArgs args = (RxDoWorkArgs) e.Argument;
            e.Result = GlobalReference.StartRX(args.channel);
        }

        private void t_Rx_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            Int32 workerResult = (Int32)e.Result;

            if (workerResult != 0) //An error occurred. Shows error message.
            {
                MessageBox.Show(String.Format("Error Code ({0})", workerResult),
                    "Rx Testing Error",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error,
                    MessageBoxDefaultButton.Button1);

                if (myTimer != null)
                {
                    if (myTimer.Enabled)
                    {
                        myTimer.Stop();
                        myTimer.Enabled = false;
                    }
                }
                iFinalCleanUp();
            }
            else
            {
                m_btnStartRx.Enabled = true;
                m_btnGetStats.Enabled = true;
            }
        }

        private void t_StopRx_DoWork(object sender, DoWorkEventArgs e)
        {
            RxStopResult resp = new RxStopResult();
            resp.errorCode = GlobalReference.GetStats(resp.RxStats);
            if (m_StoppinRx) //Only stop Rx if user requests to.
            {
                GlobalReference.StopRX();
            }  
            e.Result = resp;
        }

        private void t_StopRx_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            RxStopResult threadResult = (RxStopResult)e.Result;
            if (threadResult.errorCode == 0)
            {
                //Successfully got values. Now display values & histogram
                m_txtValidPackets.Text = threadResult.RxStats.ReceivedValidPacketsNumber.ToString();
                m_txtAddrMMPackets.Text = threadResult.RxStats.ReceivedAddressMismatchPacketsNumber.ToString();
                m_txtFCSErrorPackets.Text = threadResult.RxStats.ReceivedFcsErrorPacketsNumber.ToString();
                m_txtAvgRSSIMgMnt.Text = threadResult.RxStats.AvarageMgMntRssi.ToString();
                m_txtAvgRSSIDataCtrl.Text = threadResult.RxStats.AvarageDataCtrlRssi.ToString();
                m_txtStartingT.Text = threadResult.RxStats.StartTimeStamp.ToString();
                m_txtGetStatsT.Text = threadResult.RxStats.GetTimeStamp.ToString();
                m_txtElapseT.Text = (threadResult.RxStats.GetTimeStamp - threadResult.RxStats.StartTimeStamp).ToString();

                rateAmount = new UInt16[20];
                rssiAmount = new UInt16[6];
                ratePercentage = new float[20];
                rssiPercentage = new float[6];

                UInt32 denominator = 1;

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

                for (int i = 0; i < 20; i++ )
                {
                    rateAmount[i] = threadResult.RxStats.RateHistogram[i];
                    ratePercentage[i] = ((float)threadResult.RxStats.RateHistogram[i] / denominator) * 100;
                }

                for (int i = 0; i < 6; i++)
                {
                    rssiAmount[i] = threadResult.RxStats.RssiHistogram[i];
                    rssiPercentage[i] = ((float)threadResult.RxStats.RssiHistogram[i] / denominator) * 100;
                }

                //Display RSSI values
                iDisplayRSSIAsPercentage(m_DisplayRSSIPercentage);

                //Display Rate values
                iDisplayRateAsPercentage(m_DisplayRatePercentage);
            }
            else
            {
                //Error while getting data. Show the error message.
                MessageBox.Show(
                    String.Format("Error Code ({0})", threadResult.errorCode),
                    "Rx Getting Stats Error",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error,
                    MessageBoxDefaultButton.Button1);
            }

            if (m_StoppinRx) //Only stop Rx if user requests to.
            {
                iFinalCleanUp();
            }
        }

        private void iDisplayRSSIAsPercentage(Boolean b)
        {
            if (b) //As Percentage
            {
                m_txtRSSI0.Text = rssiPercentage[0].ToString("n2");
                m_txtRSSI1.Text = rssiPercentage[1].ToString("n2");
                m_txtRSSI2.Text = rssiPercentage[2].ToString("n2");
                m_txtRSSI3.Text = rssiPercentage[3].ToString("n2");
                m_txtRSSI4.Text = rssiPercentage[4].ToString("n2");
                m_txtRSSI5.Text = rssiPercentage[5].ToString("n2");
            }
            else //As Amount
            {
                m_txtRSSI0.Text = rssiAmount[0].ToString();
                m_txtRSSI1.Text = rssiAmount[1].ToString();
                m_txtRSSI2.Text = rssiAmount[2].ToString();
                m_txtRSSI3.Text = rssiAmount[3].ToString();
                m_txtRSSI4.Text = rssiAmount[4].ToString();
                m_txtRSSI5.Text = rssiAmount[5].ToString();
            }
        }

        private void iDisplayRateAsPercentage(Boolean b)
        {
            if (b) //As Percentage
            {
                m_txt1M.Text = ratePercentage[0].ToString("n2");
                m_txt2M.Text = ratePercentage[1].ToString("n2");
                m_txt5_5M.Text = ratePercentage[2].ToString("n2");
                m_txt11M.Text = ratePercentage[3].ToString("n2");
                m_txt6M.Text = ratePercentage[4].ToString("n2");
                m_txt9M.Text = ratePercentage[5].ToString("n2");
                m_txt12M.Text = ratePercentage[6].ToString("n2");
                m_txt18M.Text = ratePercentage[7].ToString("n2");
                m_txt24M.Text = ratePercentage[8].ToString("n2");
                m_txt36M.Text = ratePercentage[9].ToString("n2");
                m_txt48M.Text = ratePercentage[10].ToString("n2");
                m_txt54M.Text = ratePercentage[11].ToString("n2");
                m_txtMCS0.Text = ratePercentage[12].ToString("n2");
                m_txtMCS1.Text = ratePercentage[13].ToString("n2");
                m_txtMCS2.Text = ratePercentage[14].ToString("n2");
                m_txtMCS3.Text = ratePercentage[15].ToString("n2");
                m_txtMCS4.Text = ratePercentage[16].ToString("n2");
                m_txtMCS5.Text = ratePercentage[17].ToString("n2");
                m_txtMCS6.Text = ratePercentage[18].ToString("n2");
                m_txtMCS7.Text = ratePercentage[19].ToString("n2");
            }
            else //As Amount
            {
                m_txt1M.Text = rateAmount[0].ToString();
                m_txt2M.Text = rateAmount[1].ToString();
                m_txt5_5M.Text = rateAmount[2].ToString();
                m_txt11M.Text = rateAmount[3].ToString();
                m_txt6M.Text = rateAmount[4].ToString();
                m_txt9M.Text = rateAmount[5].ToString();
                m_txt12M.Text = rateAmount[6].ToString();
                m_txt18M.Text = rateAmount[7].ToString();
                m_txt24M.Text = rateAmount[8].ToString();
                m_txt36M.Text = rateAmount[9].ToString();
                m_txt48M.Text = rateAmount[10].ToString();
                m_txt54M.Text = rateAmount[11].ToString();
                m_txtMCS0.Text = rateAmount[12].ToString();
                m_txtMCS1.Text = rateAmount[13].ToString();
                m_txtMCS2.Text = rateAmount[14].ToString();
                m_txtMCS3.Text = rateAmount[15].ToString();
                m_txtMCS4.Text = rateAmount[16].ToString();
                m_txtMCS5.Text = rateAmount[17].ToString();
                m_txtMCS6.Text = rateAmount[18].ToString();
                m_txtMCS7.Text = rateAmount[19].ToString();
            }
        }

        /// <summary>
        /// When RX is ready to finish, call this function to get stats and Stop RX.
        /// This function is called from the following scenarios:
        /// 1. RX Testing duration was finite, and timer ends calls this function to stop RX.
        /// 2. RX Testing duration was infinite, and user presses the "Stop RX" button.
        /// 3. RX Testing encountered an error. The error message has already been handled in t_Rx_RunWorkerCompleted().
        /// </summary>
        private void iDefaultValues()
        {
            //Clear all data values
            for (int i = 0; i < ratePercentage.Length; i++)
            {
                rateAmount[i] = 0;
                ratePercentage[i] = 0;
            }
            for (int i = 0; i < rssiPercentage.Length; i++)
            {
                rssiAmount[i] = 0;
                rssiPercentage[i] = 0;
            }

            //Clear all text values
            m_txtValidPackets.Text = "0";
            m_txtAddrMMPackets.Text = "0";
            m_txtFCSErrorPackets.Text = "0";
            m_txtAvgRSSIMgMnt.Text = "0";
            m_txtAvgRSSIDataCtrl.Text = "0";
            m_txtStartingT.Text = "0";
            m_txtGetStatsT.Text = "0";
            m_txtElapseT.Text = "0";
            m_txtRSSI0.Text = "0";
            m_txtRSSI1.Text = "0";
            m_txtRSSI2.Text = "0";
            m_txtRSSI3.Text = "0";
            m_txtRSSI4.Text = "0";
            m_txtRSSI5.Text = "0";
            m_txt1M.Text = "0";
            m_txt2M.Text = "0";
            m_txt5_5M.Text = "0";
            m_txt11M.Text = "0";
            m_txt6M.Text = "0";
            m_txt9M.Text = "0";
            m_txt12M.Text = "0";
            m_txt18M.Text = "0";
            m_txt24M.Text = "0";
            m_txt36M.Text = "0";
            m_txt48M.Text = "0";
            m_txt54M.Text = "0";
            m_txtMCS0.Text = "0";
            m_txtMCS1.Text = "0";
            m_txtMCS2.Text = "0";
            m_txtMCS3.Text = "0";
            m_txtMCS4.Text = "0";
            m_txtMCS5.Text = "0";
            m_txtMCS6.Text = "0";
            m_txtMCS7.Text = "0";
        }
        
        private void iGetStatsAndStopRx()
        {
            //Prevent user from getting more statistics
            m_btnGetStats.Enabled = false;
            m_StoppinRx = true;

            frmMain.RadioToolThread = new BackgroundWorker();
            frmMain.RadioToolThread.WorkerReportsProgress = true;
            frmMain.RadioToolThread.WorkerSupportsCancellation = true;

            //Get Stats & Ends RX
            frmMain.RadioToolThread.DoWork += new DoWorkEventHandler(t_StopRx_DoWork);
            frmMain.RadioToolThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(t_StopRx_RunWorkerCompleted);
            frmMain.RadioToolThread.RunWorkerAsync();
        }

        private void iFinalCleanUp()
        {
            m_cboChannel.Enabled = true;
            m_btnStartRx.Enabled = true;
            m_btnStartRx.Text = Properties.Resources.txt_btnStartRx;
            m_lblCountDown.Text = "";

            //Enable everything else
            GlobalReference.WriteConsole("Rx Testing Finished");
            m_MainForm.updateTestingStatus(GlobalReference.TESTING_STATUS_IDLE);
        }

        /// <summary>
        /// A timer to count down user-specified duration of RX testing.
        /// </summary>
        /// <param name="myObject"></param>
        /// <param name="myEventArgs"></param>
        private void iTimerOnTick(Object myObject, EventArgs myEventArgs)
        {
            if (m_TimeCounter <= 0)
            {
                myTimer.Stop();
                myTimer.Enabled = false;
                iGetStatsAndStopRx();
            }

            m_lblCountDown.Text = String.Format("{0} ({1})", Properties.Resources.txt_btnRxRunning, m_TimeCounter.ToString());
            m_TimeCounter--;
        }
        /*===================================================================*/
        #endregion /* Operations */

        #region Event Handling
        /*===================================================================*/
        private void m_btnResetStatistics_Click(object sender, EventArgs e)
        {
            iDefaultValues();
        }

        private void m_btnStartRx_Click(object sender, EventArgs e)
        {
            if (!GlobalReference.isDeviceConnected())
            {
                MessageBox.Show("Device is not connected.", "Connection Error", MessageBoxButtons.OK, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1);
                return;
            }

            if (GlobalReference.getTestingState().Equals(GlobalReference.TESTING_STATUS_IDLE))
            {
                m_btnStartRx.Enabled = false;
                m_StoppinRx = false;

                //Device is idle, now starts RX testing.
                frmMain.RadioToolThread = new BackgroundWorker();
                frmMain.RadioToolThread.WorkerReportsProgress = true;
                frmMain.RadioToolThread.WorkerSupportsCancellation = true;
                frmMain.RadioToolThread.DoWork += new DoWorkEventHandler(t_Rx_DoWork);
                frmMain.RadioToolThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(t_Rx_RunWorkerCompleted);

                //Acquire user values
                Channel_e channel = (Channel_e)(m_cboChannel.SelectedItem as ComboboxItem).Value;
                m_duration = Decimal.ToInt32(m_nudDuration.Value);

                m_btnStartRx.Text = Properties.Resources.txt_btnStopRx;

                //Duration 0 means infinite testing time, until manually stops
                if (m_duration != 0)
                {
                    //User has specified a duration. We use timer.
                    myTimer = new System.Windows.Forms.Timer();
                    myTimer.Tick += new EventHandler(iTimerOnTick);
                    myTimer.Interval = 1000;
                    myTimer.Enabled = true;
                    m_TimeCounter = m_duration - 1; //Remove the extra second
                    myTimer.Start();
                    m_lblCountDown.Visible = true;
                    m_lblCountDown.Text = String.Format("{0} ({1})", Properties.Resources.txt_btnRxRunning, m_duration);
                }

                m_cboChannel.Enabled = false;
                    
                RxDoWorkArgs args = new RxDoWorkArgs(channel, m_duration);

                //Disable anything other than this tab.
                GlobalReference.WriteConsole("Rx Testing Started");
                m_MainForm.updateTestingStatus(GlobalReference.TESTING_STATUS_RX_RUNNING);

                frmMain.RadioToolThread.RunWorkerAsync(args);
            }
            else if (GlobalReference.getTestingState().Equals(GlobalReference.TESTING_STATUS_RX_RUNNING))
            {
                //Device is already running in Rx mode with infinite time, now stops.
                if (myTimer != null)
                {
                    if (myTimer.Enabled)
                    {
                        myTimer.Stop();
                        myTimer.Enabled = false;
                    }
                }

                iGetStatsAndStopRx();
            }
            else
            {
                MessageBox.Show("Other Tests Running: " + GlobalReference.getTestingState(), "Unknown Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
                return;
            }
        }

        private void m_btnShowRssiGraph_Click(object sender, EventArgs e)
        {
            Form frm = new frmRssiGraph(m_DisplayRSSIPercentage, rssiPercentage, rssiAmount);
            frm.StartPosition = FormStartPosition.CenterParent;
            frm.Show();
        }

        private void m_btnShowRateGraph_Click(object sender, EventArgs e)
        {
            Form frm = new frmRateGraph(m_DisplayRatePercentage, ratePercentage, rateAmount);
            frm.StartPosition = FormStartPosition.CenterParent;
            frm.Show();
        }

        private void m_btnGetStats_Click(object sender, EventArgs e)
        {
            getStatsThread = new BackgroundWorker();
            getStatsThread.WorkerReportsProgress = true;
            getStatsThread.WorkerSupportsCancellation = true;
            getStatsThread.DoWork += new DoWorkEventHandler(t_StopRx_DoWork);
            getStatsThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(t_StopRx_RunWorkerCompleted);

            getStatsThread.RunWorkerAsync();
        }

        private void m_rdoRSSIAmount_CheckedChanged(object sender, EventArgs e)
        {
            //Already displayed as amount
            if (!m_DisplayRSSIPercentage)
                return;

            m_DisplayRSSIPercentage = false;

            //Change label
            foreach (Label lbl in m_grpRssi.Controls.OfType<Label>())
            {
                if (lbl.Text.Equals("%"))
                    lbl.Text = "pkt";
            }

            //Change value
            iDisplayRSSIAsPercentage(false);
        }

        private void m_rdoRSSIPercentage_CheckedChanged(object sender, EventArgs e)
        {
            //Already displayed as percentage
            if (m_DisplayRSSIPercentage)
                return;

            m_DisplayRSSIPercentage = true;

            //Change label
            foreach (Label lbl in m_grpRssi.Controls.OfType<Label>())
            {
                if (lbl.Text.Equals("pkt"))
                    lbl.Text = "%";
            }

            //Change value
            iDisplayRSSIAsPercentage(true);
        }

        private void m_rdoRateAmount_CheckedChanged(object sender, EventArgs e)
        {
            //Already displayed as amount
            if (!m_DisplayRatePercentage)
                return;

            m_DisplayRatePercentage = false;

            //Change label
            foreach (Label lbl in m_grpRate.Controls.OfType<Label>())
            {
                if (lbl.Text.Equals("%"))
                    lbl.Text = "pkt";
            }

            //Change value
            iDisplayRateAsPercentage(false);
        }

        private void m_rdoRatePercentage_CheckedChanged(object sender, EventArgs e)
        {
            //Already displayed as percentage
            if (m_DisplayRatePercentage)
                return;

            m_DisplayRatePercentage = true;

            //Change label
            foreach (Label lbl in m_grpRate.Controls.OfType<Label>())
            {
                if (lbl.Text.Equals("pkt"))
                    lbl.Text = "%";
            }

            //Change value
            iDisplayRateAsPercentage(true);
        }
        /*===================================================================*/
        #endregion /* Event Handling */

        private class RxDoWorkArgs
        {
            internal Channel_e channel;
            internal Int32 duration;

            internal RxDoWorkArgs(Channel_e channel, Int32 duration)
            {
                this.channel = channel;
                this.duration = duration;
            }
        }

        private class RxStopResult
        {
            internal Int32 errorCode;
            internal SlGetRxStatResponse_t RxStats;

            internal RxStopResult()
            {
                this.RxStats = new SlGetRxStatResponse_t();
                this.errorCode =  -1;
            }
        }
    }
}
