/** {@inheritDoc} */
  @Override
  protected List<SearchResult<SequenceMatcher>> doSearchBackwards(
      final WindowReader reader, final long fromPosition, final long toPosition)
      throws IOException {

    // Initialise:
    final SearchInfo info = backwardInfo.get();
    final int[] safeShifts = info.shifts;
    final ByteMatcher startOfSequence = info.matcher;
    final SequenceMatcher verifier = info.verifier;
    long searchPosition = fromPosition;

    // Search backwards across the windows:
    Window window;
    while (searchPosition >= toPosition && (window = reader.getWindow(searchPosition)) != null) {

      // Initialise the window search:
      final byte[] array = window.getArray();
      final int arrayStartPosition = reader.getWindowOffset(searchPosition);
      final long distanceToEnd = toPosition - window.getWindowPosition();
      final int lastSearchPosition = distanceToEnd > 0 ? (int) distanceToEnd : 0;
      int arraySearchPosition = arrayStartPosition;

      // Search using the byte array for shifts, using the WindowReader
      // for verifiying the sequence with the matcher:
      ARRAY_SEARCH:
      while (arraySearchPosition >= lastSearchPosition) {

        // Shift backwards until we match the first position in the sequence,
        // or we run out of search space.
        byte currentByte = array[arraySearchPosition];
        while (!startOfSequence.matches(currentByte)) {
          arraySearchPosition -= safeShifts[currentByte & 0xff];
          if (arraySearchPosition < lastSearchPosition) {
            break ARRAY_SEARCH;
          }
          currentByte = array[arraySearchPosition];
        }

        // The first byte matched - verify there is a complete match.
        final int totalShift = arrayStartPosition - arraySearchPosition;
        final long sequencePosition = searchPosition - totalShift;
        if (verifier == null || verifier.matches(reader, sequencePosition + 1)) {
          return SearchUtils.singleResult(sequencePosition, matcher); // match found.
        }

        // No match was found - shift backward by the shift for the current byte:
        arraySearchPosition -= safeShifts[currentByte & 0xff];
      }

      // No match was found in this array - calculate the current search position:
      searchPosition -= (arrayStartPosition - arraySearchPosition);
    }

    return SearchUtils.noResults();
  }
  /**
   * Searches forward using the Boyer Moore Horspool algorithm, using byte arrays from Windows to
   * handle shifting, and the WindowReader interface on the SequenceMatcher to verify whether a
   * match exists.
   */
  @Override
  protected List<SearchResult<SequenceMatcher>> doSearchForwards(
      final WindowReader reader, final long fromPosition, final long toPosition)
      throws IOException {

    // Get the objects needed to search:
    final SearchInfo info = forwardInfo.get();
    final int[] safeShifts = info.shifts;
    final ByteMatcher endOfSequence = info.matcher;
    final SequenceMatcher verifier = info.verifier;

    // Initialise window search:
    final long endSequencePosition = matcher.length() - 1;
    final long finalPosition = toPosition + endSequencePosition;
    long searchPosition = fromPosition + endSequencePosition;

    // While there is a window to search in:
    Window window;
    while (searchPosition <= finalPosition && (window = reader.getWindow(searchPosition)) != null) {

      // Initialise array search:
      final byte[] array = window.getArray();
      final int arrayStartPosition = reader.getWindowOffset(searchPosition);
      final int arrayEndPosition = window.length() - 1;
      final int lastMatcherPosition = matcher.length() - 1;
      final long distanceToEnd = finalPosition - window.getWindowPosition() + lastMatcherPosition;
      final int lastSearchPosition =
          distanceToEnd < arrayEndPosition ? (int) distanceToEnd : arrayEndPosition;
      int arraySearchPosition = arrayStartPosition;

      // Search forwards in this array:
      ARRAY_SEARCH:
      while (arraySearchPosition <= lastSearchPosition) {

        // Shift forwards until we match the last position in the sequence,
        // or we run out of search space.
        byte currentByte = array[arraySearchPosition];
        while (!endOfSequence.matches(currentByte)) {
          arraySearchPosition += safeShifts[currentByte & 0xff];
          if (arraySearchPosition > lastSearchPosition) {
            break ARRAY_SEARCH; // outside the array, move on.
          }
          currentByte = array[arraySearchPosition];
        }

        // The last byte matched - verify there is a complete match:
        final long arrayBytesSearched = arraySearchPosition - arrayStartPosition;
        final long matchPosition = searchPosition + arrayBytesSearched - endSequencePosition;
        if (verifier.matches(reader, matchPosition)) {
          return SearchUtils.singleResult(matchPosition, matcher); // match found.
        }

        // No match was found - shift forward by the shift for the current byte:
        arraySearchPosition += safeShifts[currentByte & 0xff];
      }

      // No match was found in this array - calculate the current search position:
      searchPosition += arraySearchPosition - arrayStartPosition;
    }

    return SearchUtils.noResults();
  }