/// <summary>
        /// The append correlation result to csv.
        /// </summary>
        /// <param name="correlationResult">
        /// The correlation result.
        /// </param>
        public void AppendCorrelationResultToCsv(ChargeStateCorrelationResult correlationResult)
        {
            PeptideTarget target = correlationResult.ImsTarget;

            string modificationString = "";
            foreach (var modification in target.ModificationList)
            {
                modificationString += modification.Name + ":" + modification.AccessionNum + ";";
            }

            StringBuilder peptideInfo = new StringBuilder();
            peptideInfo.Append(target.ID + ",");
            peptideInfo.Append(target.PeptideSequence + ",");
            peptideInfo.Append(modificationString + ","); // TODO: Mods
            peptideInfo.Append(target.EmpiricalFormula + ",");
            peptideInfo.Append(target.MonoisotopicMass + ",");

            double targetElutionTime = target.NormalizedElutionTime;

            foreach (var result in correlationResult.CorrelatedResults)
            {
                int chargeState = result.ChargeState;
                IEnumerable<DriftTimeTarget> possibleDriftTimeTargets = target.DriftTimeTargetList.Where(x => x.ChargeState == chargeState).OrderBy(x => Math.Abs(x.NormalizedDriftTimeInMs - result.DriftTime));

                double targetDriftTime = 0;
                double driftTimeError = 0;

                if(possibleDriftTimeTargets.Any())
                {
                    DriftTimeTarget driftTimeTarget = possibleDriftTimeTargets.First();
                    targetDriftTime = driftTimeTarget.NormalizedDriftTimeInMs;
                    driftTimeError = result.DriftTime - targetDriftTime;
                }

                double elutionTimeError = result.NormalizedElutionTime - targetElutionTime;
                double correlationAverage = correlationResult.CorrelationSum / (correlationResult.CorrelatedResults.Count - 1);

                StringBuilder resultInfo = new StringBuilder();
                resultInfo.Append(result.ChargeState + ",");
                resultInfo.Append(result.IsotopicProfile.MonoPeakMZ + ",");
                resultInfo.Append(result.PpmError + ",");
                resultInfo.Append(result.ScanLcRep + ",");
                resultInfo.Append(result.IsotopicFitScore + ",");
                resultInfo.Append(result.IsotopicProfile.GetAbundance() + ",");
                resultInfo.Append(targetElutionTime + ",");
                resultInfo.Append(result.NormalizedElutionTime + ",");
                resultInfo.Append(elutionTimeError + ",");
                resultInfo.Append(targetDriftTime + ",");
                resultInfo.Append(result.DriftTime + ",");
                resultInfo.Append(driftTimeError + ",");
                resultInfo.Append(correlationAverage + ",");
                resultInfo.Append(result.AnalysisStatus.ToString());

                this.TextWriter.WriteLine(peptideInfo.ToString() + resultInfo.ToString());
            }
        }
        /// <summary>
        /// The run informed workflow.
        /// </summary>
        /// <param name="target">
        /// The Target.
        /// </param>
        /// <returns>
        /// The <see cref="ChargeStateCorrelationResult"/>.
        /// </returns>
        public ChargeStateCorrelationResult RunInformedWorkflow(PeptideTarget target)
        {
            Composition targetComposition = target.CompositionWithoutAdduct;
            double targetMass = targetComposition.Mass;
            string empiricalFormula = targetComposition.ToPlainString();

            double targetNet = target.NormalizedElutionTime;
            double targetNetMin = targetNet - this._parameters.NetTolerance;
            double targetNetMax = targetNet + this._parameters.NetTolerance;

            double reverseAlignedNetMin = targetNetMin;
            double reverseAlignedNetMax = targetNetMax;

            if (this._netAlignment != null)
            {
                double reverseAlignedNet = this.GetReverseAlignedNet(targetNet);
                reverseAlignedNetMin = reverseAlignedNet - this._parameters.NetTolerance;
                reverseAlignedNetMax = reverseAlignedNet + this._parameters.NetTolerance;
            }

            int scanLcSearchMin = (int)Math.Floor(reverseAlignedNetMin * this.NumberOfFrames);
            int scanLcSearchMax = (int)Math.Ceiling(reverseAlignedNetMax * this.NumberOfFrames);

            int iteration = (targetComposition == null) ? 1 : this._parameters.ChargeStateMax;
            for (int chargeState = 1; chargeState <= iteration; chargeState++)
            {
                if (targetComposition != null)
                {
                    Ion targetIon = new Ion(targetComposition, chargeState);
                    target.MassWithAdduct = targetIon.GetMonoIsotopicMz();
                }

                double minMzForSpectrum = target.MassWithAdduct - (1.6 / chargeState);
                double maxMzForSpectrum = target.MassWithAdduct + (4.6 / chargeState);

                // Generate Theoretical Isotopic Profile
                IsotopicProfile theoreticalIsotopicProfile = this._theoreticalFeatureGenerator.GenerateTheorProfile(empiricalFormula, chargeState);
                List<Peak> theoreticalIsotopicProfilePeakList = theoreticalIsotopicProfile.Peaklist.Cast<Peak>().ToList();

                // Find XIC Features
                IEnumerable<FeatureBlob> featureBlobs = this.FindFeatures(target.MassWithAdduct, scanLcSearchMin, scanLcSearchMax);

                // Filter away small XIC peaks
                featureBlobs = FeatureDetection.FilterFeatureList(featureBlobs, 0.25);

                if(!featureBlobs.Any())
                {
                    LcImsTargetResult result = new LcImsTargetResult
                    {
                        ChargeState = chargeState,
                        AnalysisStatus = AnalysisStatus.XicNotFound
                    };

                    target.ResultList.Add(result);
                }

                // Check each XIC Peak found
                foreach (var featureBlob in featureBlobs)
                {
                    // Setup result object
                    LcImsTargetResult result = new LcImsTargetResult
                    {
                        ChargeState = chargeState,
                        AnalysisStatus = AnalysisStatus.Positive
                    };

                    target.ResultList.Add(result);

                    FeatureBlobStatistics statistics = featureBlob.CalculateStatistics();
                    int unsaturatedIsotope = 0;
                    FeatureBlob isotopeFeature = null;

                    int scanLcMin = statistics.ScanLcMin;
                    int scanLcMax = statistics.ScanLcMax;
                    int scanImsMin = statistics.ScanImsMin;
                    int scanImsMax = statistics.ScanImsMax;

                    // TODO: Verify that there are no peaks at isotope #s 0.5 and 1.5?? (If we filter on drift time, this shouldn't actually be necessary)

                    // Find an unsaturated peak in the isotopic profile
                    for (int i = 1; i < 10; i++)
                    {
                        if (!statistics.IsSaturated) break;

                        // Target isotope m/z
                        double isotopeTargetMz = (target.CompositionWithoutAdduct != null) ? new Ion(targetComposition, chargeState).GetIsotopeMz(i) : target.MassWithAdduct;

                        // Find XIC Features
                        IEnumerable<FeatureBlob> newFeatureBlobs = this.FindFeatures(isotopeTargetMz, scanLcMin - 20, scanLcMax + 20);

                        // If no feature, then get out
                        if (!newFeatureBlobs.Any())
                        {
                            statistics = null;
                            break;
                        }

                        bool foundFeature = false;
                        foreach (var newFeatureBlob in newFeatureBlobs.OrderByDescending(x => x.PointList.Count))
                        {
                            var newStatistics = newFeatureBlob.CalculateStatistics();
                            if(newStatistics.ScanImsRep <= scanImsMax && newStatistics.ScanImsRep >= scanImsMin && newStatistics.ScanLcRep <= scanLcMax && newStatistics.ScanLcRep >= scanLcMin)
                            {
                                isotopeFeature = newFeatureBlob;
                                foundFeature = true;
                                break;
                            }
                        }

                        if(!foundFeature)
                        {
                            statistics = null;
                            break;
                        }

                        statistics = isotopeFeature.CalculateStatistics();
                        unsaturatedIsotope = i;
                    }

                    // Bad Feature, so get out
                    if (statistics == null)
                    {
                        result.AnalysisStatus = AnalysisStatus.IsotopicProfileNotFound;
                        continue;
                    }

                    // TODO: Calculate accurate NET and drift time using quadratic equation
                    int scanLcRep = statistics.ScanLcRep + 1;
                    int scanImsRep = statistics.ScanImsRep;

                    // Calculate NET using aligned data if applicable
                    double net = scanLcRep / this.NumberOfFrames;
                    if (this._netAlignment != null)
                    {
                        net = this._netAlignment.Interpolate(net);
                    }

                    FeatureBlob featureToUseForResult = unsaturatedIsotope > 0 ? isotopeFeature : featureBlob;

                    // Set data to result
                    result.FeatureBlobStatistics = statistics;
                    result.IsSaturated = unsaturatedIsotope > 0;
                    result.ScanLcRep = statistics.ScanLcRep;
                    result.NormalizedElutionTime = net;
                    result.DriftTime = this._uimfReader.GetDriftTime(statistics.ScanLcRep, statistics.ScanImsRep, true);
                    result.XicFeature = featureToUseForResult;

                    // Don't consider bogus results
                    if (scanImsRep < 5 || scanImsRep > this.NumberOfScans - 5)
                    {
                        result.AnalysisStatus = AnalysisStatus.DriftTimeError;
                        continue;
                    }

                        // Don't consider bogus results
                        if (scanLcRep < 3 || scanLcRep > this.NumberOfFrames - 4)
                        {
                            result.AnalysisStatus = AnalysisStatus.ElutionTimeError;
                            continue;
                        }

                        // TODO: ViperCompatibleMass Alignment???
                    if (target.TargetType == TargetType.Peptide)
                    {
                        // Filter by NET
                        if (net > targetNetMax || net < targetNetMin)
                        {
                            result.AnalysisStatus = AnalysisStatus.ElutionTimeError;
                            continue;
                        }
                    }

                    //Console.WriteLine(Target.PeptideSequence + "\t" + targetMass + "\t" + targetMz + "\t" + scanLcRep);

                    // Get ViperCompatibleMass Spectrum Data
                    XYData massSpectrum = this.GetMassSpectrum(scanLcRep, scanImsRep, minMzForSpectrum, maxMzForSpectrum);
                    List<Peak> massSpectrumPeakList = this._peakDetector.FindPeaks(massSpectrum);
                    //WriteXYDataToFile(massSpectrum, targetMz);

                    // Find Isotopic Profile
                    List<Peak> massSpectrumPeaks;
                    IsotopicProfile observedIsotopicProfile = this._msFeatureFinder.IterativelyFindMSFeature(massSpectrum, theoreticalIsotopicProfile, out massSpectrumPeaks);

                    // Add data to result
                    result.MassSpectrum = massSpectrum;

                    // No need to move on if the isotopic profile is not found
                    if (observedIsotopicProfile == null || observedIsotopicProfile.MonoIsotopicMass < 1)
                    {
                        result.AnalysisStatus = AnalysisStatus.IsotopicProfileNotFound;
                        continue;
                    }

                    // Add data to result
                    result.IsotopicProfile = observedIsotopicProfile;
                    result.MonoisotopicMass = observedIsotopicProfile.MonoIsotopicMass;
                    result.PpmError = Math.Abs(PeptideUtil.PpmError(targetMass, observedIsotopicProfile.MonoIsotopicMass));

                    // If not enough peaks to reach unsaturated isotope, no need to move on
                    if (observedIsotopicProfile.Peaklist.Count <= unsaturatedIsotope)
                    {
                        result.AnalysisStatus = AnalysisStatus.IsotopicProfileNotFound;
                        continue;
                    }

                    // If the mass error is too high, then ignore
                    if (result.PpmError > this._parameters.MassToleranceInPpm)
                    {
                        result.AnalysisStatus = AnalysisStatus.MassError;
                        continue;
                    }

                    // Correct for Saturation if needed
                    if (unsaturatedIsotope > 0)
                    {
                        IsotopicProfileUtil.AdjustSaturatedIsotopicProfile(observedIsotopicProfile, theoreticalIsotopicProfile, unsaturatedIsotope);
                    }

                    //WriteMSPeakListToFile(observedIsotopicProfile.Peaklist, targetMz);

                    // TODO: This is a hack to fix an issue where the peak width is being calculated way too large which causes the leftOfMonoPeakLooker to use too wide of a tolerance
                    MSPeak monoPeak = observedIsotopicProfile.getMonoPeak();
                    if (monoPeak.Width > 0.15) monoPeak.Width = 0.15f;

                    // Filter out flagged results
                    MSPeak peakToLeft = this._leftOfMonoPeakLooker.LookforPeakToTheLeftOfMonoPeak(monoPeak, observedIsotopicProfile.ChargeState, massSpectrumPeaks);
                    if (peakToLeft != null)
                    {
                        result.AnalysisStatus = AnalysisStatus.PeakToLeft;
                        continue;
                    }

                    double isotopicFitScore;

                    // Calculate isotopic fit score
                    if(unsaturatedIsotope > 0)
                    {
                        int unsaturatedScanLc = this.FindFrameNumberUseForIsotopicProfile(target.MassWithAdduct, scanLcRep, scanImsRep);

                        if (unsaturatedScanLc > 0)
                        {
                            // Use the unsaturated profile if we were able to get one
                            XYData unsaturatedMassSpectrum = this.GetMassSpectrum(unsaturatedScanLc, scanImsRep, minMzForSpectrum, maxMzForSpectrum);
                            //WriteXYDataToFile(unsaturatedMassSpectrum, targetMz);
                            List<Peak> unsaturatedMassSpectrumPeakList = this._peakDetector.FindPeaks(unsaturatedMassSpectrum);
                            isotopicFitScore = this._isotopicPeakFitScoreCalculator.GetFit(theoreticalIsotopicProfilePeakList, unsaturatedMassSpectrumPeakList, 0.15, this._parameters.MassToleranceInPpm);
                        }
                        else
                        {
                            // Use the saturated profile
                            isotopicFitScore = this._isotopicPeakFitScoreCalculator.GetFit(theoreticalIsotopicProfilePeakList, massSpectrumPeakList, 0.15, this._parameters.MassToleranceInPpm);
                        }
                    }
                    else
                    {
                        isotopicFitScore = this._isotopicPeakFitScoreCalculator.GetFit(theoreticalIsotopicProfilePeakList, massSpectrumPeakList, 0.15, this._parameters.MassToleranceInPpm);
                    }

                    // Add data to result
                    result.IsotopicFitScore = isotopicFitScore;

                    // Filter out bad isotopic fit scores
                    if (isotopicFitScore > this._parameters.IsotopicFitScoreThreshold && unsaturatedIsotope == 0)
                    {
                        result.AnalysisStatus = AnalysisStatus.IsotopicFitScoreError;
                        continue;
                    }

                    Console.WriteLine(chargeState + "\t" + unsaturatedIsotope + "\t" + statistics.ScanLcMin + "\t" + statistics.ScanLcMax + "\t" + statistics.ScanLcRep + "\t" + statistics.ScanImsMin + "\t" + statistics.ScanImsMax + "\t" + statistics.ScanImsRep + "\t" + isotopicFitScore.ToString("0.0000") + "\t" + result.NormalizedElutionTime.ToString("0.0000") + "\t" + result.DriftTime.ToString("0.0000"));
                }

                // TODO: Isotope Correlation (probably not going to do because of saturation issues)
            }

            // Charge State Correlation (use first unsaturated XIC feature)
            List<ChargeStateCorrelationResult> chargeStateCorrelationResultList = new List<ChargeStateCorrelationResult>();
            ChargeStateCorrelationResult bestCorrelationResult = null;
            double bestCorrelationSum = -1;

            List<LcImsTargetResult> resultList = target.ResultList.Where(x => x.AnalysisStatus == AnalysisStatus.Positive).OrderBy(x => x.IsotopicFitScore).ToList();
            int numResults = resultList.Count;

            for (int i = 0; i < numResults; i++)
            {
                LcImsTargetResult referenceResult = resultList[i];

                ChargeStateCorrelationResult chargeStateCorrelationResult = new ChargeStateCorrelationResult(target, referenceResult);
                chargeStateCorrelationResultList.Add(chargeStateCorrelationResult);

                for (int j = i + 1; j < numResults; j++)
                {
                    LcImsTargetResult testResult = resultList[j];
                    double correlation = FeatureCorrelator.CorrelateFeaturesUsingLc(referenceResult.XicFeature, testResult.XicFeature);
                    chargeStateCorrelationResult.CorrelationMap.Add(testResult, correlation);
                    Console.WriteLine(referenceResult.FeatureBlobStatistics.ScanLcRep + "\t" + referenceResult.FeatureBlobStatistics.ScanImsRep + "\t" + testResult.FeatureBlobStatistics.ScanLcRep + "\t" + testResult.FeatureBlobStatistics.ScanImsRep + "\t" + correlation);
                }

                List<LcImsTargetResult> possibleBestResultList;
                double correlationSum = chargeStateCorrelationResult.GetBestCorrelation(out possibleBestResultList);

                if(correlationSum > bestCorrelationSum)
                {
                    bestCorrelationSum = correlationSum;
                    bestCorrelationResult = chargeStateCorrelationResult;
                }
            }

            // TODO: Score Target

            // TODO: Quantify Target (return isotopic profile abundance)

            return bestCorrelationResult;
        }