/*******************************************************************************
* 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 CES_DEMO
//#define FILE_LOG

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using RPCSupport.Streaming;
using RPCSupport.Devices;

using HealthSensorPlatform.View;
using HealthSensorPlatform.Presenter;
using HealthSensorPlatform.Model;

#if CES_DEMO
using System.Collections;
#endif

// DEBUG
using System.IO;


namespace HealthSensorPlatform.CustomControls
{
    public partial class EcgView : UserControl, IDeviceView, IEcgView
    {
        /* Constants */
        const int Rbias_FMSTR_Init = 0;
        const int CAL_init = 1;
        const int ECG_InitStart = 2;
        const int PACE_InitStart = 3;
        const int BIOZ_InitStart = 4;
        const int ECGFast_Init = 5;
        const int RtoR_InitStart = 6;

        const int RToRMaxValue = 0x3FFF;

#if CES_DEMO
        const int[] averageOptions = new int[] { 1, 2, 4, 8, 10 };
#endif

        /* Fields */
        public InitArgs.RbiasInit rbiasInit;
        public InitArgs.EcgInitStart ecgArgs;
        public InitArgs.EcgFastInit ecgFastArgs;
        public InitArgs.PACEInitStart paceArgs;
        public InitArgs.BIOZInitStart biozArgs;
        public InitArgs.RToRInitStart rtorArgs;
        public InitArgs.CalInitStart calArgs;

        public GetEcgInitArgs GetEcgInitArgsPointer;
        public GetRToRInitArgs GetRToRInitArgsPointer;
        public GetBioZInitArgs GetBioZInitArgsPointer;
        public GetPaceInitArgs GetPaceInitArgsPointer;

        /// <summary>
        /// Number of R To R points which need to be removed from the chart
        /// </summary>
        int rToRRemoveOffset;

        long lastRToRTime;

        double timeResolution = 0.00001525878; 

        double[] clockFreq = { 32768, 32000, 32000, 32768 * 640 / 656 }; // 31968.7804878 = 32768 * 640 / 656

        int frequencyMasterField = 0;

        public double masterClockFrequency = 32768;

#if CES_DEMO
        public int AverageECG = 1;
#endif

        private RPCSupport.RPCClient rpcClient;
        private bool connected = false;
        //private bool stream = true;

        /// <summary>
        /// Use to scale the bioZ chart data to milliohm range
        /// </summary>
        private int bioZRangeMultiplier = 1;

        bool startedCal = false;
        bool startedEcg = false;
        bool startedPace = false;
        bool startedBioz = false;
        bool startedRtoR = false;

        private bool enableECG = false;
        private bool enableRToR = false;
        private bool enableBioZ = false;
        private bool enablePace = false;

        int rToRInterval = -1;

        ChartInfo ecgInfo;
        ChartInfo rToRInfo;
        ChartInfo bioZInfo; 
        ChartInfo paceInfo;

        Queue<int> paceTag = new Queue<int>();
        public PaceData paceData; // TODO: public for testing only
        RToRCalculator rToRCalculator;

        List<double> heartRateList = new List<double>(10);

        /* CES DEMO average feature, normal operation average is 1 */
        //int averageEcgCount = 0;
        //int averageEcgSum = 0;

        StringBuilder fileLogData = new StringBuilder();

		int[, ,] ecgDecimationDelay = new int[,,] { {{650, 1034}, {2922, 3690}, {3370, 4906}, {3370, 4906}}, 
													{{650, 1034}, {2922, 3690}, {3370, 4906}, {3370, 4906}},
													{{1242, 2202}, {1242, 2202}, {1242, 2202}, {1242, 2202}},
													{{1242, 2202}, {1242, 2202}, {1242, 2202}, {1242, 2202}}
                                                  };

        public int RToRWindowField = 0;
		
#if CES_DEMO
        FirFilter hpf;
#endif

#if FILE_LOG
        // DEBUG
        StreamWriter file; //= new StreamWriter("hsp_ecg_output_data.csv");
