/*******************************************************************************
* Copyright (C) 2016 Maxim Integrated Products, Inc., All rights Reserved.
* 
* This software is protected by copyright laws of the United States and
* of foreign countries. This material may also be protected by patent laws
* and technology transfer regulations of the United States and of foreign
* countries. This software is furnished under a license agreement and/or a
* nondisclosure agreement and may only be used or reproduced in accordance
* with the terms of those agreements. Dissemination of this information to
* any party or parties not specified in the license agreement and/or
* nondisclosure agreement is expressly prohibited.
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the name of Maxim Integrated
* Products, Inc. shall not be used except as stated in the Maxim Integrated
* Products, Inc. Branding Policy.
*
* The mere transfer of this software does not imply any licenses
* of trade secrets, proprietary technology, copyrights, patents,
* trademarks, maskwork rights, or any other form of intellectual
* property whatsoever. Maxim Integrated Products, Inc. retains all
* ownership rights.
*******************************************************************************
*/

#define USE_MEDICALCHARTHELPER
//#define CES_DEMO

using System;
using System.Collections.Generic;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using RPCSupport.Streaming;
using Maxim.Charting;
using System.Threading;
using HealthSensorPlatform.View;

using System.Windows.Forms.DataVisualization.Charting;

// DEBUG
using System.IO;

namespace HealthSensorPlatform.CustomControls
{
    public partial class OpticalView : UserControl, IDeviceView, IOpticalAlgorithmView
    {
        /* Constant Fields */
        static string StartString = "Start Monitor";
        static string StopString = "Stop Monitor";

        const int ChartTime = 4; // 4 seconds

        /* Fields */
        private bool connected = false;
        private bool streaming = false;

        private RPCSupport.RPCClient rpcClient;
        //private MedicalChartHelper medicalChartLED;
        //private MedicalChartHelper medicalChartAccel;
        private AutoScaleCalculator fifo1Calc;
        private AutoScaleCalculator fifo2Calc;
        private AutoScaleCalculator fifo3Calc;

        eStreamMode streamMode;
        byte fifo_waterlevel_mark = 0x0f;
        byte sample_avg;
        byte sample_rate;
        byte pulse_width;
        byte red_led_current;
        byte ir_led_current;
        byte green_led_current;
        byte slot_1;
        byte slot_2;
        byte slot_3;
        byte slot_4;

        // Heart Rate Sensor
        int points; // Number of x-axis points for a plot
        int sampleRate; // Human readable sample rate
        int sampleAverage; // Human readable number of average points
        // LIS2HD
        int sampleRateAccel; 
        int pointsAccel;

        EventHandler<PartialArrayIntAvailableEventArgs> appendChart; // Allows event to be unregistered

        Dictionary<string, MedianFilter> intervalDict = new Dictionary<string, MedianFilter>();
        Dictionary<string, AutoScaleCalculator> chartScaleDict = new Dictionary<string, AutoScaleCalculator>();

        List<Control> streamingControls = new List<Control>();

#if CES_DEMO
        // Algorithm Data
        Queue red;
        Queue ir;
        Queue led;
        // struct to run algorithms
        AlgorithmMobileBU.AlgorithmConfiguration config;
        AlgorithmMobileBU.AlgorithmOutput output;
        int algoCounter = 0;
#endif

        // DEBUG
        //StreamWriter file = new StreamWriter("hsp_output_data.csv");

        /* Constructors */
        public OpticalView()
        {
            InitializeComponent();

            OpticalSensorInitControls();

            cboSampleRate.SelectedIndexChanged += new EventHandler(cboSRPWLed_SelectedIndexChanged);
            cboPulseWidth.SelectedIndexChanged += new EventHandler(cboSRPWLed_SelectedIndexChanged);
            cboRedLED.SelectedIndexChanged += new EventHandler(cboSRPWLed_SelectedIndexChanged);
            cboIRLED.SelectedIndexChanged += new EventHandler(cboSRPWLed_SelectedIndexChanged);
            cboGreenLED.SelectedIndexChanged += new EventHandler(cboSRPWLed_SelectedIndexChanged);

            cboSampleRate.SelectedIndexChanged += new EventHandler(cboSampleRate_SelectedIndexChanged);
            cboSampleAvg.SelectedIndexChanged += new EventHandler(cboSampleRate_SelectedIndexChanged);

            streamingControls.AddRange(new Control[] { panel1, maximGroupBoxSettings, maximGroupBoxLEDcurrents, maximGroupBoxLEDTimingSlots, btnDefaults });
#if CES_DEMO
            // Algorithm 
            red = new Queue();
            ir = new Queue();
            led = new Queue();

            config = new AlgorithmMobileBU.AlgorithmConfiguration();
            output = new AlgorithmMobileBU.AlgorithmOutput();

            config.snrNoFingerThreshold = -9;
            config.acLowPerfusionThreshold = 950;
            config.isAGC = 0;

            //updateAlgorithm();           
            lblHeartRate.Visible = true;
            lblHeartRateText.Visible = true;
#endif
        }

        /* Delegates */
        //public delegate void StreamingStartStopEventHandler(StreamingStartStopEventArgs e);

        /* Events */
        //public event StreamingStartStopEventHandler StreamingStartStop;
        public event EventHandler<StreamingStartStopEventArgs> StreamingStartStop;

        /* Enums */
        public enum eStreamMode
        {
            eHR,
            eSPO2,
            eMulti
        }

        /* Properties */
        public RPCSupport.RPCClient RPCClient
        {
            set
            {
                rpcClient = value;
                appendChart = new EventHandler<PartialArrayIntAvailableEventArgs>(On_AppendChart);
                rpcClient.streaming.PartialArrayIntAvailable += appendChart;
            }
        }

        public bool Connected
        {
            get
            {
                return connected;
            }
            set
            {
                connected = value;
                if (connected == false)
                    if (btnMonitoring.Text == StopString)
                        streamingStartStop();
            }
        }

        public int AccelSampleRate
        {
            get
            {
                return sampleRateAccel;
            }
        }

        public int OpticalSampleRate
        {
            get
            {
                return sampleRate / sampleAverage;
            }
        }
        
        public eStreamMode ModeConfiguration
        {
            get
            {
                return streamMode;
            }
        }

        /* Methods */

        // Clean up streaming 
        public void Close()
        {
            rpcClient.streaming.PartialArrayIntAvailable -= appendChart; // Stop event from firing
            // Disable streaming if enabled
            if (btnMonitoring.Text == StopString)
                streamingStartStop();
        }

