/**
   * Method that combines the fingerprint evidence across all the read groups for the same sample
   * and then produces a matrix of LOD scores for comparing every sample with every other sample.
   */
  private void crossCheckSamples(final List<Fingerprint> fingerprints, final PrintStream out) {
    final SortedMap<String, Fingerprint> sampleFps =
        FingerprintChecker.mergeFingerprintsBySample(fingerprints);
    final SortedSet<String> samples = (SortedSet<String>) sampleFps.keySet();

    // Print header row
    out.print("\t");
    for (final String sample : samples) {
      out.print(sample);
      out.print("\t");
    }
    out.println();

    // Print results rows
    for (final String sample : samples) {
      out.print(sample);
      final Fingerprint fp = sampleFps.get(sample);

      for (final String otherSample : samples) {
        final MatchResults results =
            FingerprintChecker.calculateMatchResults(
                fp, sampleFps.get(otherSample), GENOTYPING_ERROR_RATE, LOSS_OF_HET_RATE);
        out.print("\t");
        out.print(formatUtil.format(results.getLOD()));
      }

      out.println();
    }
  }
  /**
   * Method that pairwise checks every pair of read groups and reports a LOD score for the two read
   * groups coming from the same sample.
   */
  private int crossCheckReadGroups(
      final Map<SAMReadGroupRecord, Fingerprint> fingerprints, final PrintStream out) {
    int mismatches = 0;
    int unexpectedMatches = 0;

    final List<SAMReadGroupRecord> readGroupRecords = new ArrayList<>(fingerprints.keySet());
    final List<String> output = new ArrayList<>();

    for (int i = 0; i < readGroupRecords.size(); i++) {
      final SAMReadGroupRecord lhsRg = readGroupRecords.get(i);
      for (int j = i + 1; j < readGroupRecords.size(); j++) {
        final SAMReadGroupRecord rhsRg = readGroupRecords.get(j);
        final boolean expectedToMatch =
            EXPECT_ALL_READ_GROUPS_TO_MATCH || lhsRg.getSample().equals(rhsRg.getSample());

        final MatchResults results =
            FingerprintChecker.calculateMatchResults(
                fingerprints.get(lhsRg),
                fingerprints.get(rhsRg),
                GENOTYPING_ERROR_RATE,
                LOSS_OF_HET_RATE);
        if (expectedToMatch) {
          if (results.getLOD() < LOD_THRESHOLD) {
            mismatches++;
            output.add(getMatchDetails(UNEXPECTED_MISMATCH, results, lhsRg, rhsRg));
          } else {
            if (!OUTPUT_ERRORS_ONLY) {
              output.add(getMatchDetails(EXPECTED_MATCH, results, lhsRg, rhsRg));
            }
          }
        } else {
          if (results.getLOD() > -LOD_THRESHOLD) {
            unexpectedMatches++;
            output.add(getMatchDetails(UNEXPECTED_MATCH, results, lhsRg, rhsRg));
          } else {
            if (!OUTPUT_ERRORS_ONLY) {
              output.add(getMatchDetails(EXPECTED_MISMATCH, results, lhsRg, rhsRg));
            }
          }
        }
      }
    }

    if (!output.isEmpty()) {
      out.println(
          "RESULT\tLOD_SCORE\tLOD_SCORE_TUMOR_NORMAL\tLOD_SCORE_NORMAL_TUMOR\tLEFT_RUN_BARCODE\tLEFT_LANE\tLEFT_MOLECULAR_BARCODE_SEQUENCE\tLEFT_LIBRARY\tLEFT_SAMPLE\t"
              + "RIGHT_RUN_BARCODE\tRIGHT_LANE\tRIGHT_MOLECULAR_BARCODE_SEQUENCE\tRIGHT_LIBRARY\tRIGHT_SAMPLE");
      out.println(String.join("\n", output));
    }

    if (mismatches + unexpectedMatches > 0) {
      log.info("WARNING: At least two read groups did not relate as expected.");
      return EXIT_CODE_WHEN_MISMATCH;
    } else {
      log.info("All read groups related as expected.");
      return 0;
    }
  }
  @Override
  protected int doWork() {
    // Check inputs
    for (final File f : INPUT) IOUtil.assertFileIsReadable(f);
    IOUtil.assertFileIsReadable(HAPLOTYPE_MAP);
    if (OUTPUT != null) IOUtil.assertFileIsWritable(OUTPUT);

    final HaplotypeMap map = new HaplotypeMap(HAPLOTYPE_MAP);
    final FingerprintChecker checker = new FingerprintChecker(map);

    checker.setAllowDuplicateReads(ALLOW_DUPLICATE_READS);

    log.info("Done checking input files, moving onto fingerprinting files.");

    List<File> unrolledFiles =
        IOUtil.unrollFiles(INPUT, BamFileIoUtils.BAM_FILE_EXTENSION, IOUtil.SAM_FILE_EXTENSION);
    final Map<SAMReadGroupRecord, Fingerprint> fpMap =
        checker.fingerprintSamFiles(unrolledFiles, NUM_THREADS, 1, TimeUnit.DAYS);
    final List<Fingerprint> fingerprints = new ArrayList<>(fpMap.values());

    log.info("Finished generating fingerprints from BAM files, moving on to cross-checking.");

    // Setup the output
    final PrintStream out;
    if (OUTPUT != null) {
      out = new PrintStream(IOUtil.openFileForWriting(OUTPUT), true);
    } else {
      out = System.out;
    }

    if (this.CROSSCHECK_SAMPLES) {
      crossCheckSamples(fingerprints, out);
      return 0;
    } else if (this.CROSSCHECK_LIBRARIES) {
      crossCheckLibraries(fpMap, out);
      return 0;
    } else {
      return crossCheckReadGroups(fpMap, out);
    }
  }