/** @see Runnable#run() */
  public void run() {

    if ((mzWeight == 0) && (rtWeight == 0)) {
      setStatus(TaskStatus.ERROR);
      errorMessage = "Cannot run alignment, all the weight parameters are zero";
      return;
    }

    setStatus(TaskStatus.PROCESSING);
    logger.info("Running join aligner");

    // Remember how many rows we need to process. Each row will be processed
    // twice, first for score calculation, second for actual alignment.
    for (int i = 0; i < peakLists.length; i++) {
      totalRows += peakLists[i].getNumberOfRows() * 2;
    }

    // Collect all data files
    Vector<RawDataFile> allDataFiles = new Vector<RawDataFile>();
    for (PeakList peakList : peakLists) {

      for (RawDataFile dataFile : peakList.getRawDataFiles()) {

        // Each data file can only have one column in aligned peak list
        if (allDataFiles.contains(dataFile)) {
          setStatus(TaskStatus.ERROR);
          errorMessage =
              "Cannot run alignment, because file "
                  + dataFile
                  + " is present in multiple peak lists";
          return;
        }

        allDataFiles.add(dataFile);
      }
    }

    // Create a new aligned peak list
    alignedPeakList = new SimplePeakList(peakListName, allDataFiles.toArray(new RawDataFile[0]));

    // Iterate source peak lists
    for (PeakList peakList : peakLists) {

      // Create a sorted set of scores matching
      TreeSet<RowVsRowScore> scoreSet = new TreeSet<RowVsRowScore>();

      PeakListRow allRows[] = peakList.getRows();

      // Calculate scores for all possible alignments of this row
      for (PeakListRow row : allRows) {

        if (isCanceled()) return;

        // Calculate limits for a row with which the row can be aligned
        Range mzRange = mzTolerance.getToleranceRange(row.getAverageMZ());
        Range rtRange = rtTolerance.getToleranceRange(row.getAverageRT());

        // Get all rows of the aligned peaklist within parameter limits
        PeakListRow candidateRows[] = alignedPeakList.getRowsInsideScanAndMZRange(rtRange, mzRange);

        // Calculate scores and store them
        for (PeakListRow candidate : candidateRows) {

          if (sameChargeRequired) {
            if (!PeakUtils.compareChargeState(row, candidate)) continue;
          }

          if (sameIDRequired) {
            if (!PeakUtils.compareIdentities(row, candidate)) continue;
          }

          if (compareIsotopePattern) {
            IsotopePattern ip1 = row.getBestIsotopePattern();
            IsotopePattern ip2 = candidate.getBestIsotopePattern();

            if ((ip1 != null) && (ip2 != null)) {
              ParameterSet isotopeParams =
                  parameters
                      .getParameter(JoinAlignerParameters.compareIsotopePattern)
                      .getEmbeddedParameters();

              if (!IsotopePatternScoreCalculator.checkMatch(ip1, ip2, isotopeParams)) {
                continue;
              }
            }
          }

          RowVsRowScore score =
              new RowVsRowScore(
                  row, candidate, mzRange.getSize() / 2, mzWeight, rtRange.getSize() / 2, rtWeight);

          scoreSet.add(score);
        }

        processedRows++;
      }

      // Create a table of mappings for best scores
      Hashtable<PeakListRow, PeakListRow> alignmentMapping =
          new Hashtable<PeakListRow, PeakListRow>();

      // Iterate scores by descending order
      Iterator<RowVsRowScore> scoreIterator = scoreSet.iterator();
      while (scoreIterator.hasNext()) {

        RowVsRowScore score = scoreIterator.next();

        // Check if the row is already mapped
        if (alignmentMapping.containsKey(score.getPeakListRow())) continue;

        // Check if the aligned row is already filled
        if (alignmentMapping.containsValue(score.getAlignedRow())) continue;

        alignmentMapping.put(score.getPeakListRow(), score.getAlignedRow());
      }

      // Align all rows using mapping
      for (PeakListRow row : allRows) {

        PeakListRow targetRow = alignmentMapping.get(row);

        // If we have no mapping for this row, add a new one
        if (targetRow == null) {
          targetRow = new SimplePeakListRow(newRowID);
          newRowID++;
          alignedPeakList.addRow(targetRow);
        }

        // Add all peaks from the original row to the aligned row
        for (RawDataFile file : row.getRawDataFiles()) {
          targetRow.addPeak(file, row.getPeak(file));
        }

        // Add all non-existing identities from the original row to the
        // aligned row
        PeakUtils.copyPeakListRowProperties(row, targetRow);

        processedRows++;
      }
    } // Next peak list

    // Add new aligned peak list to the project
    MZmineProject currentProject = MZmineCore.getCurrentProject();
    currentProject.addPeakList(alignedPeakList);

    // Add task description to peakList
    alignedPeakList.addDescriptionOfAppliedTask(
        new SimplePeakListAppliedMethod("Join aligner", parameters));

    logger.info("Finished join aligner");
    setStatus(TaskStatus.FINISHED);
  }
  /**
   * Filter the peak list.
   *
   * @param peakList peak list to filter.
   * @return a new peak list with entries of the original peak list that pass the filtering.
   */
  private PeakList filterPeakList(final PeakList peakList) {

    // Make a copy of the peakList
    final PeakList newPeakList =
        new SimplePeakList(
            peakList.getName()
                + ' '
                + parameters.getParameter(RowsFilterParameters.SUFFIX).getValue(),
            peakList.getRawDataFiles());

    // Get parameters - which filters are active
    final boolean filterByDuration =
        parameters.getParameter(PeakFilterParameters.PEAK_DURATION).getValue();
    final boolean filterByArea = parameters.getParameter(PeakFilterParameters.PEAK_AREA).getValue();
    final boolean filterByHeight =
        parameters.getParameter(PeakFilterParameters.PEAK_HEIGHT).getValue();
    final boolean filterByDatapoints =
        parameters.getParameter(PeakFilterParameters.PEAK_DATAPOINTS).getValue();
    final boolean filterByFWHM = parameters.getParameter(PeakFilterParameters.PEAK_FWHM).getValue();
    final boolean filterByTailingFactor =
        parameters.getParameter(PeakFilterParameters.PEAK_TAILINGFACTOR).getValue();
    final boolean filterByAsymmetryFactor =
        parameters.getParameter(PeakFilterParameters.PEAK_ASYMMETRYFACTOR).getValue();

    // Loop through all rows in peak list
    final PeakListRow[] rows = peakList.getRows();
    totalRows = rows.length;
    for (processedRows = 0; !isCanceled() && processedRows < totalRows; processedRows++) {
      final PeakListRow row = rows[processedRows];
      final RawDataFile[] rawdatafiles = row.getRawDataFiles();
      int totalRawDataFiles = rawdatafiles.length;
      boolean[] keepPeak = new boolean[totalRawDataFiles];

      for (int i = 0; i < totalRawDataFiles; i++) {
        // Peak values
        keepPeak[i] = true;
        final Feature peak = row.getPeak(rawdatafiles[i]);
        final double peakDuration =
            peak.getRawDataPointsRTRange().upperEndpoint()
                - peak.getRawDataPointsRTRange().lowerEndpoint();
        final double peakArea = peak.getArea();
        final double peakHeight = peak.getHeight();
        final int peakDatapoints = peak.getScanNumbers().length;

        Double peakFWHM = peak.getFWHM();
        Double peakTailingFactor = peak.getTailingFactor();
        Double peakAsymmetryFactor = peak.getAsymmetryFactor();
        if (peakFWHM == null) {
          peakFWHM = -1.0;
        }
        if (peakTailingFactor == null) {
          peakTailingFactor = -1.0;
        }
        if (peakAsymmetryFactor == null) {
          peakAsymmetryFactor = -1.0;
        }

        // Check Duration
        if (filterByDuration) {
          final Range<Double> durationRange =
              parameters
                  .getParameter(PeakFilterParameters.PEAK_DURATION)
                  .getEmbeddedParameter()
                  .getValue();
          if (!durationRange.contains(peakDuration)) {
            // Mark peak to be removed
            keepPeak[i] = false;
          }
        }

        // Check Area
        if (filterByArea) {
          final Range<Double> areaRange =
              parameters
                  .getParameter(PeakFilterParameters.PEAK_AREA)
                  .getEmbeddedParameter()
                  .getValue();
          if (!areaRange.contains(peakArea)) {
            // Mark peak to be removed
            keepPeak[i] = false;
          }
        }

        // Check Height
        if (filterByHeight) {
          final Range<Double> heightRange =
              parameters
                  .getParameter(PeakFilterParameters.PEAK_HEIGHT)
                  .getEmbeddedParameter()
                  .getValue();
          if (!heightRange.contains(peakHeight)) {
            // Mark peak to be removed
            keepPeak[i] = false;
          }
        }

        // Check # Data Points
        if (filterByDatapoints) {
          final Range<Integer> datapointsRange =
              parameters
                  .getParameter(PeakFilterParameters.PEAK_DATAPOINTS)
                  .getEmbeddedParameter()
                  .getValue();
          if (!datapointsRange.contains(peakDatapoints)) {
            // Mark peak to be removed
            keepPeak[i] = false;
          }
        }

        // Check FWHM
        if (filterByFWHM) {
          final Range<Double> fwhmRange =
              parameters
                  .getParameter(PeakFilterParameters.PEAK_FWHM)
                  .getEmbeddedParameter()
                  .getValue();
          if (!fwhmRange.contains(peakFWHM)) {
            // Mark peak to be removed
            keepPeak[i] = false;
          }
        }

        // Check Tailing Factor
        if (filterByTailingFactor) {
          final Range<Double> tailingRange =
              parameters
                  .getParameter(PeakFilterParameters.PEAK_TAILINGFACTOR)
                  .getEmbeddedParameter()
                  .getValue();
          if (!tailingRange.contains(peakTailingFactor)) {
            // Mark peak to be removed
            keepPeak[i] = false;
          }
        }

        // Check height
        if (filterByAsymmetryFactor) {
          final Range<Double> asymmetryRange =
              parameters
                  .getParameter(PeakFilterParameters.PEAK_ASYMMETRYFACTOR)
                  .getEmbeddedParameter()
                  .getValue();
          if (!asymmetryRange.contains(peakAsymmetryFactor)) {
            // Mark peak to be removed
            keepPeak[i] = false;
          }
        }
      }

      newPeakList.addRow(copyPeakRow(row, keepPeak));
    }

    return newPeakList;
  }