        public void DisplayAlgorithmResult(double heartRateBPM, bool heartRateBPMValid, double heartRateBPMSignalStrength, 
            double spO2Percent, bool spO2PercentValid, double spO2PercentSignalStrength)
        {
            // 100Hz only - TODO
            if (OpticalSampleRate != 100)
                return;

            if (heartRateBPMValid)
            {
                lblHeartRate.ForeColor = SystemColors.ControlText;
                lblHeartRate.Text = string.Format("{0}", heartRateBPM * (sampleRate / 100.0)); // 100.0 for default sample rate
            }
            else
            {
                lblHeartRate.ForeColor = Color.Gray;
            }

            if (spO2PercentValid)
            {
                lblSpO2.ForeColor = SystemColors.ControlText;
                lblSpO2.Text = string.Format("{0:0.0}", spO2Percent);
            }
            else
            {
                lblSpO2.ForeColor = Color.Gray;
            }
        }

        public void DisplayAlgorithmReset()
        {
            lblHeartRate.Text = "----";
            lblHeartRate.ForeColor = Color.Gray;

            lblSpO2.Text = "----";
            lblSpO2.ForeColor = Color.Gray;
        }

        System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
        void InitGraphs()
        {
            /*
            medicalChartLED = new MedicalChartHelper(chartLED,
                "ChartArea1Red", "SeriesRed", "Red ADC Code",
                "ChartArea2IR", "SeriesIR", "IR ADC Code",
                "ChartArea3Green", "SeriesGreen", "Green ADC Code",
                MedicalChartHelper.DataFormats.FormatUnsigned);
            medicalChartLED._plotPoints = 200;
            medicalChartAccel = new MedicalChartHelper(chartAccel,
                "ChartArea4AccelX", "SeriesAccelX", "Accelerometer X",
                "ChartArea5AccelY", "SeriesAccelY", "Accelerometer Y",
                "ChartArea6AccelZ", "SeriesAccelZ", "Accelerometer Z",
                MedicalChartHelper.DataFormats.Format16bit2sComplement);
            */

            MedianFilter fifo1 = new MedianFilter(10);
            MedianFilter fifo2 = new MedianFilter(10);
            MedianFilter fifo3 = new MedianFilter(10);
            MedianFilter accelx = new MedianFilter(10);
            MedianFilter accely = new MedianFilter(10);
            MedianFilter accelz = new MedianFilter(10);

            intervalDict.Add("ChartArea1Red", fifo1);
            intervalDict.Add("ChartArea2IR", fifo2);
            intervalDict.Add("ChartArea3Green", fifo3);

            intervalDict.Add("ChartArea4AccelX", accelx);
            intervalDict.Add("ChartArea5AccelY", accely);
            intervalDict.Add("ChartArea6AccelZ", accelz);

            fifo1Calc = new AutoScaleCalculator(0, 90000);
            fifo1Calc.Minimum = 0;
            fifo1Calc.Maximum = Math.Pow(2, 18);
            fifo1Calc.Intervals = 5;
            fifo1Calc.MinimumRange = 200;
            fifo1Calc.ScaleTrigger = 0.8;
            fifo1Calc.RescaleTargetRange = 0.25;
            chartScaleDict.Add("ChartArea1Red", fifo1Calc);

            fifo2Calc = new AutoScaleCalculator(0, 90000);
            fifo2Calc.Minimum = 0;
            fifo2Calc.Maximum = Math.Pow(2, 18);
            fifo2Calc.Intervals = 5;
            fifo2Calc.MinimumRange = 200;
            fifo2Calc.ScaleTrigger = 0.8;
            fifo2Calc.RescaleTargetRange = 0.25;
            chartScaleDict.Add("ChartArea2IR", fifo2Calc);

            fifo3Calc = new AutoScaleCalculator(0, 90000);
            fifo3Calc.Minimum = 0;
            fifo3Calc.Maximum = Math.Pow(2, 18);
            fifo3Calc.Intervals = 5;
            fifo3Calc.MinimumRange = 200;
            fifo3Calc.ScaleTrigger = 0.8;
            fifo3Calc.RescaleTargetRange = 0.25;
            chartScaleDict.Add("ChartArea3Green", fifo2Calc);

            AutoScaleCalculator accelXCalc = new AutoScaleCalculator(-10000, 10000);
            accelXCalc.Minimum = -32768;
            accelXCalc.Maximum = 32767;
            accelXCalc.Intervals = 5;
            accelXCalc.MinimumRange = 500;
            accelXCalc.ScaleTrigger = 0.8;
            accelXCalc.RescaleTargetRange = 0.25;
            chartScaleDict.Add("ChartArea4AccelX", accelXCalc);

            AutoScaleCalculator accelYCalc = new AutoScaleCalculator(-10000, 10000);
            accelYCalc.Minimum = -32768;
            accelYCalc.Maximum = 32767;
            accelYCalc.Intervals = 5;
            accelYCalc.MinimumRange = 500;
            accelYCalc.ScaleTrigger = 0.8;
            accelYCalc.RescaleTargetRange = 0.25;
            chartScaleDict.Add("ChartArea5AccelY", accelYCalc);

            AutoScaleCalculator accelZCalc = new AutoScaleCalculator(-10000, 10000);
            accelZCalc.Minimum = -32768;
            accelZCalc.Maximum = 32767;
            accelZCalc.Intervals = 5;
            accelZCalc.MinimumRange = 500;
            accelZCalc.ScaleTrigger = 0.8;
            accelZCalc.RescaleTargetRange = 0.25;
            chartScaleDict.Add("ChartArea6AccelZ", accelZCalc);

            /*
            Random rand = new Random();
            int[] data1 = new int[10];
            int[] data2 = new int[10];
            int[] data3 = new int[10];
            double[] data1_ = new double[10];
            double[] data2_ = new double[10];
            double[] data3_ = new double[10];
            for (int i = 0; i < data1.Length; i++)
            {
                data1[i] = rand.Next(10);
                data2[i] = rand.Next(10);
                data3[i] = rand.Next(10);
                data1_[i] = rand.NextDouble() * 10;
                data2_[i] = rand.NextDouble() * 10;
                data3_[i] = rand.NextDouble() * 10;
            }
            */

            /*
            int[] dummyData = new int[] { 0 };
            medicalChartLED.AppendDataChartArea1(dummyData, 0, 0);
            medicalChartLED.AppendDataChartArea2(dummyData, 0, 0);
            medicalChartLED.AppendDataChartArea3(dummyData, 0, 0);
            //medicalChartAccel._xCount1++;
            //medicalChartAccel._xCount2++;
            //medicalChartAccel._xCount3++;
            medicalChartAccel.AppendDataChartArea1(dummyData, 0, 0);
            medicalChartAccel.AppendDataChartArea2(dummyData, 0, 0);
            medicalChartAccel.AppendDataChartArea3(dummyData, 0, 0);
            */

            setupGraphs();

            chartLED.ChartAreas["ChartArea3Green"].Visible = false;
            timer.Interval = 50;    
            //timer.Tick += new EventHandler(timer_Tick);
        }

