public static void WriteNDPResultForSytoscape(List <SpectrumData> allSpecList, string outputPath)    //output NDP for Sytoscape input
 {
     using (StreamWriter sw = new StreamWriter(outputPath)) {
         for (int i = 0; i < allSpecList.Count - 1; i++)
         {
             for (int j = i + 1; j < allSpecList.Count; j++)
             {
                 double ndp = Cal_Cosine_Product.CalCosineProduct(allSpecList[i], allSpecList[j]);
                 if (ndp >= 0.01)
                 {
                     List <double> commonNeutralLostList = new List <double>();
                     for (int m = 0; m < allSpecList[i].neutralLossList.Count; m++)
                     {
                         for (int n = 0; n < allSpecList[j].neutralLossList.Count; n++)
                         {
                             if (Cal_Cosine_Product.IsEqualMZ(allSpecList[i].neutralLossList[m], allSpecList[j].neutralLossList[n]))   // judge whether 2 mz values <=0.01
                             {
                                 commonNeutralLostList.Add(Math.Round(allSpecList[i].neutralLossList[m], 2));
                             }
                         }
                     }
                     commonNeutralLostList = commonNeutralLostList.Distinct().ToList();
                     commonNeutralLostList = commonNeutralLostList.OrderBy(x => x).ToList();
                     sw.Write(allSpecList[i].group.Replace(" ", "") + "," + ndp + "," + allSpecList[j].group.Replace(" ", "") + ",");
                     for (int k = 0; k < commonNeutralLostList.Count; k++)
                     {
                         sw.Write(commonNeutralLostList[k] + ",");
                     }
                     sw.WriteLine();
                 }
             }
         }
     }
 }
        /**
         * Merge multiple idMSMS (if mz difference less than 0.01, pick the higher intensity one)
         * This function is used in two cases:1, merge 20-50ev idMSMSs, 2, merge idMSMSs in the same pcgroup with NDP>threshold
         */
        private static SpectrumData MergeSpectraToOne(List <SpectrumData> spectraWithSamePcgrp)
        {
            SpectrumData maxSpectrum = new SpectrumData();

            maxSpectrum.peakList = new List <PeakData>();
            maxSpectrum.group    = spectraWithSamePcgrp.OrderByDescending(x => double.Parse(x.group.Split('_')[0])).ToList()[0].group; //choose the highest mz as precursor(if this is 20-50ev, group is the same, then no influnce)
            List <PeakData> allPeaksInOneGroup = new List <PeakData>();

            for (int i = 0; i < spectraWithSamePcgrp.Count; i++)
            {
                allPeaksInOneGroup.AddRange(spectraWithSamePcgrp[i].peakList);
            }
            var allpeaks = from peaks in allPeaksInOneGroup
                           group peaks by peaks.mz;

            foreach (var peaks in allpeaks)
            {
                PeakData maxPeak = peaks.OrderByDescending(x => x.intensity).ToList()[0]; //choose the highest intensity peak in one mz
                maxSpectrum.peakList.Add(maxPeak);
            }
            for (int m = 0; m < maxSpectrum.peakList.Count; m++)   //remove redundancy(two mz <0.01)
            {
                for (int n = m + 1; n < maxSpectrum.peakList.Count; n++)
                {
                    if (Cal_Cosine_Product.IsEqualMZ(maxSpectrum.peakList[m].mz, maxSpectrum.peakList[n].mz))
                    {
                        if (maxSpectrum.peakList[m].intensity >= maxSpectrum.peakList[n].intensity)
                        {
                            maxSpectrum.peakList.RemoveAt(n);
                            n = n - 1;
                        }
                        else
                        {
                            maxSpectrum.peakList[m] = maxSpectrum.peakList[n];
                            n = n - 1;
                        }
                    }
                }
            }
            for (int i = 0; i < maxSpectrum.peakList.Count; i++)   //assign max precursor to all peaks
            {
                maxSpectrum.peakList[i].precursor = maxSpectrum.group;
            }
            maxSpectrum.peakList = maxSpectrum.peakList.OrderBy(x => x.mz).ToList();
            return(maxSpectrum);
        }
        static void Main(string[] args)
        {
            Stopwatch watch = new Stopwatch();

            watch.Start();
            string rootPath = @"E:\Different tissues\idMSMS different tissue\MIX Results\";

            /**
             * Fill 0 values in XCMS peaktable
             */
            //string fillpeaksPath = rootPath + "NormPeakArea.csv";
            //string nonefillpeaksPath = rootPath + "data_minfrac0.1_sn3_nonfill.csv";
            //string outPath = rootPath + "NormPeakArea_fill0.csv";
            //Data_Operation.Fill0Peaks(fillpeaksPath, nonefillpeaksPath, outPath);

            /**
             * export precursors (change parapeters to export all/without isotope/only adducts)
             */
            //string CSVFilePath = rootPath + "NormPeakArea.csv";
            //List<SpectrumData> allSpectraList = IdMSMS_Deconvolution.ProcessPeaktableToSpectralist(CSVFilePath);
            //Data_Operation.FindPrecursorMZ(allSpectraList, outPrecursorPath);

            /**
             * Assemble idMS/MS with multi-eV
             */
            string CSVFilePath                = rootPath + "AllDeconvolutedSpectra_MS_pvals_9s.csv";
            string outputFilePath             = rootPath + "idmsms_3_6_0.9_minCor_rmv50_50ev.csv";
            string NLOutputFilePath           = rootPath + "NL_motif.csv";
            string NDPmatrixPath              = rootPath + "NDPmatrix.csv";
            List <SpectrumData> allMergedList = IdMSMS_Deconvolution.MergeIdMSMSOfDiffCIDs(CSVFilePath);

            FileProcess.WriteSpectrumList(allMergedList, outputFilePath);   //output idMSMS spectra
            FileProcess.OutputNeutralLoss(allMergedList, NLOutputFilePath); //output NL_motif
            string commonNLPath = rootPath + "Common NL table.csv";
            string outNLHitPath = rootPath + "NLmatrix.csv";

            FileProcess.OutputSelectedNLto360Spec(allMergedList, commonNLPath, outNLHitPath); //output binary NL hit table
            //List<SpectrumData> allSpectraList = FileProcess.ReadSpectralCSVFile(CSVFilePath); // use if there is spectra list to read
            //List<SpectrumData> allKnownSpectraList = FileProcess.ReadKnownMSMSSpectraList(knownMSMSPath); // use if there is known spectra list
            List <SpectrumData> allKnownSpectraList = allMergedList;
            List <double[]>     NDPResultList       = new List <double[]>();

            string[] queryTitleLine = new string[allMergedList.Count]; // add QueryTitle line to result
            for (int i = 0; i < allMergedList.Count; i++)
            {
                queryTitleLine[i] = allMergedList[i].group.ToString();
            }
            string[] libraryTitleLine = new string[allKnownSpectraList.Count]; // add Library title to result
            for (int i = 0; i < allKnownSpectraList.Count; i++)
            {
                libraryTitleLine[i] = allKnownSpectraList[i].peakList[0].sampleName;
            }
            for (int i = 0; i < allMergedList.Count; i++)
            {
                double[] line = new double[allMergedList.Count];                                       //
                for (int j = 0; j < allMergedList.Count; j++)                                          //
                {
                    line[j] = Cal_Cosine_Product.CalCosineProduct(allMergedList[i], allMergedList[j]); //
                }
                NDPResultList.Add(line);
            }
            FileProcess.WriteNDPListToFile(NDPResultList, queryTitleLine, queryTitleLine, NDPmatrixPath); // output NDPmatrix

            watch.Stop();
            Console.WriteLine("weight: " + IdMSMS_Deconvolution.weight + " minSamp:" + IdMSMS_Deconvolution.minSampleNrForCor + " ndpThreh:" + IdMSMS_Deconvolution.ndpThreshold);
            Console.WriteLine("Deconvoluted spectra number: " + allMergedList.Count);
            Console.WriteLine("calculation time: " + watch.Elapsed);
            Console.WriteLine("OK");
            Console.Read();
        }
        public static double rtWindow       = 9;      //  rt window to precursor
        //

        public static List <SpectrumData> MergeIdMSMSOfDiffCIDs(string rootDataPath) // using idMSMS precursor-product method by calculated pvals, this method will merge 20-50ev data by selecting highest intensity(the peaks are different among CIDs)
        {
            List <SpectrumData> mergedSpecList = new List <SpectrumData>();
            List <SpectrumData> mergedCIDList  = new List <SpectrumData>();
            List <SpectrumData> AllCIDList     = new List <SpectrumData>(); // all CID
            List <SpectrumData> CID20evList    = ProcessPeaktableToSpectralist(rootDataPath.Replace("MS", "20ev"));

            AllCIDList.AddRange(CID20evList.Where(x => double.Parse(x.group.Split('_')[1]) > 50).ToList()); //remove rt<50
            CID20evList = null;
            GC.Collect();
            Console.WriteLine("20ev done!");
            List <SpectrumData> CID30evList = ProcessPeaktableToSpectralist(rootDataPath.Replace("MS", "30ev"));

            AllCIDList.AddRange(CID30evList.Where(x => double.Parse(x.group.Split('_')[1]) > 50).ToList());
            CID30evList = null;
            GC.Collect();
            Console.WriteLine("30ev done!");
            List <SpectrumData> CID40evList = ProcessPeaktableToSpectralist(rootDataPath.Replace("MS", "40ev"));

            AllCIDList.AddRange(CID40evList.Where(x => double.Parse(x.group.Split('_')[1]) > 50).ToList());
            CID40evList = null;
            GC.Collect();
            Console.WriteLine("40ev done!");
            List <SpectrumData> CID50evList = ProcessPeaktableToSpectralist(rootDataPath.Replace("MS", "50ev"));

            AllCIDList.AddRange(CID50evList.Where(x => double.Parse(x.group.Split('_')[1]) > 50).ToList());
            CID50evList = null;
            GC.Collect();
            Console.WriteLine("50ev done!");
            List <SpectrumData> MSList        = ProcessPeaktableToSpectralist(rootDataPath); // Note: pcgroup is different among different CIDs
            List <PeakData>     allMSPeakList = new List <PeakData>();

            for (int i = 0; i < MSList.Count; i++)
            {
                allMSPeakList.AddRange(MSList[i].peakList.Where(x => x.rt > 50).ToList()); //already averaged, meaning that each PeakData=one mz line
            }
            MSList = null;
            GC.Collect();
            var allSpec = from specs in AllCIDList
                          group specs by specs.peakList[0].precursor;

            foreach (var specs in allSpec)
            {
                PeakData precursorPeak   = allMSPeakList.Where(x => x.mz == double.Parse(specs.Key.Split('_')[0])).ToList()[0]; // find this precursor in MS
                double   precursorPeakRT = precursorPeak.rt;

                /**
                 * Filter presursor: 1,precursor should pass the "noise"threshold; 2, isotope is removed from precursor list(but isotopes can be fragments; 3, precursors will meet the criteria that "diffrt"(retention time difference) and "feature"(mass difference) are zero
                 */
                List <PeakData> peaksofeachprecursor = new List <PeakData>();
                for (int k = 0; k < specs.ToList().Count; k++)
                {
                    peaksofeachprecursor.AddRange(specs.ToList()[k].peakList);
                }
                if (precursorPeak.isNoise == true || IsIsotope(precursorPeak.isotopes) || !IsSelfCor(peaksofeachprecursor))   // determine whether precursor peak contains minimum number of "minSampleNrForCor" or is isotope, if not, not include
                {
                    continue;
                }

                /**
                 * Filter fragments: 1, fragments should not be "noise" peaks,2, fragment's "sample position" should be the subcollection of precursor's position,3, fragment's m/z less than precursor(The highest intensity of fragment is selected eventually,and then calculate its relativeintensity)
                 */
                List <string>   precursorPeakPosition = precursorPeak.sampleName.Split('_').ToList();
                double          precursorMZ           = precursorPeak.mz;
                List <PeakData> allPeaksInOneGroup    = new List <PeakData>();
                List <PeakData> MList = new List <PeakData>();
                foreach (var spec in specs)
                {
                    allPeaksInOneGroup.AddRange(spec.peakList.Where(x => (x.isNoise == false && x.diffrt < rtWindow)).ToList());// determine whether fragment peak contains minimum number of "minSampleNrForCor", if not, not include
                }
                for (int i = 0; i < allPeaksInOneGroup.Count; i++)
                {
                    List <string> fragmentPeakPosition = allPeaksInOneGroup[i].sampleName.Split('_').ToList();
                    double        fragmentMZ           = allPeaksInOneGroup[i].mz;                         //until here, the higher mz is not removed, so fragment mz can be higher than precursor mz here
                    if (precursorPeakPosition.Intersect(fragmentPeakPosition).Count() < minSampleNrForCor) // min samples for correlation
                    {
                        if (allPeaksInOneGroup[i].isotopes.Contains("[M]"))                                //after saving [M]+, check whether it is in the isotope list,if so, add this [M]+
                        {
                            MList.Add(allPeaksInOneGroup[i]);
                        }
                        allPeaksInOneGroup.RemoveAt(i);
                        i = i - 1;
                    }
                }
                for (int i = 0; i < MList.Count; i++)
                {
                    for (int j = 0; j < allPeaksInOneGroup.Count; j++)
                    {
                        if (IsMainPeak(allPeaksInOneGroup[j], MList[i]))
                        {
                            allPeaksInOneGroup.Add(MList[i]);
                            break;
                        }
                    }
                }
                SpectrumData maxSpectrum = new SpectrumData();
                maxSpectrum.peakList = allPeaksInOneGroup;
                maxSpectrum.group    = specs.Key;
                List <SpectrumData> sd = new List <SpectrumData>();
                sd.Add(maxSpectrum);
                maxSpectrum          = MergeSpectraToOne(sd); // merge 20-50ev
                maxSpectrum.peakList = maxSpectrum.peakList.OrderBy(x => x.mz).ToList();
                if (maxSpectrum.peakList.Count != 0)
                {
                    mergedCIDList.Add(CalRelativeIntensity(maxSpectrum)); //calculate relative intensity
                }
            }
            AllCIDList = null;
            GC.Collect();
            Console.WriteLine("Filter done!");

            /**
             * merging sub-idMS/MS function: merge those idMSMSs which are in the same pcgroup && ndp>thresholdNDP, then remove higher mz than precursor, calculate relative intensity, calculate neutral loss
             */
            int n      = 0;
            var pcgrps = mergedCIDList.GroupBy(x => int.Parse(x.group.Split('_')[2])); //same pcgroup

            foreach (var specs in pcgrps)
            {
                List <SpectrumData> specList = specs.ToList();
                if (specList.Count > 1)  //check if the pcgroup contains nore than one idMSMS
                {
                    List <SpectrumData> similarList = new List <SpectrumData>();
                    specList = specList.OrderBy(x => double.Parse(x.group.Split('_')[0])).ToList();//ascending order mz, merge from bottom up
                    for (int i = 0; i < specList.Count - 1; i++)
                    {
                        PeakData precursorPeak = allMSPeakList.Where(x => x.mz == double.Parse(specList[i].group.Split('_')[0])).ToList()[0]; // find this precursor in MS
                        if (!Data_Operation.IsPrecursor(precursorPeak.adduct))                                                                // if the mz is not precursor, merge upwards
                        {
                            for (int j = i + 1; j < specList.Count; j++)
                            {
                                double ndp = 0;
                                ndp = Cal_Cosine_Product.CalCosineProduct(specList[i], specList[j]);
                                if (ndp > ndpThreshold)
                                {
                                    List <SpectrumData> candiList = new List <SpectrumData>();
                                    candiList.Add(specList[i]);
                                    candiList.Add(specList[j]);
                                    SpectrumData MeSpec = MergeSpectraToOne(candiList);
                                    specList[j] = MeSpec; //merge a and b (merged result is stored in b and delete a)
                                    specList.RemoveAt(i);
                                    i--;
                                    n++;
                                    break;
                                }
                            }
                        }
                    }
                }
                for (int i = 0; i < specList.Count; i++)
                {
                    SpectrumData removeSpec = RemoveMZHigherThanPrecursor(specList[i]); //remove higher mz than precursor
                    if (removeSpec.peakList.Count != 0)
                    {
                        SpectrumData finalSpec = CalRelativeIntensity(removeSpec);// cal relative intensity
                        finalSpec.peakList        = finalSpec.peakList.OrderByDescending(x => x.mz).ToList();
                        finalSpec.neutralLossList = Data_Operation.FindNeutralLoss(finalSpec);
                        mergedSpecList.Add(finalSpec);
                    }
                }
            }
            Console.WriteLine("n:" + n);
            Console.WriteLine("merge done!");
            return(mergedSpecList);
        }