@Override
  protected void finish() {
    final MetricsFile<PreAdapterSummaryMetrics, Integer> preAdapterSummaryMetricsFile =
        getMetricsFile();
    final MetricsFile<PreAdapterDetailMetrics, Integer> preAdapterDetailMetricsFile =
        getMetricsFile();
    final MetricsFile<BaitBiasSummaryMetrics, Integer> baitBiasSummaryMetricsFile =
        getMetricsFile();
    final MetricsFile<BaitBiasDetailMetrics, Integer> baitBiasDetailMetricsFile = getMetricsFile();

    for (final ArtifactCounter counter : artifactCounters.values()) {
      // build metrics
      counter.finish();

      // write metrics
      preAdapterSummaryMetricsFile.addAllMetrics(counter.getPreAdapterSummaryMetrics());
      baitBiasSummaryMetricsFile.addAllMetrics(counter.getBaitBiasSummaryMetrics());

      for (final PreAdapterDetailMetrics preAdapterDetailMetrics :
          counter.getPreAdapterDetailMetrics()) {
        if (CONTEXTS_TO_PRINT.isEmpty()
            || CONTEXTS_TO_PRINT.contains(preAdapterDetailMetrics.CONTEXT)) {
          preAdapterDetailMetricsFile.addMetric(preAdapterDetailMetrics);
        }
      }
      for (final BaitBiasDetailMetrics baitBiasDetailMetrics : counter.getBaitBiasDetailMetrics()) {
        if (CONTEXTS_TO_PRINT.isEmpty()
            || CONTEXTS_TO_PRINT.contains(baitBiasDetailMetrics.CONTEXT)) {
          baitBiasDetailMetricsFile.addMetric(baitBiasDetailMetrics);
        }
      }
    }

    preAdapterDetailMetricsFile.write(preAdapterDetailsOut);
    preAdapterSummaryMetricsFile.write(preAdapterSummaryOut);
    baitBiasDetailMetricsFile.write(baitBiasDetailsOut);
    baitBiasSummaryMetricsFile.write(baitBiasSummaryOut);
  }
  @Override
  protected void acceptRead(final SAMRecord rec, final ReferenceSequence ref) {
    // see if the whole read should be skipped
    if (recordFilter.filterOut(rec)) return;

    // check read group + library
    final String library =
        (rec.getReadGroup() == null)
            ? UNKNOWN_LIBRARY
            : getOrElse(rec.getReadGroup().getLibrary(), UNKNOWN_LIBRARY);
    if (!libraries.contains(library)) {
      // should never happen if SAM is valid
      throw new PicardException("Record contains library that is missing from header: " + library);
    }

    // set up some constants that don't change in the loop below
    final int contextFullLength = 2 * CONTEXT_SIZE + 1;
    final ArtifactCounter counter = artifactCounters.get(library);
    final byte[] readBases = rec.getReadBases();
    final byte[] readQuals;
    if (USE_OQ) {
      final byte[] tmp = rec.getOriginalBaseQualities();
      readQuals = tmp == null ? rec.getBaseQualities() : tmp;
    } else {
      readQuals = rec.getBaseQualities();
    }

    // iterate over aligned positions
    for (final AlignmentBlock block : rec.getAlignmentBlocks()) {
      for (int offset = 0; offset < block.getLength(); offset++) {
        // remember, these are 1-based!
        final int readPos = block.getReadStart() + offset;
        final int refPos = block.getReferenceStart() + offset;

        // skip low BQ sites
        final byte qual = readQuals[readPos - 1];
        if (qual < MINIMUM_QUALITY_SCORE) continue;

        // skip N bases in read
        final char readBase = Character.toUpperCase((char) readBases[readPos - 1]);
        if (readBase == 'N') continue;

        /**
         * Skip regions outside of intervals.
         *
         * <p>NB: IntervalListReferenceSequenceMask.get() has side-effects which assume that
         * successive ReferenceSequence's passed to this method will be in-order (e.g. it will break
         * if you call acceptRead() with chr1, then chr2, then chr1 again). So this only works if
         * the underlying iteration is coordinate-sorted.
         */
        if (intervalMask != null && !intervalMask.get(ref.getContigIndex(), refPos)) continue;

        // skip dbSNP sites
        if (dbSnpMask != null && dbSnpMask.isDbSnpSite(ref.getName(), refPos)) continue;

        // skip the ends of the reference
        final int contextStartIndex = refPos - CONTEXT_SIZE - 1;
        if (contextStartIndex < 0 || contextStartIndex + contextFullLength > ref.length()) continue;

        // skip contexts with N bases
        final String context = getRefContext(ref, contextStartIndex, contextFullLength);
        if (context.contains("N")) continue;

        // count the base!
        counter.countRecord(context, readBase, rec);
      }
    }
  }