        void setupGraphs()
        {
            chartLED.SuspendLayout();
            chartAccel.SuspendLayout();

            /* Initialize Empty Chart Grid */
            chartLED.Series["SeriesRed"].Points.Clear();
            chartLED.Series["SeriesIR"].Points.Clear();
            chartLED.Series["SeriesGreen"].Points.Clear();

            chartAccel.Series["SeriesAccelX"].Points.Clear();
            chartAccel.Series["SeriesAccelY"].Points.Clear();
            chartAccel.Series["SeriesAccelZ"].Points.Clear();

            // Clear labels
            chartLED.ChartAreas["ChartArea1Red"].AxisX.CustomLabels.Clear();
            chartLED.ChartAreas["ChartArea2IR"].AxisX.CustomLabels.Clear();
            chartLED.ChartAreas["ChartArea3Green"].AxisX.CustomLabels.Clear();
            chartAccel.ChartAreas["ChartArea4AccelX"].AxisX.CustomLabels.Clear();
            chartAccel.ChartAreas["ChartArea5AccelY"].AxisX.CustomLabels.Clear();
            chartAccel.ChartAreas["ChartArea6AccelZ"].AxisX.CustomLabels.Clear();

            chartLED.Series["SeriesRed"].Points.Add(0);
            chartLED.Series["SeriesIR"].Points.Add(0);
            chartLED.Series["SeriesGreen"].Points.Add(0);

            chartAccel.Series["SeriesAccelX"].Points.Add(0);
            chartAccel.Series["SeriesAccelY"].Points.Add(0);
            chartAccel.Series["SeriesAccelZ"].Points.Add(0);

            // Initialize Plots for OS24
            chartLED.ChartAreas["ChartArea1Red"].AxisX.Interval = points / ChartTime;
            chartLED.ChartAreas["ChartArea2IR"].AxisX.Interval = points / ChartTime;
            chartLED.ChartAreas["ChartArea3Green"].AxisX.Interval = points / ChartTime;
            chartLED.ChartAreas["ChartArea1Red"].AxisX.Maximum = points;
            chartLED.ChartAreas["ChartArea2IR"].AxisX.Maximum = points;
            chartLED.ChartAreas["ChartArea3Green"].AxisX.Maximum = points;
            chartLED.ChartAreas["ChartArea1Red"].AxisY.Maximum = 90000;
            chartLED.ChartAreas["ChartArea1Red"].AxisY.Minimum = 0;
            chartLED.ChartAreas["ChartArea2IR"].AxisY.Maximum = 90000;
            chartLED.ChartAreas["ChartArea2IR"].AxisY.Minimum = 0;
            chartLED.ChartAreas["ChartArea3Green"].AxisY.Maximum = 90000;
            chartLED.ChartAreas["ChartArea3Green"].AxisY.Minimum = 0;

            // Initialize Plots for LIS2HD
            sampleRateAccel = sampleRate;
            //pointsAccel = (sampleRateAccel / sampleAverage) * ChartTime;
            pointsAccel = (sampleRateAccel) * ChartTime;
            chartAccel.ChartAreas["ChartArea4AccelX"].AxisX.Interval = pointsAccel / ChartTime;
            chartAccel.ChartAreas["ChartArea5AccelY"].AxisX.Interval = pointsAccel / ChartTime;
            chartAccel.ChartAreas["ChartArea6AccelZ"].AxisX.Interval = pointsAccel / ChartTime;
            chartAccel.ChartAreas["ChartArea4AccelX"].AxisX.Maximum = pointsAccel;
            chartAccel.ChartAreas["ChartArea5AccelY"].AxisX.Maximum = pointsAccel;
            chartAccel.ChartAreas["ChartArea6AccelZ"].AxisX.Maximum = pointsAccel;
            chartAccel.ChartAreas["ChartArea4AccelX"].AxisY.Minimum = -10000;
            chartAccel.ChartAreas["ChartArea5AccelY"].AxisY.Minimum = -10000;
            chartAccel.ChartAreas["ChartArea6AccelZ"].AxisY.Minimum = -10000;
            chartAccel.ChartAreas["ChartArea4AccelX"].AxisY.Maximum = 10000;
            chartAccel.ChartAreas["ChartArea5AccelY"].AxisY.Maximum = 10000;
            chartAccel.ChartAreas["ChartArea6AccelZ"].AxisY.Maximum = 10000;

            // Set X-axis labels in seconds
            for (int i = 0; i < ChartTime + 1; i++)
            {
                chartLED.ChartAreas["ChartArea1Red"].AxisX.CustomLabels.Add( (sampleRate / sampleAverage) * (2 * i - 1) / 2, (sampleRate / sampleAverage) * (2 * i + 1) / 2, i.ToString());
                chartLED.ChartAreas["ChartArea2IR"].AxisX.CustomLabels.Add( (sampleRate / sampleAverage) * (2 * i - 1) / 2, (sampleRate / sampleAverage) * (2 * i + 1) / 2, i.ToString());
                chartLED.ChartAreas["ChartArea3Green"].AxisX.CustomLabels.Add( (sampleRate / sampleAverage) * (2 * i - 1) / 2, (sampleRate / sampleAverage) * (2 * i + 1) / 2, i.ToString());

                chartAccel.ChartAreas["ChartArea4AccelX"].AxisX.CustomLabels.Add(sampleRateAccel * (2 * i - 1) / 2, sampleRateAccel * (2 * i + 1) / 2, i.ToString());
                chartAccel.ChartAreas["ChartArea5AccelY"].AxisX.CustomLabels.Add(sampleRateAccel * (2 * i - 1) / 2, sampleRateAccel * (2 * i + 1) / 2, i.ToString());
                chartAccel.ChartAreas["ChartArea6AccelZ"].AxisX.CustomLabels.Add(sampleRateAccel * (2 * i - 1) / 2, sampleRateAccel * (2 * i + 1) / 2, i.ToString());
            }

            chartLED.ResumeLayout();
            chartLED.Invalidate();
            chartAccel.ResumeLayout();
            chartAccel.Invalidate();
        }

        void timer_Tick(object sender, EventArgs e)
        {
            /*Single tmpData = dynamicData[0].Y;
            for (int i = 0; i < dynamicData.Length - 1; i++)
            {
                dynamicData[i].Y = dynamicData[i + 1].Y;
            }
            dynamicData[dynamicData.Length - 1].Y = tmpData;*/

            //PointF[] newData = GenerateDynamicData(100, 5);
            //UpdateLedChart("SeriesRed", newData);
            //UpdateLedChart("SeriesIR", newData);
            //UpdateLedChart("SeriesGreen", newData);
        }

