/// <summary>
 /// Initializes a new instance of the <see cref="ObservedPeak"/> class with peak and its calculated statistics.
 /// </summary>
 /// <param name="group">
 /// The group.
 /// </param>
 /// <param name="peak">
 /// The peak.
 /// </param>
 /// <param name="statistics">
 /// The Statistics.
 /// </param>
 public ObservedPeak(VoltageGroup group, StandardImsPeak peak, PeakScores statistics)
 {
     this.VoltageGroup = group;
     this.Peak = peak;
     this.Statistics = statistics;
     this.ObservationType = ObservationType.Peak;
     this.mobilityPoint = null;
 }
        /// <summary>
        /// The Peak shape score. Evaluating how "good" the peak looks. A good
        /// peak shape score indicates that the peak is not a result of noise
        /// or instrument errors. Mostly the feature intensity along is sufficient
        /// to exclude noise but a good shape score helps evaluating the experiment
        /// and thus the reliability of the data analysis result.
        /// </summary>
        /// <param name="reader">
        /// The reader.
        /// </param>
        /// <param name="massToleranceInPpm">
        /// The mass Tolerance In Ppm.
        /// </param>
        /// <param name="driftTimeToleranceInMs">
        /// The drift Time Tolerance In Scans.
        /// </param>
        /// <param name="imsPeak">
        /// The imsPeak.
        /// </param>
        /// <param name="voltageGroup">
        /// The voltage group.
        /// </param>
        /// <param name="targetMz">
        /// The Target MZ.
        /// </param>
        /// <param name="globalMaxIntensities">
        /// The global Max Intensities.
        /// </param>
        /// <param name="numberOfScans">
        /// The number Of Scans.
        /// </param>
        /// <returns>
        /// The <see cref="double"/>.
        /// </returns>
        public static double PeakShapeScore(StandardImsPeak imsPeak, DataReader reader, double massToleranceInPpm, double driftTimeToleranceInMs, VoltageGroup voltageGroup, double globalMaxIntensities, int numberOfScans)
        {
            int scanRep = imsPeak.PeakApex.DriftTimeCenterInScanNumber;
            double toleranceInMz = massToleranceInPpm / 1e6 * imsPeak.PeakApex.MzCenterInDalton;
            int scanWidth = (int)Math.Ceiling(driftTimeToleranceInMs / 1000 / voltageGroup.AverageTofWidthInSeconds);
            int scanWindowSize = scanWidth * 2 + 1;

            int scanNumberMin = scanRep - scanWidth;
            int scanNumberMax = scanRep + scanWidth;
            if ((scanNumberMin < 0) || (scanNumberMax > numberOfScans - 1))
            {
                return 0;
            }

            int[][] intensityWindow = reader.GetFramesAndScanIntensitiesForAGivenMz(
                voltageGroup.FirstFrameNumber,
                voltageGroup.LastFrameNumber,
                DataReader.FrameType.MS1,
                scanNumberMin,
                scanNumberMax,
                imsPeak.PeakApex.MzCenterInDalton,
                toleranceInMz);

            // Average the intensity window across frames
            int frames = intensityWindow.GetLength(0);
            double[] averagedPeak = new double[scanWindowSize];
            double highestPeak = 0;
            for (int i = 0; i < scanWindowSize; i++)
            {
                for (int j = 0; j < frames; j++)
                {
                    averagedPeak[i] += intensityWindow[j][i];
                }

                averagedPeak[i] /= frames;
                highestPeak = (averagedPeak[i] > highestPeak) ? averagedPeak[i] : highestPeak;
            }

            // For peaks with peak width lower than 3, return a peak score of 0

            // TODO get the intensity threshold here from the noise level instead.
            if (scanWindowSize >= 3)
            {
                if (averagedPeak[scanRep - scanNumberMin] < globalMaxIntensities * 0.0001
                    || averagedPeak[scanRep - scanNumberMin - 1] < globalMaxIntensities * 0.0001
                    || averagedPeak[scanRep - scanNumberMin + 1] < globalMaxIntensities * 0.0001)
                {
                    return 0;
                }
            }

            // Perform a statistical normality test
            double normalityScore = NormalityTest.PeakNormalityTest(averagedPeak, NormalityTest.JaqueBeraTest, 100, globalMaxIntensities);
            return normalityScore;
        }
        /// <summary>
        /// The score feature using isotopic profile.
        /// </summary>
        /// <param name="imsPeak">
        /// The ims Peak.
        /// </param>
        /// <param name="reader">
        /// The reader.
        /// </param>
        /// <param name="target">
        /// The Target.
        /// </param>
        /// <param name="isotopicPeakList">
        /// The isotopic peak list.
        /// </param>
        /// <param name="voltageGroup">
        /// The voltage Group.
        /// </param>
        /// <param name="selectedMethod">
        /// The selected Method.
        /// </param>
        /// <param name="globalMaxIntensities">
        /// </param>
        /// <returns>
        /// The <see cref="double"/>.
        /// </returns>
        /// <exception cref="InvalidOperationException">
        /// </exception>
        public static double IsotopicProfileScore(StandardImsPeak imsPeak, DataReader reader, IImsTarget target, List<Peak> isotopicPeakList, VoltageGroup voltageGroup, IsotopicScoreMethod selectedMethod, double globalMaxIntensities, double totalScans)
        {
            // No need to move on if the isotopic profile is not found
            // if (observedIsotopicProfile == null || observedIsotopicProfile.MonoIsotopicMass < 1)
            // {
            // result.AnalysisStatus = AnalysisStatus.IsotopicProfileNotFound;
            // continue;
            // }

            // Find Isotopic Profile
            // List<Peak> massSpectrumPeaks;
            // IsotopicProfile observedIsotopicProfile = _msFeatureFinder.IterativelyFindMSFeature(massSpectrum, theoreticalIsotopicProfile, out massSpectrumPeaks);
            if (target.CompositionWithoutAdduct == null)
            {
                throw new InvalidOperationException("Cannot score feature using isotopic profile for Ims Target without CompositionWithoutAdduct provided.");
            }

            // Bad Feature, so get out
            if (imsPeak == null)
            {
                return 0;
            }

            // Get the scanWindow size
            int scanNumberMax = imsPeak.PeakApex.DriftTimeFullWidthHalfMaxHigherBondInScanNumber;
            int scanNumberMin = imsPeak.PeakApex.DriftTimeFullWidthHalfMaxLowerBondInScanNumber;
            if ((scanNumberMin < 0) || (scanNumberMax > totalScans - 1))
            {
                return 0;
            }

            // Get the mass error from the observed feature peak from the Target theoretical peak
            double mzOffset = imsPeak.PeakApex.MzCenterInDalton - target.MassWithAdduct;

            List<double> observedIsotopicPeakList = new List<double>();

            int totalIsotopicIndex = isotopicPeakList.Count;
            int[] isotopicIndexMask = new int[totalIsotopicIndex];

            // Find an unsaturated peak in the isotopic profile
            for (int i = 0; i < totalIsotopicIndex; i++)
            {
                // Isotopic centerMz
                double Mz = isotopicPeakList[i].XValue;

                var peakList = reader.GetXic(Mz + mzOffset,
                    imsPeak.PeakApex.MzWindowToleranceInPpm,
                    voltageGroup.FirstFrameNumber,
                    voltageGroup.LastFrameNumber,
                    scanNumberMin,
                    scanNumberMax,
                    DataReader.FrameType.MS1,
                    DataReader.ToleranceType.PPM);

                // Sum the intensities
                double sumIntensities = 0;
                foreach (var point in peakList)
                {
                    sumIntensities += point.Intensity;
                    if (point.IsSaturated)
                    {
                        isotopicIndexMask[i] = 1;
                    }
                }

                sumIntensities /= voltageGroup.FrameAccumulationCount;
                observedIsotopicPeakList.Add(sumIntensities);
            }

            // Return 0 if the intensity sum is really small
            if (observedIsotopicPeakList.Sum() < globalMaxIntensities * 0.0003)
            {
                return 0;
            }

            // If the unsaturated isotopes are below a certain threshold
            if (totalIsotopicIndex - isotopicIndexMask.Sum() <= 1)
            {
                return 0;
            }

            if (selectedMethod == IsotopicScoreMethod.Angle)
            {
                return IsotopicProfileScoreAngle(observedIsotopicPeakList, isotopicPeakList);
            }
            else if (selectedMethod == IsotopicScoreMethod.EuclideanDistance)
            {
                return IsotopicProfileScoreEuclidean(observedIsotopicPeakList, isotopicPeakList);
            }
            else if (selectedMethod == IsotopicScoreMethod.PearsonCorrelation)
            {
                return PearsonCorrelation(observedIsotopicPeakList, isotopicPeakList);
            }
            else if (selectedMethod == IsotopicScoreMethod.Bhattacharyya)
            {
                return BhattacharyyaDistance(observedIsotopicPeakList, isotopicPeakList);
            }
            else if (selectedMethod == IsotopicScoreMethod.EuclideanDistanceAlternative)
            {
                return EuclideanAlternative(observedIsotopicPeakList, isotopicPeakList);
            }
            else
            {
                throw new NotImplementedException();
            }
        }
        /// <summary>
        /// The intensity score.
        /// </summary>
        /// <param name="featurePeak">
        /// The feature blob.
        /// </param>
        /// <param name="globalMaxIntensity">
        /// The global Max Intensity.
        /// </param>
        /// <returns>
        /// The <see cref="double"/>.
        /// </returns>
        public static double IntensityScore(StandardImsPeak featurePeak, double globalMaxIntensity)
        {
            // Sort features by relative intensity
            double summedIntensities = featurePeak.SummedIntensities;

            // Divide intensities by accumulation (If summing instead of averaging is used)
            // summedIntensities /= voltageGroup.FrameAccumulationCount;

            // normalize the score
            return ScoreUtil.MapToZeroOneTrignometry(summedIntensities, false, globalMaxIntensity / 3);
        }
        /// <summary>
        /// The report feature evaluation.
        /// </summary>
        /// <param name="peak">
        /// The peak.
        /// </param>
        /// <param name="scores">
        /// The scores.
        /// </param>
        /// <param name="verbose">
        /// The verbose.
        /// </param>
        /// <param name="target">
        /// The target.
        /// </param>
        /// <param name="badScanRange">
        /// The bad scan range.
        /// </param>
        /// <param name="lowAbsoluteIntensity">
        /// The low intensity.
        /// </param>
        /// <param name="lowRelativeIntensity"></param>
        /// <param name="badPeakShape">
        /// The bad peak shape.
        /// </param>
        /// <param name="lowIsotopicAffinity">
        /// The low isotopic affinity.
        /// </param>
        /// <returns>
        /// If the feature pass all the filters <see cref="bool"/>.
        /// </returns>
        private static string ReportFeatureEvaluation(StandardImsPeak peak, PeakScores scores, bool verbose, IImsTarget target, bool badScanRange, bool lowAbsoluteIntensity, bool lowRelativeIntensity, bool badPeakShape, bool lowIsotopicAffinity)
        {
            Trace.WriteLine(string.Format("        Candidate feature found at [centerMz = {0:F4}, drift time = {1:F2} ms(#{2})] ", peak.PeakApex.MzCenterInDalton, peak.PeakApex.DriftTimeCenterInMs,     peak.PeakApex.DriftTimeCenterInScanNumber));
            Trace.WriteLine(string.Format("            IntensityScore: {0:F4}", scores.IntensityScore));

            if (!lowAbsoluteIntensity)
            {
                Trace.WriteLine(string.Format("            peakShapeScore: {0:F4}", scores.PeakShapeScore));

                if (target.HasCompositionInfo)
                {
                    Trace.WriteLine(string.Format("            isotopicScore:  {0:F4}", scores.IsotopicScore));
                }
            }

            string rejectionReason = badScanRange ? "[Bad scan range] " :
                lowAbsoluteIntensity ? "[Low Absolute Intensity] " :
                badPeakShape ? "[Bad Peak Shape] " :
                lowIsotopicAffinity ? "[Different Isotopic Profile] " :
                lowRelativeIntensity ? "[Low Relative Intensity] " : string.Empty;

            bool rejected = badScanRange || lowAbsoluteIntensity || lowIsotopicAffinity || badPeakShape || lowRelativeIntensity;

            if (verbose)
            {
                if (rejected)
                {
                    Trace.WriteLine("        " + rejectionReason);
                }
                else
                {
                    Trace.WriteLine("        [PASS]");
                }

                Trace.WriteLine(string.Empty);
            }

            return rejectionReason;
        }
 /// <summary>
 /// Initializes a new instance of the <see cref="LibraryMatchResult"/> class.
 /// </summary>
 /// <param name="peak">
 /// The peak.
 /// </param>
 /// <param name="conlusion">
 /// The conlusion.
 /// </param>
 /// <param name="distance">
 /// The distance.
 /// </param>
 public LibraryMatchResult(StandardImsPeak peak, AnalysisStatus conlusion, DriftTimeFeatureDistance distance)
 {
     this.AnalysisStatus = conlusion;
     this.ImsFeature = peak;
     this.distance = distance;
 }