/**
  * Returns a boolean indicating whether the given charge can be found on the given fragment ion.
  *
  * @param theoreticIon the ion of interest
  * @param charge the candidate charge
  * @param precursorCharge the precursor charge
  * @return a boolean indicating whether the given charge can be found on the given fragment ion
  */
 public boolean chargeValidated(Ion theoreticIon, int charge, int precursorCharge) {
   if (charge == 1) {
     return true;
   }
   switch (theoreticIon.getType()) {
     case IMMONIUM_ION:
     case RELATED_ION: // note: it is possible to implement higher charges but then modify
                       // IonMatch.getPeakAnnotation(boolean html) as well to see the charge
                       // displayed on the spectrum
       return false;
     case REPORTER_ION: // note: it is possible to implement higher charges but then modify
                        // IonMatch.getPeakAnnotation(boolean html) as well to see the charge
                        // displayed on the spectrum
       return false;
     case PEPTIDE_FRAGMENT_ION:
       PeptideFragmentIon peptideFragmentIon = ((PeptideFragmentIon) theoreticIon);
       return charge <= peptideFragmentIon.getNumber() && charge < precursorCharge;
     case TAG_FRAGMENT_ION:
       TagFragmentIon tagFragmentIon = ((TagFragmentIon) theoreticIon);
       return charge <= tagFragmentIon.getNumber() && charge < precursorCharge;
     case PRECURSOR_ION:
       return charge >= precursorCharge;
     default:
       throw new UnsupportedOperationException(
           "Ion type "
               + theoreticIon.getTypeAsString()
               + " not implemented in the spectrum annotator.");
   }
 }
 /**
  * Returns a boolean indicating whether the neutral losses of the given fragment ion fit the
  * requirement of the given neutral losses map.
  *
  * @param neutralLosses map of expected neutral losses: neutral loss
  * @param theoreticIon the ion of interest
  * @return a boolean indicating whether the neutral losses of the given fragment ion are fit the
  *     requirement of the given neutral losses map
  */
 public boolean lossesValidated(NeutralLossesMap neutralLosses, Ion theoreticIon) {
   if (theoreticIon.hasNeutralLosses()) {
     for (NeutralLoss neutralLoss : theoreticIon.getNeutralLosses()) {
       if (!isAccounted(neutralLosses, neutralLoss, theoreticIon)) {
         return false;
       }
     }
   }
   return true;
 }
  /**
   * Returns a boolean indicating whether the neutral loss should be accounted for.
   *
   * @param neutralLosses map of expected neutral losses
   * @param neutralLoss the neutral loss of interest
   * @param ion the fragment ion of interest
   * @return boolean indicating whether the neutral loss should be considered
   */
  public boolean isAccounted(NeutralLossesMap neutralLosses, NeutralLoss neutralLoss, Ion ion) {

    if (neutralLosses == null || neutralLosses.isEmpty()) {
      return false;
    }

    for (String neutralLossName : neutralLosses.getAccountedNeutralLosses()) {

      NeutralLoss neutralLossRef = NeutralLoss.getNeutralLoss(neutralLossName);

      if (neutralLoss.isSameAs(neutralLossRef)) {
        switch (ion.getType()) {
          case PEPTIDE_FRAGMENT_ION:
            PeptideFragmentIon peptideFragmentIon = ((PeptideFragmentIon) ion);
            switch (ion.getSubType()) {
              case PeptideFragmentIon.A_ION:
              case PeptideFragmentIon.B_ION:
              case PeptideFragmentIon.C_ION:
                return neutralLosses.getForwardStart(neutralLossName)
                    <= peptideFragmentIon.getNumber();
              case PeptideFragmentIon.X_ION:
              case PeptideFragmentIon.Y_ION:
              case PeptideFragmentIon.Z_ION:
                return neutralLosses.getRewindStart(neutralLossName)
                    <= peptideFragmentIon.getNumber();
              default:
                throw new UnsupportedOperationException(
                    "Fragment ion type "
                        + ion.getSubTypeAsString()
                        + " not implemented in the spectrum annotator.");
            }
          case TAG_FRAGMENT_ION:
            TagFragmentIon tagFragmentIon = ((TagFragmentIon) ion);
            switch (ion.getSubType()) {
              case TagFragmentIon.A_ION:
              case TagFragmentIon.B_ION:
              case TagFragmentIon.C_ION:
                return neutralLosses.getForwardStart(neutralLossName) <= tagFragmentIon.getNumber();
              case TagFragmentIon.X_ION:
              case TagFragmentIon.Y_ION:
              case TagFragmentIon.Z_ION:
                return neutralLosses.getRewindStart(neutralLossName) <= tagFragmentIon.getNumber();
              default:
                throw new UnsupportedOperationException(
                    "Fragment ion type "
                        + ion.getSubTypeAsString()
                        + " not implemented in the spectrum annotator.");
            }
          default:
            return true;
        }
      }
    }
    return false;
  }
 /**
  * Convenience method to match a reporter ion in a spectrum. The charge is assumed to be 1.
  *
  * @param theoreticIon the theoretic ion to look for
  * @param charge the charge of the ion
  * @param spectrum the spectrum
  * @param massTolerance the mass tolerance to use
  * @return a list of all the ion matches
  * @throws java.lang.InterruptedException exception thrown if the thread is interrupted
  */
 public static ArrayList<IonMatch> matchReporterIon(
     Ion theoreticIon, int charge, Spectrum spectrum, double massTolerance)
     throws InterruptedException {
   ArrayList<IonMatch> result = new ArrayList<IonMatch>(1);
   double targetMass = theoreticIon.getTheoreticMz(charge);
   for (double mz : spectrum.getOrderedMzValues()) {
     if (Math.abs(mz - targetMass) <= massTolerance) {
       result.add(new IonMatch(spectrum.getPeakMap().get(mz), theoreticIon, charge));
     }
     if (mz > targetMass + massTolerance) {
       break;
     }
   }
   return result;
 }
  /**
   * Matches a theoretic ion in the spectrum. Returns an IonMatch containing the ion and the peak.
   * Null if not found.
   *
   * @param theoreticIon the theoretic ion
   * @param inspectedCharge the expected charge
   * @return the IonMatch between the ion and the peak
   */
  protected IonMatch matchInSpectrum(Ion theoreticIon, Integer inspectedCharge) {

    Double fragmentMz = theoreticIon.getTheoreticMz(inspectedCharge);

    // Get the peaks matching the desired m/z
    ArrayList<Peak> matchedPeaks = spectrumIndex.getMatchingPeaks(fragmentMz);

    if (matchedPeaks.isEmpty()) {
      return null;
    }

    // Select the most accurate or most intense according to the annotation settings
    IonMatch ionMatch = new IonMatch(null, theoreticIon, inspectedCharge);
    ionMatch.peak =
        (matchedPeaks.size() == 1) ? matchedPeaks.get(0) : getBestPeak(matchedPeaks, ionMatch);
    return ionMatch;
  }
  /** Updates the mass shifts. */
  protected void updateMassShifts() {
    if (theoreticalFragmentIons != null) {
      HashMap<Integer, ArrayList<Ion>> peptideFragmentIons =
          theoreticalFragmentIons.get(IonType.PEPTIDE_FRAGMENT_ION.index);
      ArrayList<Ion> ions = peptideFragmentIons.get(PeptideFragmentIon.A_ION);
      if (ions != null) {
        for (Ion ion : ions) {
          ion.setTheoreticMass(ion.getTheoreticMass() + massShift + massShiftNTerm);
        }
      }
      ions = peptideFragmentIons.get(PeptideFragmentIon.B_ION);
      if (ions != null) {
        for (Ion ion : ions) {
          ion.setTheoreticMass(ion.getTheoreticMass() + massShift + massShiftNTerm);
        }
      }
      ions = peptideFragmentIons.get(PeptideFragmentIon.C_ION);
      if (ions != null) {
        for (Ion ion : ions) {
          ion.setTheoreticMass(ion.getTheoreticMass() + massShift + massShiftNTerm);
        }
      }
      ions = peptideFragmentIons.get(PeptideFragmentIon.X_ION);
      if (ions != null) {
        for (Ion ion : ions) {
          ion.setTheoreticMass(ion.getTheoreticMass() + massShift + massShiftCTerm);
        }
      }
      ions = peptideFragmentIons.get(PeptideFragmentIon.Y_ION);
      if (ions != null) {
        for (Ion ion : ions) {
          ion.setTheoreticMass(ion.getTheoreticMass() + massShift + massShiftCTerm);
        }
      }
      ions = peptideFragmentIons.get(PeptideFragmentIon.Z_ION);
      if (ions != null) {
        for (Ion ion : ions) {
          ion.setTheoreticMass(ion.getTheoreticMass() + massShift + massShiftCTerm);
        }
      }

      HashMap<Integer, ArrayList<Ion>> tagFragmentIons =
          theoreticalFragmentIons.get(IonType.TAG_FRAGMENT_ION.index);
      ions = tagFragmentIons.get(TagFragmentIon.A_ION);
      if (ions != null) {
        for (Ion ion : ions) {
          ion.setTheoreticMass(ion.getTheoreticMass() + massShift + massShiftNTerm);
        }
      }
      ions = tagFragmentIons.get(TagFragmentIon.B_ION);
      if (ions != null) {
        for (Ion ion : ions) {
          ion.setTheoreticMass(ion.getTheoreticMass() + massShift + massShiftNTerm);
        }
      }
      ions = tagFragmentIons.get(TagFragmentIon.C_ION);
      if (ions != null) {
        for (Ion ion : ions) {
          ion.setTheoreticMass(ion.getTheoreticMass() + massShift + massShiftNTerm);
        }
      }
      ions = tagFragmentIons.get(TagFragmentIon.X_ION);
      if (ions != null) {
        for (Ion ion : ions) {
          ion.setTheoreticMass(ion.getTheoreticMass() + massShift + massShiftCTerm);
        }
      }
      ions = tagFragmentIons.get(TagFragmentIon.Y_ION);
      if (ions != null) {
        for (Ion ion : ions) {
          ion.setTheoreticMass(ion.getTheoreticMass() + massShift + massShiftCTerm);
        }
      }
      ions = tagFragmentIons.get(TagFragmentIon.Z_ION);
      if (ions != null) {
        for (Ion ion : ions) {
          ion.setTheoreticMass(ion.getTheoreticMass() + massShift + massShiftCTerm);
        }
      }
    }
  }