        /*private void UpdateLedChart(string seriesName, PointF[] data)
        {
            int count; 

            chartLED.SuspendLayout();
            //chartLED.Series[seriesName].Points.Clear();
            foreach (PointF pt in data)
            {
                //chartLED.Series[seriesName].Points.AddXY(pt.X, pt.Y);
                chartLED.Series[seriesName].Points.Add(pt.Y);
            }

            for (count = chartLED.Series[seriesName].Points.Count; 
                count > points; 
                count = chartLED.Series[seriesName].Points.Count)
            {
                chartLED.Series[seriesName].Points.RemoveAt(0);
            } 
            chartLED.ResumeLayout();
            chartLED.Invalidate();
        }*/

        private void UpdateXYZChart(string seriesName, int[] data)
        {
            int datapoint;
            int count;

            chartAccel.SuspendLayout();
            //chartLED.Series[seriesName].Points.Clear();
            foreach (int val in data)
            {
                //chartLED.Series[seriesName].Points.AddXY(pt.X, pt.Y);
                datapoint = val;
                // Two's complement conversion
                if (val > 0x8000)
                {
                    datapoint = datapoint - 0x10000;
                }
                    chartAccel.Series[seriesName].Points.Add(datapoint);
            }

            for (count = chartAccel.Series[seriesName].Points.Count;
                count > pointsAccel;
                count = chartAccel.Series[seriesName].Points.Count)
            {
                chartAccel.Series[seriesName].Points.RemoveAt(0);
            }
            //chartAccel.ResetAutoValues();
            chartAccel.ResumeLayout();
            chartAccel.Invalidate();
        }

        private void UpdateLedChart(string seriesName, int[] data)
        {
            int[] dataPoints = new int[points];
            int j = 0;
            int count;

            chartLED.SuspendLayout();
            //chartLED.Series[seriesName].Points.Clear();

            // Update Chart with new data
            if (data.Length > points)
                j = data.Length - points;

            for (; j < data.Length; j++ )
            {
                chartLED.Series[seriesName].Points.Add(data[j]);
#if CES_DEMO
                if (seriesName.CompareTo("SeriesRed") == 0)
                    red.Enqueue(data[j]);
                else if (seriesName.CompareTo("SeriesIR") == 0)
                    ir.Enqueue(data[j]);
                else if (seriesName.CompareTo("SeriesGreen") == 0)
                    led.Enqueue(data[j]);
#endif
            }

            for (count = chartLED.Series[seriesName].Points.Count; 
                count > points; 
                count = chartLED.Series[seriesName].Points.Count)
            {
                chartLED.Series[seriesName].Points.RemoveAt(0);
            }
        }

        private void UpdateLedChartScale(string chartName, string seriesName)
        {
            int min = Int32.MaxValue, max = 0;
            int i, j;
            int[] data = new int[points];
            int graphCount;
            //int maxRound, minRound;

            graphCount = chartLED.Series[seriesName].Points.Count;

            for (j = 0; j < graphCount; j++ )
            {
                data[j] = (int)chartLED.Series[seriesName].Points[j].YValues[0];
            }

            //if (data.Length < sampleRate)
            //    return; // not enough data to draw graph
            /*else*/
            if (graphCount < (sampleRate / sampleAverage) * 2) // look back 2 seconds
            {
                i = 0;
            }
            else // look back 2 seconds
            {
                i = graphCount - (sampleRate / sampleAverage) * 2;
            }

            min = data[i];
            max = data[i];
            for (; i < graphCount; i++ )
            {
                if (data[i] < min && data[i] != 0)
                    min = data[i];
                else if (data[i] > max)
                    max = data[i];
            }

            /*
            MedianFilter filter;
            intervalDict.TryGetValue(chartName, out filter);

            var minMax = chartInterval(min, max, chartLED.ChartAreas[chartName].AxisY.Minimum, chartLED.ChartAreas[chartName].AxisY.Maximum, filter);
            minRound = minMax.Item1;
            maxRound = minMax.Item2;



            // For no finger on sensor
            if (max < 500)
                max = 500;
            */

            AutoScaleCalculator chartScale;
            chartScaleDict.TryGetValue(chartName, out chartScale);

            var minMax = chartScale.Interval(min, max);

            // Round to nearest 100 with averaging
            //chartLED.ChartAreas[chartName].AxisY.Maximum = (chartLED.ChartAreas[chartName].AxisY.Maximum + (max / 100 + 1) * 100)/2;
            //chartLED.ChartAreas[chartName].AxisY.Maximum = (((int)chartLED.ChartAreas[chartName].AxisY.Maximum + max)/200 + 1) * 100;
            //chartLED.ChartAreas[chartName].AxisY.Minimum = (chartLED.ChartAreas[chartName].AxisY.Minimum + (min / 100 - 1) * 100)/2;
            //chartLED.ChartAreas[chartName].AxisY.Minimum = (((int)chartLED.ChartAreas[chartName].AxisY.Minimum + min)/200 - 1) * 100;
            chartLED.ChartAreas[chartName].AxisY.Minimum = minMax.Item1;
            chartLED.ChartAreas[chartName].AxisY.Maximum = minMax.Item2;

            chartLED.ResumeLayout();
            chartLED.Invalidate();
        }

        private void UpdateXYZChartScale(string chartName, string seriesName)
        {
            int min = Int32.MaxValue, max = Int32.MinValue;
            int i, j;
            int maxRound, minRound;
            int graphCount;

            int[] data = new int[pointsAccel];

            graphCount = chartAccel.Series[seriesName].Points.Count;

            // Save all points from plot to data[]
            for (j = 0; j < graphCount; j++)
            {
                data[j] = (int)chartAccel.Series[seriesName].Points[j].YValues[0];
            }

            if (graphCount < (sampleRateAccel / sampleAverage) * 2) // look back 3 seconds
                i = 0;
            else
                i = graphCount - (sampleRateAccel / sampleAverage) * 2;

            // Find min/max
            min = data[i];
            max = data[i];
            for (; i < graphCount; i++)
            {
                if (data[i] < min)
                    min = data[i];
                else if (data[i] > max)
                    max = data[i];
            }

            /*
            // Sane defaults
            if (min == max)
            {
                min = 0;
                max = 100;
            }

            MedianFilter filter;
            intervalDict.TryGetValue(chartName, out filter);

            var minMax = chartInterval(min, max, chartAccel.ChartAreas[chartName].AxisY.Minimum, chartAccel.ChartAreas[chartName].AxisY.Maximum, filter);
            minRound = minMax.Item1;
            maxRound = minMax.Item2;

            if (maxRound > 32768)
                maxRound = 33000;

            if (minRound < -32768)
                minRound = -33000;
            */

            AutoScaleCalculator chartScale;
            chartScaleDict.TryGetValue(chartName, out chartScale);

            var minMax = chartScale.Interval(min, max);
            minRound = (int)minMax.Item1;
            maxRound = (int)minMax.Item2;

            // Set the Min and Max for Y Axis
            //chartAccel.ChartAreas[chartName].AxisY.Maximum = (((int)chartAccel.ChartAreas[chartName].AxisY.Maximum + max) / 200 + 1) * 100;
            //chartAccel.ChartAreas[chartName].AxisY.Minimum = (((int)chartAccel.ChartAreas[chartName].AxisY.Minimum + min) / 200 - 1) * 100;
            chartAccel.ChartAreas[chartName].AxisY.Maximum = maxRound;
            chartAccel.ChartAreas[chartName].AxisY.Minimum = minRound;
            //System.Diagnostics.Debug.Print(chartName + " count: " + graphCount + " minRound: " + minRound + " maxRound: " + maxRound + " min: " + min + " max: " + max);

            chartAccel.ResumeLayout();
            chartAccel.Invalidate();
        }