#endif

        /* Constructor */
        public EcgView()
        {
            InitializeComponent();

            //paceInfo = ecgInfo;

            /*foreach(int item in averageOptions)
            {
                cboAverageEcg.Items.Add(item.ToString());
            }*/
            //cboAverageEcg.SelectedIndex = 0;
            //cboMaxScale.SelectedIndex = 1;

            chartRtoR.ChartAreas[0].AxisY.Maximum = 100;

            ecgInfo = new ChartInfo
            {
                SampleRate = 128, // Power on defaults for ECG
                Length = 10,
                Average = 1,
                Shift = 6,
                Gain = 20,
                // ECG is 18 bits, with MSB as sign bit
                Threshold = 0x1ffff,
                Offset = 0x40000,
                Chart = chartECG,
                SeriesName = "Series",
            };
            rToRInfo = new ChartInfo
            {
                SampleRate = 128,
                Length = 10,
                Chart = chartECG,
                SeriesName = "SeriesRToR",
            };
            bioZInfo = new ChartInfo
            {
                SampleRate = 64,
                Length = 10,
                Shift = 4,
                Gain = 20,
                // BioZ is 20 bits, with MSB as sign bit
                Threshold = 0x7ffff,
                Offset = 0x100000,
                Chart = chartBioz,
                SeriesName = "Series",
            };
            paceInfo = new ChartInfo
            {
                SampleRate = 65536 * ecgInfo.SampleRate, // 2 * fMSTR
                Length = 10,
                Average = 1,
                Shift = 0, // Keep tag info
                Chart = chartECG,
                SeriesName = "SeriesPace",
            };

            NamedImage paceFalling = new NamedImage("PaceFallingImage", HealthSensorPlatform.Properties.Resources.pace_falling);
            chartECG.Images.Add(paceFalling);
            NamedImage paceRising = new NamedImage("PaceRisingImage", HealthSensorPlatform.Properties.Resources.pace_rising);
            chartECG.Images.Add(paceRising);
#if CES_DEMO
            //grpAverage.Visible = true;

            hpf = new FirFilter();
            //cboEcgHpf.SelectedIndex = 0;
#endif
        }

        /* Delegates */
        public delegate InitArgs.EcgInitStart GetEcgInitArgs();
        public delegate InitArgs.RToRInitStart GetRToRInitArgs();
        public delegate InitArgs.BIOZInitStart GetBioZInitArgs();
        public delegate InitArgs.PACEInitStart GetPaceInitArgs();
        //public delegate void StreamingStartStopEventHandler(StreamingStartStopEventArgs e);

        /* Events */
        /// <summary>
        /// Streaming event 
        /// </summary>
        public event EventHandler<StreamingStartStopEventArgs> StreamingStartStop;

        /* Enums */
        public enum StreamDataType
        {
            Ecg,
            RToR,
            Pace,
            BioZ
        };

        /* Properties */
        public RPCSupport.RPCClient RPCClient
        {
            set
            {
                rpcClient = value;
                //streamHandler = new EventHandler<PartialArrayIntAvailableEventArgs>(On_AppendChart);
                //rpcClient.streaming.PartialArrayIntAvailable += streamHandler;
            }
        }
        /*
        public bool Stream
        {
            get
            {
                return stream;
            }
            set
            {
                stream = value;
                if (stream == true)
                    rpcClient.streaming.PartialArrayIntAvailable += streamHandler;
                else
                    rpcClient.streaming.PartialArrayIntAvailable -= streamHandler;
            }
        }
*/
        public bool Connected
        {
            get
            {
                return connected;
            }
            set
            {
                connected = value;
            }
        }
        public bool EnableECG
        {
            get
            {
                return enableECG;
            }
            set
            {
                enableECG = value;
                chartECG.Enabled = value;
            }
        }
        public bool EnableRToR
        {
            get
            {
                return enableRToR;
            }
            set
            {
                enableRToR = value;
                chartRtoR.Enabled = value;
                grpBxHeartRate.Enabled = value;
            }
        }
        public bool EnableBioZ
        {
            get
            {
                return enableBioZ;
            }
            set
            {
                enableBioZ = value;
                chartBioz.Enabled = value;
            }
        }
        public bool EnablePace
        {
            get
            {
                return enablePace;
            }
            set
            {
                enablePace = value;
                chartPace.Enabled = value;
            }
        }
        public bool EnableDCLeadOff
        {
            get
            {
                return grpBxDCLeadOff.Enabled;
            }
            set
            {
                grpBxDCLeadOff.Enabled = value;
            }

        }
        public bool EnableEcgDCLeadOff { get; set; }
        public bool EnableBioZOverUnderRange
        {
            get
            {
                return grpBxBioZOverUnderRange.Enabled;
            }
            set
            {
                grpBxBioZOverUnderRange.Enabled = value;
            }
        }

        public bool BioZMilliOhmRange
        {
            get
            {
                return (bioZRangeMultiplier == 1000);
            }

            set
            {
                if (value)
                {
                    bioZRangeMultiplier = 1000;
                    chartBioz.ChartAreas["ChartArea"].AxisY.Title = "Bioz (m‎Ω)";
                }
                else
                {
                    bioZRangeMultiplier = 1;
                    chartBioz.ChartAreas["ChartArea"].AxisY.Title = "Bioz (‎Ω)";
                }
            }
        }

        public double SampleRateEcg // Human readable sample rate
        {
            get
            {
                return ecgInfo.SampleRate;
            }
            set
            {
                ecgInfo.SampleRate = value;
                //rToRInfo.SampleRate = value;
                paceInfo.SampleRate = MasterClockFrequency * 2 * value;
            }
        }
        public double SampleRateBioZ // Human readable sample rate
        {
            get
            {
                return bioZInfo.SampleRate;
            }
            set
            {
                bioZInfo.SampleRate = value;
            }
        }
        public int GainECG // Human readable gain
        {
            get
            {
                return ecgInfo.Gain;
            }
            set
            {
                ecgInfo.Gain = value;
            }
        }
        public int GainBioZ // Human readable gain
        {
            get
            {
                return bioZInfo.Gain;
            }
            set
            {
                bioZInfo.Gain = value;
            }
        }
        public int CurrentBioZ // Human readable bioZ current generator magnitude
        {
            get
            {
                return bioZInfo.CurrentGenerator;
            }
            set
            {
                bioZInfo.CurrentGenerator = value;
            }
        }
        /// <summary>
        /// This value is t_RES = 1 / (2 * fMSTR) = 1 / (2 * 32768). For R-to-R the LSB time is 512*t_RES, while for
        /// PACE, the value is t_RES.
        /// </summary>
        public double TimeResolution
        {
            get
            {
                return timeResolution;
            }
        }

        public ChartInfo EcgInfo { get { return ecgInfo; } }
        public ChartInfo RToRInfo { get { return rToRInfo; } }
        public ChartInfo BioZInfo { get { return bioZInfo; } }
        public ChartInfo PaceInfo { get { return paceInfo; } }

        public InitArgs.EcgInitStart EcgArgs { get { return ecgArgs; } }
        public InitArgs.RToRInitStart RToRArgs { get { return rtorArgs; } }

        public int MasterClockField { get { return frequencyMasterField; } }
        public int FrequencyMasterField
        {
            get
            {
                return frequencyMasterField;
            }

            set
            {
                frequencyMasterField = value;
                masterClockFrequency = clockFreq[frequencyMasterField];
                paceInfo.SampleRate = 2 * masterClockFrequency;
                rToRInfo.SampleRate = masterClockFrequency / 256.0;
                timeResolution = 1.0 / (2 * masterClockFrequency);
            }
        }
        public double MasterClockFrequency
        {
            get
            {
                return masterClockFrequency;
            }
            /*
            set
            {
                masterClockFrequency = value;
                paceInfo.SampleRate = 2 * value;
                TimeResolution = 1 / (2 * value);
            }*/
        }

        public int EcgDecimationDelay
        {
            get
            {
                int fmstr = frequencyMasterField;
                int ecgRate = ecgArgs.Rate;
                int lpf = ecgArgs.Dlpf > 0 ? 1 : 0;

                return ecgDecimationDelay[fmstr, ecgRate, lpf];
            }
        }
        public int RToRDelay
        {
            get
            {

                return 5376 + 3370 + 256 * RToRWindowField;
            }
        }

        public Chart ChartECG { get { return chartECG; } } // Testing
        public IHspSetting HspSetting
        {
            get;
            set;
        }

        private void EcgView_Load(object sender, EventArgs e)
        {
            InitChart();
        }

        String ValueToBits(int value, int width)
        {
            String str = "";
            String val = "";
            int mask = 1;
            for (int i = 0; i < width; i++)
            {
                if ((mask & value) == mask)
                {
                    val = "1";
                }
                else
                {
                    val = "0";
                }
                mask = mask << 1;
                str = val + str;
            }
            return str;
        }

        String[] ValueToBitStringArray(int width)
        {
            List<String> stringList = new List<string>();
            for (int i = 0; i < Math.Pow(2, width); i++)
            {
                stringList.Add(ValueToBits(i, width));
            }
            return stringList.ToArray();
        }

        private void InitChart()
        {
            // Reset Averaging
            //averageEcgSum = 0;
            //averageEcgCount = 0;

            //ecgInfo.Average = averageOptions[cboAverageEcg.SelectedIndex];
            ecgInfo.Average = 1; 

            // Rest Pace Tags
            paceTag.Clear();

            //UpdateChart(chartPace, new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
            //paceData = new PaceData(new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 });
            //UpdateChart(StreamDataType.Ecg, new int[] {1});
            DisplayEcg(new double[] { 0 }, null, null);
            //UpdateChart(chartECG, "SeriesRToR", new int[] {0});
            //UpdateChart(StreamDataType.BioZ, new int[] {0});
            DisplayBioZ(new double[] { 0 });
            UpdateChartAxis(chartECG);
            //UpdateChartAxis(chartRtoR);
            UpdateChartAxis(chartBioz);
            //UpdateChartAxis(chartPace);

            paceData = null;
            
            //chartRtoR.ChartAreas[0].AxisY.Maximum = 100;
        }

        private void ResetChart()
        {
            paceData = null;
        }

        private void UpdateChartAxis(System.Windows.Forms.DataVisualization.Charting.Chart chart)
        {
            ChartInfo chartInfo = new ChartInfo();

            if (chart.Name == "chartECG")
            {
                chartInfo = ecgInfo;
            }
            else if (chart.Name == "chartBioz")
            {
                chartInfo = bioZInfo;
            }
            else if (chart.Name == "chartPace")
            {
                chartInfo = paceInfo;
            }
            else if (chart.Name == "chartRtoR")
            {
                chartInfo = rToRInfo;
            }

            chart.SuspendLayout();
            chart.ChartAreas[0].AxisX.Minimum = 0;
            chart.ChartAreas[0].AxisX.Maximum = chartInfo.Points; 
            chart.ChartAreas[0].AxisX.Interval = (float)chartInfo.Points / chartInfo.Length;

            if (chart.Name != "chartRtoR")
            {
                chart.ChartAreas[0].AxisX.CustomLabels.Clear();
                // Set X-axis range in seconds
                for (int i = 0; i < 11; i ++ )
                {
                    chart.ChartAreas[0].AxisX.CustomLabels.Add(Math.Ceiling(((float)chartInfo.SampleRate / chartInfo.Average) * (2 * i - 1) / 2), 
                        Math.Floor(((float)chartInfo.SampleRate / chartInfo.Average) * (2 * i + 1) / 2), i.ToString());
                }
            }

            chart.ResumeLayout();
        }

        public void DisplayEcg(double[] ecg, PacePoint[] pace, double[] rToR)
        {
            List<double> xList = new List<double>();
            List<double> yList = new List<double>();
            List<double> xListFalling = new List<double>();
            List<double> yListFalling = new List<double>();

            int offset = chartECG.Series["Series"].Points.Count; // Pace Offset

            chartECG.SuspendLayout();

            for(int i = 0; i < ecg.Length; i++ )
            {
                chartECG.Series["Series"].Points.Add(ecg[i]);
            }

            if (pace != null)
            {
                // Shift Old Points - Rising
                for (int i = 0; i < chartECG.Series["SeriesPace"].Points.Count; i++)
                {
                    double xValue = chartECG.Series["SeriesPace"].Points[i].XValue;

                    if (offset > ecgInfo.Points) // Do not shift until chart is full
                        xValue -= ecg.Length;

                    if (xValue >= 0)
                    {
                        xList.Add(xValue);
                        yList.Add(chartECG.Series["SeriesPace"].Points[i].YValues[0]);
                    }
                }
                // Shift Old Points - Falling
                for (int i = 0; i < chartECG.Series["SeriesPaceFalling"].Points.Count; i++ )
                {
                    double xValueFalling = chartECG.Series["SeriesPaceFalling"].Points[i].XValue;

                    if (offset > ecgInfo.Points)
                        xValueFalling -= ecg.Length;

                    if (xValueFalling >= 0)
                    {
                        xListFalling.Add(xValueFalling);
                        yListFalling.Add(chartECG.Series["SeriesPaceFalling"].Points[i].YValues[0]);
                    }
                }

                // Add New Points
                for (int i = 0; i < pace.Length; i++)
                {
                    if (pace[i].Polarity == true)
                    {
                        //xList.Add(pace[i].Time + offset);
                        //yList.Add(0.05);
                        chartECG.Series["SeriesPace"].Points.AddXY(pace[i].Time + offset, 0.05*(0+1));
                        //System.Diagnostics.Debug.Print("Rising: " + (pace[i].Time + offset));
                    }
                    else
                    {
                        //xListFalling.Add(pace[i].Time + offset);
                        //yListFalling.Add(-0.05);
                        chartECG.Series["SeriesPaceFalling"].Points.AddXY(pace[i].Time + offset, -0.08*(0+1));
                        //System.Diagnostics.Debug.Print("Falling: " + (pace[i].Time + offset));
                    }
                }

                // Bind data to chart
                //chartECG.Series["SeriesPace"].Points.DataBindXY(xList, yList);
                //chartECG.Series["SeriesPaceFalling"].Points.DataBindXY(xListFalling, yListFalling);
            }

            UpdateChartFormat(StreamDataType.Ecg, ecgInfo);
            //if (rToR != null) // Remove extra points
            //    UpdateChartFormat(StreamDataType.RToR, rToRInfo);
            if (pace != null) // Not needed anymore due to new plot style
                UpdateChartFormat(StreamDataType.Pace, PaceInfo, offset + ecg.Length - ecgInfo.Points);
            chartECG.ResumeLayout();
        }

        public void DisplayBioZ(double[] bioZ)
        {
            chartBioz.SuspendLayout();

            for(int i = 0; i < bioZ.Length; i++)
            {
                chartBioz.Series["Series"].Points.Add(bioZ[i] * bioZRangeMultiplier);
            }

            UpdateChartFormat(StreamDataType.BioZ, bioZInfo);

            chartBioz.ResumeLayout();
        }

        public double DisplayRToR(int rToR)
        {
            double rate;
            int rToREcg;

            long now = DateTime.Now.Ticks;

            if (rToR == RToRMaxValue) // Ignore the max value, counter overflow condition
                return 0;

            rate = rToRCalculator.BeatsPerMinute(rToR); // RToRBeatsPerMinute(rToR);

            if (heartRateList.Count < 10)
                heartRateList.Add(rate);
            else if (heartRateList.Count == 10)
            {
                heartRateList.RemoveAt(0);
                heartRateList.Add(rate);

                double sum = 0;
                foreach (double d in heartRateList)
                    sum += d;

                lblHeartRateAverage10.Text = (sum / 10).ToString("F1");

            }

            lblHeartRateBeatToBeat.Text = rate.ToString("F1");

            if (heartRateList.Count != 1)
            {
                rToREcg = rToRCalculator.EcgPoints(rToR, false); // RToREcgPoints(rToR, false);
            }
            else
            {
                // First Point delay by Tdrtor_ecg
                //rToREcg = RToREcgPoints(rToR, true) + 1; // DEBUG 3 extra 0x07 are sent from FW at start of stream, 1 extra point for GUI chart init
                int rToRInEcg = rToRCalculator.EcgPoints(rToR, true);
                rToREcg = rToRInEcg + 1;
            }

            if (lastRToRTime < 0)
                lastRToRTime = now;
            else
            {
                long diff = now - lastRToRTime;
                TimeSpan elaspedTicks = new TimeSpan(diff);

                int fullScaleTime = (int)(Math.Pow(2, 14) * 512 * TimeResolution + 0.5);

                if (elaspedTicks.TotalSeconds > fullScaleTime)
                {
                    int overflowCount = (int)(elaspedTicks.TotalSeconds / fullScaleTime);

                    for (int i = 0; i < overflowCount; i++ )
                        rToREcg += rToRCalculator.EcgPoints(EcgDelay.RToRMaxValue, false); //RToREcgPoints(0x3FFF, false);
                }

                lastRToRTime = now;
            }

            for (int i = 1; i < rToREcg - rToRRemoveOffset; i++ )
            {
                chartECG.Series["SeriesRToR"].Points.Add(-5000);
            }
            int rToRCount = chartECG.Series["SeriesRToR"].Points.Count;

            if (rToRCount < chartECG.Series["Series"].Points.Count)
                chartECG.Series["SeriesRToR"].Points.Add(chartECG.Series["Series"].Points[rToRCount].YValues[0]); // R to R comes in after ECG
            else
                chartECG.Series["SeriesRToR"].Points.Add(0); // R to R comes in faster than ECG, add as 0 for now

            rToRRemoveOffset = 0;

            return rate;
        }

        public void BioZFunction(bool enable)
        {
            chartBioz.Visible = enable;

            if (enable == false)
            {
                //tableLayoutPanelCharts.RowCount = 1;
                tableLayoutPanelCharts.RowStyles[1] = new RowStyle(SizeType.Percent, 0);
                tableLayoutPanelCharts.RowStyles[0] = new RowStyle(SizeType.Percent, 100);
            }
            else
            {
                tableLayoutPanelCharts.RowStyles[0] = new RowStyle(SizeType.Percent, 50);
                tableLayoutPanelCharts.RowStyles[1] = new RowStyle(SizeType.Percent, 50);
            }

        }


        /*
        public double RToRBeatsPerMinute(int rToRCode)
        {
            return 60 * 1000/ RToRMillisecond(rToRCode);
        }

        public double RToRMillisecond(int rToRCode)
        {
            return rToRCode * 512.0 * 1000 / (2 * MasterClockFrequency);
        }

        public int RToREcgPoints(int rToR, bool first)
        {
            double sampleRateRToR = MasterClockFrequency / 256;
            int rToRDifferentialDelay = RToRDelay - EcgDecimationDelay;

            double rToRInEcgSamples;
            int rToRInEcgSamplesInt;

            if (first)
            {
                rToRInEcgSamples = (rToR - rToRDifferentialDelay / 256.0) * (SampleRateECG / sampleRateRToR);
                rToRInEcgSamplesInt = (int)(rToRInEcgSamples + 0.5);

                rToRInEcgSampleError = rToRInEcgSamplesInt - rToRInEcgSamples;

                return rToRInEcgSamplesInt;
            }
            else
            {
                rToRInEcgSamples = rToR * (SampleRateECG / sampleRateRToR) - rToRInEcgSampleError;
                rToRInEcgSamplesInt = (int)(rToRInEcgSamples + 0.5);

                rToRInEcgSampleError = rToRInEcgSamplesInt - rToRInEcgSamples;

                return rToRInEcgSamplesInt;

            }
        }
        */

        private double[] UpdateChartFormat(StreamDataType dataType, ChartInfo chartInfo)
        {
            Chart chart = null;
            String series = "";
            double[] chartData;
            int minRound = 0, maxRound = 0;
            double min = Double.MaxValue, max = Double.MinValue;
            int count;
            
            switch (dataType)
            {
                case StreamDataType.Ecg:
                    chart = chartECG;
                    series = "Series";
                    break;
                case StreamDataType.BioZ:
                    chart = chartBioz;
                    series = "Series";
                    break;
                case StreamDataType.RToR:
                    chart = chartECG;
                    series = "SeriesRToR";
                    break;
                case StreamDataType.Pace:
                    chart = chartECG;
                    series = "SeriesPace";
                    break;

            }
            
            count = chart.Series[series].Points.Count;

            // Remove extra points
            while (count > chartInfo.Points)
            {
                chart.Series[series].Points.RemoveAt(0);
                if (dataType == StreamDataType.Ecg && EnableRToR) // Scroll R To R with ECG
                    if (chart.Series["SeriesRToR"].Points.Count > 0) // TODO
                        chart.Series["SeriesRToR"].Points.RemoveAt(0);
                    else
                        rToRRemoveOffset++;

                count = chart.Series[series].Points.Count;
            }

            chartData = new double[chart.Series[series].Points.Count];

            // Copy data points and find min/max value
            for (int i = count / 2; i < count; i++) // Autoscale on last half of data only
            {
                chartData[i] = chart.Series[series].Points[i].YValues[0];

                if (chartData[i] < min)
                    min = chartData[i];
                if (chartData[i] > max)
                    max = chartData[i];
            }

            if (min == max) // prevent any invalid ranges
                max = max + 1;

            if (chartInfo == ecgInfo)
            {
                // Round to 1mV for ECG
                minRound = ((int)(min / 1) - 1) * 1;
                maxRound = ((int)(max / 1) + 1) * 1;

                if (minRound == -1 && maxRound == 1) // Manual control of auto interval
                    chart.ChartAreas[0].AxisY.Interval = 0.5;
                else
                    chart.ChartAreas[0].AxisY.Interval = 0;
            }
            else if (chartInfo == bioZInfo)
            {
                // Round to 100's for automatic axis scaling - for raw codes
                minRound = (((int)min / 100 - 1) * 100);
                maxRound = (((int)max / 100 + 1) * 100);
            }
            /*else if (chartInfo == rToRInfo)
            {
                // Round to 100ms
                minRound = 0; // R to R rate should never be negative;
                maxRound = ((int)(max / 100) + 1) * 100;
            }
            else
            {
                // Round to 100's for automatic axis scaling - for us of Pace length
                minRound = (((int)min / 100 - 1) * 100);
                maxRound = (((int)max / 100 + 1) * 100);
            }*/

            if (chartInfo == ecgInfo || chartInfo == bioZInfo)
            {
                // Set full Y-axis range
                chart.ChartAreas[0].AxisY.Minimum = minRound;
                chart.ChartAreas[0].AxisY.Maximum = maxRound;
            }

            return chartData;
        }

        private double[] UpdateChartFormat(StreamDataType dataType, ChartInfo chartInfo, int shift)
        {
            List<double> xList = new List<double>();
            List<double> yList = new List<double>();
            List<double> xListFalling = new List<double>();
            List<double> yListFalling = new List<double>();

            int offset = chartECG.Series["Series"].Points.Count; // Pace Offset

            // Shift Old Points - Rising
            for (int i = 0; i < chartECG.Series["SeriesPace"].Points.Count; i++)
            {
                double xValue = chartECG.Series["SeriesPace"].Points[i].XValue;

                if (offset >= ecgInfo.Points) // Do not shift until chart is full
                    xValue -= shift;

                if (xValue >= 0)
                {
                    xList.Add(xValue);
                    yList.Add(chartECG.Series["SeriesPace"].Points[i].YValues[0]);
                }
            }
            // Shift Old Points - Falling
            for (int i = 0; i < chartECG.Series["SeriesPaceFalling"].Points.Count; i++)
            {
                double xValueFalling = chartECG.Series["SeriesPaceFalling"].Points[i].XValue;

                if (offset >= ecgInfo.Points)
                    xValueFalling -= shift;

                if (xValueFalling >= 0)
                {
                    xListFalling.Add(xValueFalling);
                    yListFalling.Add(chartECG.Series["SeriesPaceFalling"].Points[i].YValues[0]);
                }
            }

            chartECG.Series["SeriesPace"].Points.DataBindXY(xList, yList);
            chartECG.Series["SeriesPaceFalling"].Points.DataBindXY(xListFalling, yListFalling);

            return yList.ToArray();
        }

        /*
        public EcgFifo[] ConvertEcg(int[] data)
        {
            EcgFifo[] voltage = new EcgFifo[data.Length];
            ChartInfo chartInfo = ecgInfo;
            int dataShift;

            for (int i = 0; i < data.Length; i++ )
            {
                dataShift = data[i] >> chartInfo.Shift;

                // Two's Complement Conversions
                if (dataShift > chartInfo.Threshold)
                {
                    dataShift -= chartInfo.Offset;
                }

                voltage[i].Data = 1000 * 7.62939453125e-6 * dataShift / chartInfo.Gain;
                voltage[i].PTag = data[i] & 0x07;
                voltage[i].ETag = (data[i] >> 3) & 0x07;
            }

            return voltage;
        }

        public BioZFifo[] ConvertBioZ(int[] data)
        {
            BioZFifo[] impedence = new BioZFifo[data.Length];
            ChartInfo chartInfo = bioZInfo;
            int dataShift;
            int dataPoint;

            for (int i = 0; i < data.Length; i ++)
            {
                dataShift = data[i] >> chartInfo.Shift;

                // Two's Complement Conversions
                if (dataShift > chartInfo.Threshold)
                {
                    dataShift -= chartInfo.Offset;
                }

                // 1.9734 = 1/2^19 * 1e-6
                impedence[i].Data = dataShift * bioZRangeMultiplier * 1.9073486328125 / 
                    (chartInfo.Gain * ((chartInfo.CurrentGenerator == 0) ? 1 : chartInfo.CurrentGenerator));
                impedence[i].BTag = data[i] & 0x07;
            }

            return impedence;
        }

        public PaceData ConvertPace(int[] data)
        {
            paceData = new PaceData(data);

            return paceData;
        }

        public double ConvertRToR(int data)
        {
            rToRInterval = data;

            return rToRInterval;
        }
         */

        int[] ProcessRToR(int[] ecgData)
        {
            int[] rToRArray;

            rToRArray = new int[ecgData.Length];
            for (int i = 0; i < rToRArray.Length; i++)
                rToRArray[i] = Int32.MinValue;

            if (rToRInterval > 0) // Valid R to R
            {
                var ecgCode = EcgMaximumCode(ecgData);
                rToRArray[ecgCode.Item1] = ecgCode.Item2;
            }

            return rToRArray;
        }

        public Tuple<int, int> EcgMaximumCode(int[] data)
        {
            int i;
            int max = Int32.MinValue;
            int maxCode = 0;
            int maxIndex = 0;
            int point;

            for (i = 0; i < data.Length; i++ )
            {
                point = data[i] >> ecgInfo.Shift;
                if (data[i] > ecgInfo.Threshold)
                    point -= ecgInfo.Offset;

                if (point > max) // Two's complement offset
                {
                    max = point;
                    maxCode = data[i];
                    maxIndex = i;
                }
            }

            return new Tuple<int, int>(maxIndex, maxCode);
        }

        private bool LeadOffBit(int value, int bit)
        {
            int state;
            int mask = 1 << bit;
            state = ((value & mask) == mask) ? 1 : 0;
            return state == 1;
        }

        //public void On_AppendChart(object sender, PartialArrayIntAvailableEventArgs e)
        //{
            /*
            int leadOffState;

            if (e.array1.Length > 0) // Occurs with streaming from flash
            {
                leadOffState = e.array1[0];

                switch (e.reportID)
                {
                    case PartialArrayIntAvailableEventArgs.PACKET_MAX30001_LEADOFF_DC:
                        if (LeadOffBit(leadOffState, 8)) // check for ECG
                        {
                            UpdateLeadOffState(lblLdoffPh, LeadOffBit(leadOffState, 3));
                            UpdateLeadOffState(lblLdoffPl, LeadOffBit(leadOffState, 2));
                            UpdateLeadOffState(lblLdoffNh, LeadOffBit(leadOffState, 1));
                            UpdateLeadOffState(lblLdoffNl, LeadOffBit(leadOffState, 0));
                        }
                        if (LeadOffBit(leadOffState, 9)) // check for BIOZ
                        {
                            UpdateLeadOffState(lblLdoffPh, LeadOffBit(leadOffState, 3));
                            UpdateLeadOffState(lblLdoffPl, LeadOffBit(leadOffState, 2));
                            UpdateLeadOffState(lblLdoffNh, LeadOffBit(leadOffState, 1));
                            UpdateLeadOffState(lblLdoffNl, LeadOffBit(leadOffState, 0));
                        }
                        break;
                    case PartialArrayIntAvailableEventArgs.PACKET_MAX30001_LEADOFF_AC:
                        UpdateLeadOffState(lblBioZACOver, LeadOffBit(leadOffState, 0));
                        UpdateLeadOffState(lblBioZACUnder, LeadOffBit(leadOffState, 1));
                        break;
                }
            }
            */
            /*
            StringBuilder outputData = new StringBuilder();

            if (e.reportID == PartialArrayIntAvailableEventArgs.PACKET_MAX30001_ECG)
            {
                if (e.array1.Length != 0)
                {
                    if (ecgBuffer != null)
                    { 
                        UpdateChart(StreamDataType.Ecg, ecgBuffer);

                        if (EnableRToR)
                        {
                            int[] rToRArray = ProcessRToR(ecgBuffer);

                            UpdateChart(StreamDataType.RToR, rToRArray);
                            rToRInterval = -1; // R to R processed reset value
                        }
                    }

                    ecgBuffer = ecgBuffer1;
                    ecgBuffer1 = e.array1; 
                    // DEBUG
                    foreach (int data in e.array1)
                    {
                        outputData.Append(data.ToString());
                        outputData.Append(Environment.NewLine);
                    }

                    file.Write(outputData.ToString());

                    ConvertEcg(e.array1);
                }
            }
            if (e.reportID == PartialArrayIntAvailableEventArgs.PACKET_MAX30001_PACE)
            {
                if (e.array1.Length != 0)
                {
                    // Store Pace data for update with ECG PTAGs
                    ConvertPace(e.array1);
                }
            }
            if (e.reportID == PartialArrayIntAvailableEventArgs.PACKET_MAX30001_RTOR)
            {
                if (e.array1.Length != 0)
                {
                    ConvertRToR(e.array1[0]);
                }
            }
            if (e.reportID == PartialArrayIntAvailableEventArgs.PACKET_MAX30001_BIOZ)
            {
                if (e.array1.Length != 0)
                {
                    ConvertBioZ(e.array1);
                    //UpdateChart(StreamDataType.BioZ, e.array1);
                }
            }*/
        //}

        private void Start()
        {
            //ParseRbiasArgs();
            /*rpcClient.MAX30001.Rbias_FMSTR_Init(
                rbiasInit.En_rbias,
                rbiasInit.Rbias,
                rbiasInit.Rbiasp,
                rbiasInit.Rbiasn,
                rbiasInit.Fmstr
            );*/


            //if (ckbxCal.Checked)
            /*{
                startedCal = true;
                //ParseCalArgs();
                calArgs.En_Vcal = 0;
                calArgs.Vmode = 0;
                calArgs.Vmag = 1;
                calArgs.Fcal = 3;
                calArgs.Thigh = 0x1F;
                calArgs.Fifty = 0;
                rpcClient.MAX30001.CAL_InitStart(
                    calArgs.En_Vcal,
                    calArgs.Vmode,
                    calArgs.Vmag,
                    calArgs.Fcal,
                    calArgs.Thigh,
                    calArgs.Fifty);
            }*/

            /*
            rpcClient.MAX30001.INT_assignment(MAX30001.max30001_intrpt_Location.MAX30001_INT_B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT,  //  en_enint_loc,      en_eovf_loc,   en_fstint_loc,
                       MAX30001.max30001_intrpt_Location.MAX30001_INT_2B, MAX30001.max30001_intrpt_Location.MAX30001_INT_B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT,  //  en_dcloffint_loc,  en_bint_loc,   en_bovf_loc,
                       MAX30001.max30001_intrpt_Location.MAX30001_INT_2B, MAX30001.max30001_intrpt_Location.MAX30001_INT_2B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT,  //  en_bover_loc,      en_bundr_loc,  en_bcgmon_loc,
                       MAX30001.max30001_intrpt_Location.MAX30001_INT_B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT,  //  en_pint_loc,       en_povf_loc,   en_pedge_loc,
                       MAX30001.max30001_intrpt_Location.MAX30001_INT_2B, MAX30001.max30001_intrpt_Location.MAX30001_INT_B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT,  //  en_lonint_loc,     en_rrint_loc,  en_samp_loc,
                       MAX30001.max30001_intrpt_type.MAX30001_INT_ODNR, MAX30001.max30001_intrpt_type.MAX30001_INT_ODNR);                //  intb_Type,         int2b_Type)
            */
            if (connected)
            {
                rpcClient.MAX30001.WriteReg(0x02, 0x03); // Stop Interrupts
                rpcClient.MAX30001.WriteReg(0x03, 0x03); // Stop Interrupts
            }

            if (enableECG)    
            {
                startedEcg = true;
                if (GetEcgInitArgsPointer != null)
                {
                    ecgArgs = GetEcgInitArgsPointer();
                    rpcClient.MAX30001.ECG_InitStart(
                        ecgArgs.En_ecg,
                        ecgArgs.Openp,
                        ecgArgs.Openn,
                        ecgArgs.Pol,
                        ecgArgs.Calp_sel,
                        ecgArgs.Caln_sel,
                        ecgArgs.E_fit,
                        ecgArgs.Rate,
                        ecgArgs.Gain,
                        ecgArgs.Dhpf,
                        ecgArgs.Dlpf);
                }

 /*               rpcClient.MAX30001.ECGFast_Init(
                    ecgFastArgs.Clr_Fast,
                    ecgFastArgs.Fast,
                    ecgFastArgs.Fast_Th);*/
            }

            if (enablePace)
            {
                startedPace = true;
                if (GetPaceInitArgsPointer != null)
                {
                    paceArgs = GetPaceInitArgsPointer();
                    rpcClient.MAX30001.PACE_InitStart(
                        paceArgs.En_pace,
                        paceArgs.Clr_pedge,
                        paceArgs.Pol,
                        paceArgs.Gn_diff_off,
                        paceArgs.Gain,
                        paceArgs.Aout_lbw,
                        paceArgs.Aout,
                        paceArgs.Dacp,
                        paceArgs.Dacn);
                }
            }
            if (enableBioZ)
            {
                startedBioz = true;
                if (GetBioZInitArgsPointer != null)
                {
                    biozArgs = GetBioZInitArgsPointer();
                    rpcClient.MAX30001.BIOZ_InitStart(
                        biozArgs.En_bioz,
                        biozArgs.Openp,
                        biozArgs.Openn,
                        biozArgs.Calp_sel,
                        biozArgs.Caln_sel,
                        biozArgs.CG_mode,
                        biozArgs.B_fit,
                        biozArgs.Rate,
                        biozArgs.Ahpf,
                        biozArgs.Ext_rbias,
                        biozArgs.Gain,
                        biozArgs.Dhpf,
                        biozArgs.Dlpf,
                        biozArgs.Fcgen,
                        biozArgs.Cgmon,
                        biozArgs.Cgmag,
                        biozArgs.Phoff);
                }
            }
            if (enableRToR)
            {
                startedRtoR = true;
                if (GetRToRInitArgsPointer != null)
                {
                    rtorArgs = GetRToRInitArgsPointer();
                    rpcClient.MAX30001.RtoR_InitStart(
                        rtorArgs.En_rtor,
                        rtorArgs.Wndw,
                        rtorArgs.Gain,
                        rtorArgs.Pavg,
                        rtorArgs.Ptsf,
                        rtorArgs.Hoff,
                        rtorArgs.Ravg,
                        rtorArgs.Rhsf,
                        rtorArgs.Clr_rrint);
                }

                rToRCalculator = new RToRCalculator(frequencyMasterField, ecgArgs.Rate, ecgArgs.Dlpf, rtorArgs.Wndw);
            }

            /*rpcClient.MAX30001.Rbias_FMSTR_Init(
                0,
                0,
                0,
                0,
                0);*/

            rpcClient.MAX30001.INT_assignment(MAX30001.max30001_intrpt_Location.MAX30001_INT_B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT,  //  en_enint_loc,      en_eovf_loc,   en_fstint_loc,
                      MAX30001.max30001_intrpt_Location.MAX30001_INT_2B, MAX30001.max30001_intrpt_Location.MAX30001_INT_B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT,  //  en_dcloffint_loc,  en_bint_loc,   en_bovf_loc,
                      MAX30001.max30001_intrpt_Location.MAX30001_INT_2B, MAX30001.max30001_intrpt_Location.MAX30001_INT_2B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT,  //  en_bover_loc,      en_bundr_loc,  en_bcgmon_loc,
                      MAX30001.max30001_intrpt_Location.MAX30001_INT_B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT,  //  en_pint_loc,       en_povf_loc,   en_pedge_loc,
                      MAX30001.max30001_intrpt_Location.MAX30001_INT_2B, MAX30001.max30001_intrpt_Location.MAX30001_INT_B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT,  //  en_lonint_loc,     en_rrint_loc,  en_samp_loc,
                      MAX30001.max30001_intrpt_type.MAX30001_INT_ODNR, MAX30001.max30001_intrpt_type.MAX30001_INT_ODNR);                //  intb_Type,         int2b_Type)

            if (startedEcg | startedCal | startedPace | startedBioz | startedRtoR)
            {
                rpcClient.MAX30001.StartStreaming();
            }

            // Clear Lead Off
            UpdateLeadOffState(lblLdoffPh, false);
            UpdateLeadOffState(lblLdoffPl, false);
            UpdateLeadOffState(lblLdoffNh, false);
            UpdateLeadOffState(lblLdoffNl, false);
            UpdateLeadOffState(lblBioZACOver, false);
            UpdateLeadOffState(lblBioZACUnder, false); 

            // Clear Charts 
            chartECG.Series["Series"].Points.Clear();
            chartECG.Series["SeriesRToR"].Points.Clear();
            chartECG.Series["SeriesPace"].Points.Clear();
            chartECG.Series["SeriesPaceFalling"].Points.Clear();
            //chartRtoR.Series["Series"].Points.Clear();
            chartBioz.Series["Series"].Points.Clear();
            //chartPace.Series["Series"].Points.Clear();
            InitChart();
            fileLogData.Clear(); // Clear initiation points

            // Reset R To R state
            rToRRemoveOffset = 0;
            lastRToRTime = -1;

            // Clear Heart Rate
            heartRateList = new List<double>(10);
            lblHeartRateAverage10.Text = "--";
            lblHeartRateBeatToBeat.Text = "--";

#if CES_DEMO
            hpf.Reset();
#endif
            btnMonitor.Text = "Stop Monitor";
            if (StreamingStartStop != null)
            {
                StreamingStartStop(this, new StreamingStartStopEventArgs() { state = true });
            }
#if FILE_LOG
            // Debug
            DateTime localDate = DateTime.Now;

            file = new StreamWriter("hsp_ecg_output_data.csv");
            file.WriteLine("# " + localDate.ToString() + ", Sample Rate Register Setting: " + ecgArgs.Rate);
            file.WriteLine(@"# ECG (μV)     Raw Integer");
#endif

            // DEBUG - simulate the "first" R To R value as a > 10 second value to see what happens
            //DisplayRToR(0x600);
        }
        private void btnMonitor_Click(object sender, EventArgs e)
        {
            if (btnMonitor.Text == "Start Monitor")
            {
                if (Connected)
                {
                    //if (ckbxEcg.Checked | ckbxPace.Checked | ckbxBioz.Checked | ckbxRtoR.Checked)
                    if (EnableECG | EnableBioZ)
                        Start();
                    else if (EnableRToR)
                        MessageBox.Show("Also enable ECG for R-to-R streaming", "Streaming");
                    else
                        MessageBox.Show("Enable ECG or BioZ before starting streaming", "Streaming");
                }
            }
            else
            {
                Stop();
            }
        }

        private void Stop()
        {
            if (startedEcg | startedCal | startedPace | startedBioz | startedRtoR)
            {
                rpcClient.MAX30001.StopStreaming(connected);

                //rpcClient.MAX30001.WriteReg(0x02, 0);
                //rpcClient.MAX30001.WriteReg(0x03, 0);
            }

            startedCal = false;
            startedEcg = false;
            startedPace = false;
            startedBioz = false;
            startedRtoR = false;

            btnMonitor.Text = "Start Monitor";
            if (StreamingStartStop != null)
                StreamingStartStop(this, new StreamingStartStopEventArgs() { state = false });

#if FILE_LOG
            // Debug
            file.Close();
#endif
        }

        // Clean up streaming 
        public void Close()
        {
            // Disable streaming if enabled
            if (StreamingStartStop != null && btnMonitor.Text == "Stop Monitor")
            {
                StreamingStartStop(this, new StreamingStartStopEventArgs() { state = false });
                Stop();
            }
        }


        private void chkbxCheckedChanged(object sender, EventArgs e)
        {
            /*
            EnableDisableControls(CAL_init, ckbxCal.Checked);
            EnableDisableControls(ECG_InitStart, ckbxEcg.Checked);
            EnableDisableControls(PACE_InitStart, ckbxPace.Checked);
            EnableDisableControls(BIOZ_InitStart, ckbxBioz.Checked);
            EnableDisableControls(ECGFast_Init, ckbxEcg.Checked);
            EnableDisableControls(RtoR_InitStart, ckbxRtoR.Checked);
            */
        }

        private void btnStartTest_Click(object sender, EventArgs e)
        {
            System.Windows.Forms.DataVisualization.Charting.Chart test = chartECG;

            double ymin = test.ChartAreas[0].AxisY.Minimum;  
            double ymax = test.ChartAreas[0].AxisY.Maximum;
            if (btnStartTest.Text == "Start Test")
            {
                rpcClient.MAX30001.StartTest();
                btnStartTest.Text = "Stop Test";
            }
            else
            {
                Stop();
                btnStartTest.Text = "Start Test";
            }

        }

        public void SetDCLeadOff(int leadOffState)
        {
            if (LeadOffBit(leadOffState, 8)) // check for ECG
            {
                UpdateLeadOffState(lblLdoffPh, LeadOffBit(leadOffState, 3));
                UpdateLeadOffState(lblLdoffPl, LeadOffBit(leadOffState, 2));
                UpdateLeadOffState(lblLdoffNh, LeadOffBit(leadOffState, 1));
                UpdateLeadOffState(lblLdoffNl, LeadOffBit(leadOffState, 0));
            }
            if (LeadOffBit(leadOffState, 9)) // check for BIOZ
            {
                UpdateLeadOffState(lblLdoffPh, LeadOffBit(leadOffState, 3));
                UpdateLeadOffState(lblLdoffPl, LeadOffBit(leadOffState, 2));
                UpdateLeadOffState(lblLdoffNh, LeadOffBit(leadOffState, 1));
                UpdateLeadOffState(lblLdoffNl, LeadOffBit(leadOffState, 0));
            }
        }

        public void SetACLeadOff(int leadOffState)
        {
            UpdateLeadOffState(lblBioZACOver, LeadOffBit(leadOffState, 0));
            UpdateLeadOffState(lblBioZACUnder, LeadOffBit(leadOffState, 1));
        }

        //
        // Update LeadOff GUI State
        //
        private void UpdateLeadOffState(TextBox tb, bool state)
        {
            tb.Text = (state == true) ? "1" : "0";
            if (state == true) tb.BackColor = Color.LightGreen;
            else tb.BackColor = Color.Yellow;
        }

        private void UpdateLeadOffState(Label lbl, bool state)
        {
            lbl.ForeColor = (state == true) ? MaximStyle.MaximColor.Red : MaximStyle.MaximColor.Green;
        }

        /* Inner Classes of EcgView */



        /// <summary>
        /// Information used to generate the X-axis of a chart
        /// </summary>
        public class ChartInfo
        {
            /// <summary>
            /// Sample Rate
            /// </summary>
            private double sampleRate = 0;
            public double SampleRate
            {
                get
                {
                    return sampleRate;
                }
                set
                {
                    sampleRate = value;
                    points = Convert.ToInt32(length * sampleRate / average);
                }
            }

            /// <summary>
            /// Length of chart in seconds
            /// </summary>
            private int length = 1;
            public int Length
            {
                get
                {
                    return length;
                }
                set
                {
                    length = value;
                    points = Convert.ToInt32(length * sampleRate / average);
                }
            }

            /// <summary>
            /// Number of total points in chart
            /// </summary>
            private int points = 1;
            public int Points
            {
                get
                {
                    return points;
                }
            }

            public int Gain;

            private int average = 1;
            public int Average
            {
                get
                {
                    return average;
                }
                set
                {
                    average = value;
                    points = Convert.ToInt32(length * sampleRate / average);
                }
            }

            /// <summary>
            /// Value passed the threshold is negative
            /// </summary>
            private int threshold = Int32.MaxValue;
            public int Threshold
            {
                get
                {
                    return threshold;
                }
                set
                {
                    threshold = value;
                }
            }

            /// <summary>
            /// Amount to offset value by to convert to negative 
            /// </summary>
            private int offset = 0;
            public int Offset
            {
                get
                {
                    return offset;
                }
                set
                {
                    offset = value;
                }
            }

            /// <summary>
            /// Number of bits to shift raw data by
            /// </summary>
            public int Shift;


            /// <summary>
            /// Current generator for BioZ
            /// </summary>
            public int CurrentGenerator;

            public Chart Chart;

            public String SeriesName;
        }

        public class PacePoint
        {
            public double Time;
            public bool Polarity;

            public PacePoint(double time, bool polarity)
            {
                this.Time = time;
                this.Polarity = polarity;
            }
        }

        // DEBUG
        int offsetTest = 0;
        private void button1_Click(object sender, EventArgs e)
        {
            //timer1.Enabled = !timer1.Enabled;
            chartECG.Series["SeriesPace"].Points.AddXY(1.99354553222656 + offsetTest, 0.05 * (1));
            chartECG.Series["SeriesPaceFalling"].Points.AddXY(1.99455261230469 + offsetTest, -0.08 * (1));

            offsetTest += 100;
        }

        // DEBUG
        private void timer1_Tick(object sender, EventArgs e)
        {
            /*
            PartialArrayIntAvailableEventArgs ecg1 = new PartialArrayIntAvailableEventArgs();
            PartialArrayIntAvailableEventArgs ecg2 = new PartialArrayIntAvailableEventArgs();
            PartialArrayIntAvailableEventArgs rr1 = new PartialArrayIntAvailableEventArgs();

            ecg1.array1 = new int[] { 0x00, 0x00, 0x00, 0x00, 0xfffff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
            ecg2.array1 = new int[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
            rr1.array1 = new int[] { 0x88 };

            ecg1.reportID = PartialArrayIntAvailableEventArgs.PACKET_MAX30001_ECG;
            ecg2.reportID = PartialArrayIntAvailableEventArgs.PACKET_MAX30001_ECG;
            rr1.reportID = PartialArrayIntAvailableEventArgs.PACKET_MAX30001_RTOR;

            if (clicks % 15 == 0)
            {
                On_AppendChart(this, ecg2);
            }
            else if (clicks % 5 == 0)
            {
                On_AppendChart(this, ecg1);
                On_AppendChart(this, rr1);
            }
            else
                On_AppendChart(this, ecg2);

            clicks++;
            */
            DisplayEcg(new double[] { 0, 0, 0, 0, 1, 2, 3, 4, 5 }, null, new double[] {-10, -10, -10, -10, -10, -10, 4, -10, -10});
        }
    }
}
