/// <summary>
        /// Finds local extrema of a given data set.
        /// Performs search windowing and thresholding (if enabled). Offline algorithm.
        /// Clears any previously found extrema from memory before processing data.
        /// </summary>
        /// <param name="dataList">Data to operate on.</param>
        /// <returns>Number of local extrema found.</returns>
        public int FindLocalExtrema(List <double> dataList)
        {
            ExtremaPoint lastExtremaPoint = new ExtremaPoint();

            // Clear the maxima fields
            _maxima.Clear();
            _minima.Clear();

            // Inspect each element in dataList to determine if its a local maxima/minima
            for (int i = 0; i < dataList.Count; i++)
            {
                ExtremaPoint extremaPoint = this.AnalyseDataPoint(dataList, i, lastExtremaPoint);

                if (extremaPoint != null)
                {
                    if (extremaPoint.extremaType == ExtremaPoint.ExtremaType.Maxima)
                    {
                        _maxima.Add(extremaPoint.index);
                        lastExtremaPoint = extremaPoint;
                    }

                    if (extremaPoint.extremaType == ExtremaPoint.ExtremaType.Minima)
                    {
                        _minima.Add(extremaPoint.index);
                        lastExtremaPoint = extremaPoint;
                    }
                }
            }

            // Return total number of extrema found
            return(_maxima.Count() + _minima.Count());
        }
        private ExtremaPoint AnalyseDataPoint(List <double> dataList, int index, ExtremaPoint lastExtremaPoint)
        {
            List <double> range = dataList;

            ExtremaPoint currExtremaPoint = new ExtremaPoint(index, ExtremaPoint.ExtremaType.none);

            // Remove unneeded elements of left-side of window
            range = range.Skip <double>(lastExtremaPoint.index).ToList <double>();

            // Remove unneeded elements of right-side of window.
            // Need to add 1 because converting from index bounds to number of elements.
            range = range.Take <double>(index + this._searchWindowRadius - lastExtremaPoint.index + 1).ToList <double>();

            AnalyseStates currState = AnalyseStates.CheckIsMostExtremeInSampleWindow;

            while (true)
            {
                switch (currState)
                {
                case AnalyseStates.CheckIsMostExtremeInSampleWindow:
                    // Make sure range is not empty and if current dataIList is the maximum/minimum then store index
                    if ((range.Count() > 0) && (dataList[index] == range.Max()))
                    {
                        // If enforcing alternate extrema, quit if this is another maxima
                        if ((_enforceAlternatingExtrema == true) && (lastExtremaPoint.extremaType == ExtremaPoint.ExtremaType.Maxima))
                        {
                            return(null);
                        }

                        currExtremaPoint.extremaType = ExtremaPoint.ExtremaType.Maxima;

                        currState = AnalyseStates.CheckLeftThreshold;
                        break;
                    }

                    if ((range.Count() > 0) && (dataList[index] == range.Min()))
                    {
                        // If enforcing alternate extrema, quit if this is another minima
                        if ((_enforceAlternatingExtrema == true) && (lastExtremaPoint.extremaType == ExtremaPoint.ExtremaType.Minima))
                        {
                            return(null);
                        }

                        currExtremaPoint.extremaType = ExtremaPoint.ExtremaType.Minima;

                        currState = AnalyseStates.CheckLeftThreshold;
                        break;
                    }

                    return(null);

                case AnalyseStates.CheckLeftThreshold:
                    // If thresholding is not enabled, data point has already
                    // met qualifying criteria and return true
                    if (_thresholdingEnabled == false)
                    {
                        return(currExtremaPoint);
                    }

                    // Check for special case. If index is 0, there are no
                    // left elements to check for threshold, therefore
                    // it fails this check automatically
                    if (index == 0)
                    {
                        return(null);
                    }

                    // Check for left threshold. This has to be an iterative process
                    for (int j = index - 1; j >= 0; j--)
                    {
                        if (currExtremaPoint.extremaType == ExtremaPoint.ExtremaType.Maxima)
                        {
                            // Check if data climbs back above the previously found maximum,
                            // and if so, quit to next loop iteration
                            if (dataList[j] > range.Max())
                            {
                                return(null);
                            }

                            // Check if data exceeds low threshold
                            if (dataList[j] - dataList[index] <= -_thresholdValue)
                            {
                                currState = AnalyseStates.CheckRightThreshold;
                                break;
                            }
                        }
                        else if (currExtremaPoint.extremaType == ExtremaPoint.ExtremaType.Minima)
                        {
                            // Check if data climbs back above the previously found maximum,
                            // and if so, quit to next loop iteration
                            if (dataList[j] < range.Min())
                            {
                                return(null);
                            }

                            if (dataList[j] - dataList[index] >= _thresholdValue)
                            {
                                currState = AnalyseStates.CheckRightThreshold;
                                break;
                            }
                        }

                        // Check if reached the last data point, and if so, quit
                        if (j == 0)
                        {
                            return(null);
                        }
                    }

                    break;

                case AnalyseStates.CheckRightThreshold:
                    // Check for special case. If index is 0, there are no
                    // left elements to check for threshold, therefore
                    // it fails this check automatically
                    if (index == dataList.Count - 1)
                    {
                        return(null);
                    }

                    // Check for right threshold. This has to be an iterative process
                    for (int j = index + 1; j <= dataList.Count - 1; j++)
                    {
                        if (currExtremaPoint.extremaType == ExtremaPoint.ExtremaType.Maxima)
                        {
                            // Check if data climbs back above the previously found maximum,
                            // and if so, quit to next loop iteration
                            if (dataList[j] > range.Max())
                            {
                                return(null);
                            }

                            // Check if data exceeds low threshold
                            if (dataList[j] - dataList[index] <= -_thresholdValue)
                            {
                                currState = AnalyseStates.IsQualifingExtrema;
                                break;
                            }
                        }
                        else if (currExtremaPoint.extremaType == ExtremaPoint.ExtremaType.Minima)
                        {
                            // Check if data climbs back above the previously found maximum,
                            // and if so, quit to next loop iteration
                            if (dataList[j] < range.Min())
                            {
                                return(null);
                            }

                            if (dataList[j] - dataList[index] >= _thresholdValue)
                            {
                                currState = AnalyseStates.IsQualifingExtrema;
                                break;
                            }
                        }

                        // Check if reached the last data point, and if so, quit
                        if (j == dataList.Count - 1)
                        {
                            return(null);
                        }
                    }
                    break;

                case AnalyseStates.IsQualifingExtrema:
                    return(currExtremaPoint);
                }
            }
        }