        /*private PointF[] dynamicData;
        private static PointF[] GenerateDynamicData(double amplitude, int sampleCount)
        {
            Random rnd = new Random();
            PointF[] points = new PointF[sampleCount];
            points[0] = new PointF(0, (Single)(amplitude * rnd.NextDouble()));
            for (int i = 1; i < sampleCount; i++)
            {
                points[i] = new PointF(i, (Single)(points[i - 1].Y + (amplitude / 10 * (0.5 - rnd.NextDouble()))));
                if (points[i].Y > amplitude) points[i].Y = (Single)amplitude;
            }
            return (points);
        }*/

        /// <summary>
        /// Calculate rounded minimum and maximum for chart data
        /// </summary>
        /// <param name="minimum"></param>
        /// <param name="maximum"></param>
        /// <param name="chartInterval"></param>
        /// <returns></returns>
        private static Tuple<int, int> chartInterval(int minimum, int maximum, double chartMinimum, double chartMaximum, MedianFilter chartInterval)
        {
            int delta, interval, roundInterval, roundCenter;
            double mag, magPower, magMostSigDigit;
            int center;

            // 10% and 90% range
            int chartRange = (int)(chartMaximum - chartMinimum);
            int dataRange = maximum - minimum;
            if (minimum < chartMinimum + chartRange * 0.10 || maximum > chartMaximum - chartRange * 0.10
                || dataRange > 0.9 * chartRange || dataRange < 0.1 * chartRange)
            {

                delta = (int)((maximum - minimum));

                interval = delta * (100 / 35) / 5; // delta * 4 = 25% * 4 = 100 (full chart scale)
                center = minimum + (maximum - minimum) / 2;

                mag = Math.Floor(Math.Log10(interval));
                magPower = Math.Pow(10, mag);

                magMostSigDigit = (int)(interval / (double)magPower + 0.5);

                if (magMostSigDigit >= 5.0)
                    magMostSigDigit = 10;
                else if (magMostSigDigit >= 2.0)
                    magMostSigDigit = 5;
                else if (magMostSigDigit >= 1.0)
                    magMostSigDigit = 2;

                roundInterval = (int)(magMostSigDigit * magPower);
                if (roundInterval < 100)
                {
                    roundInterval = 100;
                }
                roundInterval = chartInterval.Filter(roundInterval);

                roundCenter = ((center + 100) / 200) * 200;

                int roundMin = (int)(roundCenter - roundInterval * 2.5);
                int roundMax = (int)(roundCenter + roundInterval * 2.5);
                if (roundInterval > 104858)
                {
                    roundMin = 0;
                    roundMax = 550000;
                }
                /*else
                {
                    if (roundMin < 0) // || roundMax > 524288)
                    {
                        roundMin = 0;
                        roundMax = roundInterval * 5;
                    }
                }*/

                return new Tuple<int, int>(roundMin, roundMax);
            }

            return new Tuple<int, int>((int)chartMinimum, (int)chartMaximum);
        }

        void OpticalSensorInitControls()
        {
            cboMode.Items.AddRange(new string[] { "HR","SPO2","LED" });
            cboSampleRate.Items.AddRange(new string[] { "50", "100", "200", "400" });
            cboSampleAvg.Items.AddRange(new string[] { "1", "2", "4", "8", "16", "32" });
            cboPulseWidth.Items.AddRange(new string[] { "69", "118", "215", "411" });
            cboADCFSrangenA.Items.AddRange(new string[] { "2048", "4096", "8192", "16384" });
            double slopeInt = 25.0 / 128.0;
            for (int index = 0; index < 256; index++)
            {
                string str = index < 0x0F ? (0.2 * index).ToString() : (slopeInt * index + slopeInt).ToString();
                cboIRLED.Items.Add(str);
                cboRedLED.Items.Add(str);
                cboGreenLED.Items.Add(str);
                cboPilotPA.Items.Add(str);
            }

            OpticalSensorDefaults();
            calculateAvgCurrent();

            InitGraphs();            
        }

        public void OpticalSensorDefaults()
        {
            cboMode.SelectedItem = "SPO2";
            cboRedLED.SelectedItem = "10.15625";
            cboIRLED.SelectedItem = "10.15625";
            cboGreenLED.SelectedItem = "10.15625";
            cboSampleRate.SelectedItem = "100";
            cboPulseWidth.SelectedItem = "411";
            cboSampleAvg.SelectedItem = "1";
            cboADCFSrangenA.SelectedItem = "8192";

            cboLEDslot1.SelectedIndex = 1;
            cboLEDslot2.SelectedIndex = 2;
            cboLEDslot3.SelectedIndex = 3;
            cboLEDslot4.SelectedIndex = 0;

            // Used to generate the initial chart grid
            sample_rate = (byte)cboSampleRate.SelectedIndex;
            sample_avg = (byte)cboSampleAvg.SelectedIndex;

            sampleRate = (1 << sample_rate) * 50; // (1 << sample_rate) = 2^sample_rate
            sampleAverage = (1 << sample_avg); // 2^sample_avg
            points = (sampleRate / sampleAverage) * ChartTime; // 4 seconds, 2^sample_av

            sampleRateAccel = sampleRate; 
            pointsAccel = (sampleRateAccel / sampleAverage) * ChartTime;
        }

        private void cbo_IndexChanged(object sender, EventArgs e)
        {
            if ((MaximStyle.MaximComboBox)sender == cboMode)
            {
                bool HRmode = (cboMode.Text == "HR");
                bool SPO2mode = (cboMode.Text == "SPO2");
                bool multiLEDmode = (cboMode.Text == "LED");
                //labelcboRedLED.Enabled = myMAX30101.valid_Red;
                //cboRedLED.Enabled = myMAX30101.valid_Red;
                bool valid_IR = (multiLEDmode || SPO2mode);
                bool valid_Green = multiLEDmode;
                bool valid_Red = multiLEDmode;
                labelcboIRLED.Enabled = valid_IR;
                cboIRLED.Enabled = valid_IR;
                labelcboGreenLED.Enabled = valid_Green;
                cboGreenLED.Enabled = valid_Green;
                cboLEDslot1.Enabled = multiLEDmode;
                cboLEDslot2.Enabled = multiLEDmode;
                cboLEDslot3.Enabled = multiLEDmode;
                cboLEDslot4.Enabled = multiLEDmode;
                {
                    // Hide unused LED channel graphs
                    // | mode | ChartArea1Red | ChartArea2IR | ChartArea3Green |
                    // | LED  |   visible     |   visible    |   visible       | MAX30101 only
                    // | SPO2 |   visible     |   visible    |   hidden        |
                    // | HR   |   visible     |   hidden     |   hidden        | MAX30101: Red only. MAX30100: IR only.
                    valid_Red = true;
                    valid_IR = multiLEDmode || SPO2mode;
                    valid_Green = multiLEDmode;
                    
                    chartLED.ChartAreas["ChartArea1Red"].Visible = valid_Red;
                    chartLED.ChartAreas["ChartArea2IR"].Visible = valid_IR;
                    chartLED.ChartAreas["ChartArea3Green"].Visible = valid_Green;                   
                }
            }
        }

        public void On_AppendChart(object sender, PartialArrayIntAvailableEventArgs e)
        {
            if (streaming)
            {
                if ((e.reportID & 0xF0) == PartialArrayIntAvailableEventArgs.PACKET_MAX30101)
                {
                    if (e.array1.Length != 0)
                    {
                        UpdateLedChart("SeriesRed", e.array1);
                        UpdateLedChartScale("ChartArea1Red", "SeriesRed");
                    }
                    if (e.array2.Length != 0)
                    {
                        UpdateLedChart("SeriesIR", e.array2);
                        UpdateLedChartScale("ChartArea2IR", "SeriesIR");
                    }
                    if (e.array3.Length != 0)
                    {
                        UpdateLedChart("SeriesGreen", e.array3);
                        UpdateLedChartScale("ChartArea3Green", "SeriesGreen");
                    }
                }
                if (e.reportID == PartialArrayIntAvailableEventArgs.PACKET_LIS2DH)
                {
                    if (e.array1.Length != 0)
                    {
                        UpdateXYZChart("SeriesAccelX", e.array1);
                        UpdateXYZChartScale("ChartArea4AccelX", "SeriesAccelX");
                    }
                    if (e.array2.Length != 0)
                    {
                        UpdateXYZChart("SeriesAccelY", e.array2);
                        UpdateXYZChartScale("ChartArea5AccelY", "SeriesAccelY");
                    }
                    if (e.array3.Length != 0)
                    {
                        UpdateXYZChart("SeriesAccelZ", e.array3);
                        UpdateXYZChartScale("ChartArea6AccelZ", "SeriesAccelZ");
                    }

#if CES_DEMO
                if (sampleRate == 100)
                    updateAlgorithm();
#endif
                }
            }
        }

#if CES_DEMO
        private void updateAlgorithm()
        {
            int rtn = 0;

            while (red.Count != 0 ) //&& ir.Couunt != 0 && led.Count != 0)
            {
                StringBuilder outputData = new StringBuilder();
                int redVal = (Int32)red.Dequeue(); // Always dequeue red, checked in while loop
                int irVal = ir.Count > 0 ? (Int32)ir.Dequeue() : 1;
                int ledVal = led.Count > 0 ? (Int32)led.Dequeue() : 1;

                switch (streamMode)
                {
                    case eStreamMode.eSPO2:
                        rtn = AlgorithmMobileBU.runAlgorithm_win(irVal, redVal, 1, // ir, r, led
                                    25, sampleRate, ++algoCounter, 
                                    0, 0, 0, ref config, ref output);
                        break;
                    case eStreamMode.eHR: // TODO: Not functioning
                        rtn = AlgorithmMobileBU.runAlgorithm_win(1, redVal, 1, // ir, r, led
                                    25, sampleRate, ++algoCounter, 
                                    0, 0, 0, ref config, ref output);
                        break;
                    case eStreamMode.eMulti:
                        rtn = AlgorithmMobileBU.runAlgorithm_win(irVal, redVal, ledVal, // ir, r, led
                                    25, sampleRate, ++algoCounter, 
                                    0, 0, 0, ref config, ref output);
                        break;
                }

                //outputData.Append(DateTime.Now.ToString("HH:mm:ss.ffff"));
                //outputData.Append(",");
                //outputData.Append(irVal);
                //outputData.Append(",");
                //outputData.Append(redVal);
                //outputData.Append(",");
                //outputData.Append(0); // Can be green
                //outputData.Append(",");
                //outputData.Append(0); // Ax
                //outputData.Append(",");
                //outputData.Append(0); // Ay
                //outputData.Append(",");
                //outputData.Append(0); // Az
                //outputData.Append(",");

                //file.WriteLine(outputData.ToString()); // Raw data output
            }

            lblHeartRate.Text = Math.Round(output.hr).ToString(); //redIn.ToString();
            lblRespRate.Text = output.resp_rate.ToString("N");
            lblSpO2.Text = output.R == 0 ? "0" : (-45.060 * output.R * output.R + 30.354 * output.R + 94.845).ToString("N");//output.spo2.ToString("N");
            lblHeartRateVariation.Text = output.hrv.ToString("N");
            lblPulseInterval.Text = output.pulse_interval.ToString("N");
        }

#endif

        private void OpticalView_Load(object sender, EventArgs e)
        {
            //OpticalSensorInitControls();
            //OpticalSensorDefaults();
        }

        private void streamingStartStop()
        {
            if (cboMode.Text == "HR") streamMode = eStreamMode.eHR;
            if (cboMode.Text == "SPO2") streamMode = eStreamMode.eSPO2;
            if (cboMode.Text == "LED") streamMode = eStreamMode.eMulti;

            if (Connected && btnMonitoring.Text == StartString)
            {
                streaming = true;
                sample_rate = (byte)cboSampleRate.SelectedIndex;
                sample_avg = (byte)cboSampleAvg.SelectedIndex;
                pulse_width = (byte)cboPulseWidth.SelectedIndex;
                red_led_current = (byte)cboRedLED.SelectedIndex;
                ir_led_current = (byte)cboIRLED.SelectedIndex;
                green_led_current = (byte)cboGreenLED.SelectedIndex;
                slot_1 = (byte)cboLEDslot1.SelectedIndex;
                slot_2 = (byte)cboLEDslot2.SelectedIndex;
                slot_3 = (byte)cboLEDslot3.SelectedIndex;
                slot_4 = (byte)cboLEDslot4.SelectedIndex;

                sampleRate = Int32.Parse((string)cboSampleRate.SelectedItem);
                sampleAverage = Int32.Parse((string)cboSampleAvg.SelectedItem);
                points = (sampleRate / sampleAverage ) * ChartTime;

                foreach (Control c in streamingControls)
                    c.Enabled = false;

#if CES_DEMO
                // For Algorithm
                sampleRate = (1 << sample_rate) * 50; // (1 << sample_rate) = 2^sample_rate
                sampleAverage = (1 << sample_avg); // 2^sample_avg
                points = (sampleRate / sampleAverage) * ChartTime; // 4 seconds, 2^sample_av

                sampleRateAccel = 10; // TODO, update with real value from Jerry
                pointsAccel = (sampleRateAccel / sampleAverage) * ChartTime;

                // Reset algorithm counter
                algoCounter = 0;

                if (sampleRate == 100)
                {
                    lblHeartRateText.Enabled = true;
                    lblHeartRate.Enabled = true;
                    lblSpO2.Enabled = true;
                    lblSpO2Text.Enabled = true;
                }
                else
                {
                    lblHeartRateText.Enabled = false;
                    lblHeartRate.Enabled = false;
                    lblSpO2.Enabled = false;
                    lblSpO2Text.Enabled = false;
                }
#endif

                // Setup grid
                setupGraphs();

                switch (streamMode)
                {
                    case eStreamMode.eHR:
                        rpcClient.MAX30101.HRmode_init(fifo_waterlevel_mark, sample_avg, sample_rate, pulse_width,
                                                                         red_led_current, (byte)(sample_rate + 4));
                        break;
                    case eStreamMode.eMulti:
                        rpcClient.MAX30101.Multimode_init(fifo_waterlevel_mark, sample_avg, sample_rate, pulse_width,
                                             red_led_current, ir_led_current, green_led_current,
                                             slot_1, slot_2, slot_3, slot_4, (byte)(sample_rate + 4));
                        break;
                    case eStreamMode.eSPO2:
                        rpcClient.MAX30101.SpO2mode_init(fifo_waterlevel_mark, sample_avg, sample_rate, pulse_width,
                                                                         red_led_current, ir_led_current, (byte)(sample_rate + 4));
                        break;
                }

                btnMonitoring.Text = StopString;

                if (StreamingStartStop != null) 
                    StreamingStartStop(this, new StreamingStartStopEventArgs() { state = true });

            }
            else if (btnMonitoring.Text == StopString)
            {
                streaming = false;
                switch (streamMode)
                {
                    case eStreamMode.eHR:
                        rpcClient.MAX30101.HRmode_stop(connected);
                        break;
                    case eStreamMode.eMulti:
                        rpcClient.MAX30101.Multimode_stop(connected);
                        break;
                    case eStreamMode.eSPO2:
                        rpcClient.MAX30101.SpO2mode_stop(connected);
                        break;
                }
                //rpcClient.MAX30101.StopStreaming();
                btnMonitoring.Text = StartString;

                foreach (Control c in streamingControls)
                    c.Enabled = true;
                
                if (StreamingStartStop != null)
                    StreamingStartStop(this, new StreamingStartStopEventArgs() { state = false });
            }
        }

        private void btnMonitoring_Click(object sender, EventArgs e)
        {
            //if (Connected)
            streamingStartStop();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            timer.Start();
        }

        private void button1_Click_1(object sender, EventArgs e)
        {
            /* Debug*/
            //int count = chartLED.Series["SeriesRed"].Points.Count;
            //int count2 = chartAccel.Series["SeriesAccelX"].Points.Count;
            UpdateXYZChartScale("ChartArea6AccelZ", "SeriesAccelZ");
        }

        private void btnDefaults_Click(object sender, EventArgs e)
        {
            OpticalSensorDefaults();
        }

        private void cboSRPWLed_SelectedIndexChanged(object sender, EventArgs e)
        {
            calculateAvgCurrent();
        }

        private void calculateAvgCurrent()
        {
            double ratio = double.Parse(cboPulseWidth.Text) * 1e-6 * double.Parse(cboSampleRate.Text);
            string _noDataString = "---";
            if (ratio <= 1)
            {
                lblAvgIRLEDcurrent.Text = String.Format("{0:0.00}", double.Parse(cboIRLED.Text) * ratio);
                lblAvgRedLEDcurrent.Text = String.Format("{0:0.00}", double.Parse(cboRedLED.Text) * ratio);
                // https://jira.maxim-ic.com/browse/OS24EVK-17 Green LED: Multi-LED Mode timeslice; FIFO Read Red, IR, Green
                lblAvgGreenLEDcurrent.Text = String.Format("{0:0.00}", double.Parse(cboGreenLED.Text) * ratio);
                // https://jira.maxim-ic.com/browse/OS24EVK-32 update lblAvgPilotPALEDcurrent from cboPilotPA similar to lblAvgGreenLEDcurrent
                //lblAvgPilotPALEDcurrent.Text = String.Format("{0:0.00}", double.Parse(cboPilotPA.Text) * ratio);
            }
            else
            {
                lblAvgIRLEDcurrent.Text = _noDataString;
                lblAvgRedLEDcurrent.Text = _noDataString;
                // https://jira.maxim-ic.com/browse/OS24EVK-17 Green LED: Multi-LED Mode timeslice; FIFO Read Red, IR, Green
                lblAvgGreenLEDcurrent.Text = _noDataString;
                // https://jira.maxim-ic.com/browse/OS24EVK-32 update lblAvgPilotPALEDcurrent from cboPilotPA similar to lblAvgGreenLEDcurrent
                lblAvgPilotPALEDcurrent.Text = _noDataString;
            }
        }

        void cboSampleRate_SelectedIndexChanged(object sender, EventArgs e)
        {
            if ((string)cboSampleRate.SelectedItem == "100" && (string)cboSampleAvg.SelectedItem == "1")
                grpAlgorithm.Enabled = true;
            else
                grpAlgorithm.Enabled = false;

            sampleRate = Int32.Parse((string)cboSampleRate.SelectedItem);
            sampleAverage = Int32.Parse((string)cboSampleAvg.SelectedItem);
        }

        public string SettingsString()
        {
            StringBuilder strBuilder = new StringBuilder();

            strBuilder.Append("% ");
            strBuilder.Append("Mode = ");
            strBuilder.Append(cboMode.SelectedItem);
            strBuilder.Append(Environment.NewLine);

            strBuilder.Append("% ");
            strBuilder.Append("Sample Rate (Hz) = ");
            strBuilder.Append(cboSampleRate.SelectedItem);
            strBuilder.Append(" Sample Average = ");
            strBuilder.Append(cboSampleAvg.SelectedItem);
            strBuilder.Append(" Pulse Width (us) = ");
            strBuilder.Append(cboPulseWidth.SelectedItem);
            strBuilder.Append(" ADC Full Scale Range (nA) = ");
            strBuilder.Append(cboADCFSrangenA.SelectedItem);
            strBuilder.Append(Environment.NewLine);

            strBuilder.Append("% ");
            strBuilder.Append(" LED Red (mA) = ");
            strBuilder.Append(cboRedLED.SelectedItem);
            strBuilder.Append(" LED IR (mA) = ");
            strBuilder.Append(cboIRLED.SelectedItem);
            strBuilder.Append(" LED Green (mA) = ");
            strBuilder.Append(cboGreenLED.SelectedItem);
            strBuilder.Append(Environment.NewLine);

            strBuilder.Append("% ");
            strBuilder.Append("LED Slot 1 = ");
            strBuilder.Append(cboLEDslot1.SelectedIndex);
            strBuilder.Append(" LED Slot 2 = ");
            strBuilder.Append(cboLEDslot2.SelectedIndex);
            strBuilder.Append(" LED Slot 3 = ");
            strBuilder.Append(cboLEDslot3.SelectedIndex);
            strBuilder.Append(" LED Slot 4 = ");
            strBuilder.Append(cboLEDslot4.SelectedIndex);

            return strBuilder.ToString();
        }

        public string AccelSettingString()
        {
            return "% Sample Rate (Hz) = " + AccelSampleRate;
        }

        public InitArgs.HRModeInitStart GetHRModeArgs()
        {
            InitArgs.HRModeInitStart args = new InitArgs.HRModeInitStart();

            args.FifoWaterlevelMark = fifo_waterlevel_mark;
            args.SampleRate = (byte)cboSampleRate.SelectedIndex;
            args.SampleAverage = (byte)cboSampleAvg.SelectedIndex;
            args.PulseWidth = (byte)cboPulseWidth.SelectedIndex;
            args.RedLedCurrent = (byte)cboRedLED.SelectedIndex;

            return args;
        }

        public InitArgs.SpO2HRModeInitStart GetSpO2HRModeArgs()
        {
            InitArgs.SpO2HRModeInitStart args = new InitArgs.SpO2HRModeInitStart();

            args.FifoWaterlevelMark = fifo_waterlevel_mark;
            args.SampleRate = (byte)cboSampleRate.SelectedIndex;
            args.SampleAverage = (byte)cboSampleAvg.SelectedIndex;
            args.PulseWidth = (byte)cboPulseWidth.SelectedIndex;
            args.RedLedCurrent = (byte)cboRedLED.SelectedIndex;
            args.IRLedCurrent = (byte)cboIRLED.SelectedIndex;

            return args;
        }

        public InitArgs.MultiModeInitStart GetMultiModeArgs()
        {
            InitArgs.MultiModeInitStart args = new InitArgs.MultiModeInitStart();

            args.FifoWaterlevelMark = fifo_waterlevel_mark;
            args.SampleRate = (byte)cboSampleRate.SelectedIndex;
            args.SampleAverage = (byte)cboSampleAvg.SelectedIndex;
            args.PulseWidth = (byte)cboPulseWidth.SelectedIndex;
            args.RedLedCurrent = (byte)cboRedLED.SelectedIndex;
            args.IRLedCurrent = (byte)cboIRLED.SelectedIndex;
            args.GreenLedCurrent = (byte)cboGreenLED.SelectedIndex;
            args.Slot1 = (byte)cboLEDslot1.SelectedIndex;
            args.Slot2 = (byte)cboLEDslot2.SelectedIndex;
            args.Slot3 = (byte)cboLEDslot3.SelectedIndex;
            args.Slot4 = (byte)cboLEDslot4.SelectedIndex;

            return args;
        }

        public void SetHRModeArgs(InitArgs.HRModeInitStart args)
        {
            cboSampleRate.SelectedIndex = args.SampleRate; 
            cboSampleAvg.SelectedIndex = args.SampleAverage;
            cboPulseWidth.SelectedIndex = args.PulseWidth;
            cboRedLED.SelectedIndex = args.RedLedCurrent;
        }
        
        public void SetSpO2HRModeArgs(InitArgs.SpO2HRModeInitStart args)
        {
            cboSampleRate.SelectedIndex = args.SampleRate; 
            cboSampleAvg.SelectedIndex = args.SampleAverage;
            cboPulseWidth.SelectedIndex = args.PulseWidth;
            cboRedLED.SelectedIndex = args.RedLedCurrent;
            cboIRLED.SelectedIndex = args.IRLedCurrent;
        }

        public void SetMultiModeArgs(InitArgs.MultiModeInitStart args)
        {
            cboSampleRate.SelectedIndex = args.SampleRate;
            cboSampleAvg.SelectedIndex = args.SampleAverage;
            cboPulseWidth.SelectedIndex = args.PulseWidth;
            cboRedLED.SelectedIndex = args.RedLedCurrent;
            cboIRLED.SelectedIndex = args.IRLedCurrent;
            cboGreenLED.SelectedIndex = args.GreenLedCurrent;
            cboLEDslot1.SelectedIndex = args.Slot1;
            cboLEDslot2.SelectedIndex = args.Slot2;
            cboLEDslot3.SelectedIndex = args.Slot3;
            cboLEDslot4.SelectedIndex = args.Slot4;
        }

        class MedianFilter
        {
            Queue<int> items;
            int depth;

            public MedianFilter(int depth)
            {
                items = new Queue<int>(depth + 1);
                this.depth = depth;
            }

            public int Filter(int current)
            {
                int last;

                items.Enqueue(current);
                if (items.Count > depth)
                {
                    items.Dequeue();
                }

                last = items.Peek();

                if (items.Count < depth)
                {
                    return current;
                }
                else
                {
                    int count;
                    int[] arrayItems = items.ToArray();
                    int maxCount = 0;
                    int maxKey = 0;

                    Dictionary<int, int> valueOccurance = new Dictionary<int, int>();

                    for (int i = 0; i < arrayItems.Length; i++)
                    {
                        bool exists = valueOccurance.TryGetValue(arrayItems[i], out count);
                        if (exists)
                        {
                            count++;
                            valueOccurance.Remove(arrayItems[i]);
                            valueOccurance.Add(arrayItems[i], count);
                        }
                        else
                        {
                            valueOccurance.Add(arrayItems[i], 1);
                        }
                    }

                    foreach (KeyValuePair<int, int> item in valueOccurance)
                    {
                        if (item.Key > maxCount)
                        {
                            maxKey = item.Key;
                        }
                    }

                    return maxKey;
                }

            }
        }

        /*
        public class StreamingStartStopEventArgs : EventArgs
        {
            public bool state { get; set; }
        }*/

    }
}
