/** {@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();
  }
  /** {@inheritDoc} */
  @Override
  public List<SearchResult<SequenceMatcher>> searchForwards(
      final byte[] bytes, final int fromPosition, final int toPosition) {

    // 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;

    // Determine a safe position to start searching at.
    final int lastMatcherPosition = getMatcher().length() - 1;
    int searchPosition =
        fromPosition > 0 ? fromPosition + lastMatcherPosition : lastMatcherPosition;

    // Calculate safe bounds for the end of the search:
    final int lastPossiblePosition = bytes.length - 1;
    final int lastPossibleSearchPosition = toPosition + lastMatcherPosition;
    final int finalPosition =
        lastPossibleSearchPosition < lastPossiblePosition
            ? lastPossibleSearchPosition
            : lastPossiblePosition;

    // Search forwards:
    while (searchPosition <= finalPosition) {

      // Shift forwards until we match the last position in the sequence,
      // or we run out of search space (in which case just return not found).
      byte currentByte = bytes[searchPosition];
      while (!endOfSequence.matches(currentByte)) {
        searchPosition += safeShifts[currentByte & 0xff];
        if (searchPosition > finalPosition) {
          return SearchUtils.noResults();
        }
        currentByte = bytes[searchPosition];
      }

      // The last byte matched - verify there is a complete match:
      final int startMatchPosition = searchPosition - lastMatcherPosition;
      if (verifier.matchesNoBoundsCheck(bytes, startMatchPosition)) {
        return SearchUtils.singleResult(startMatchPosition, matcher); // match found.
      }

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

    return SearchUtils.noResults();
  }
  /** {@inheritDoc} */
  @Override
  public List<SearchResult<SequenceMatcher>> searchBackwards(
      final byte[] bytes, final int fromPosition, final int toPosition) {

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

    // Calculate safe bounds for the start of the search:
    final int firstPossiblePosition = bytes.length - getMatcher().length();
    int searchPosition =
        fromPosition < firstPossiblePosition ? fromPosition : firstPossiblePosition;

    // Calculate safe bounds for the end of the search:
    final int lastPosition = toPosition > 0 ? toPosition : 0;

    // Search backwards:
    while (searchPosition >= lastPosition) {

      // Shift backwards until we match the first position in the
      // sequence, or we run out of search space:
      byte currentByte = bytes[searchPosition];
      while (!startOfSequence.matches(currentByte)) {
        searchPosition -= safeShifts[currentByte & 0xFF];
        if (searchPosition < lastPosition) {
          return SearchUtils.noResults();
        }
        currentByte = bytes[searchPosition];
      }

      // The first byte matched - verify there is a complete match.
      // There is only a verifier if the sequence length was greater than one;
      // if the sequence is only one in length, we have already found it.
      if (verifier == null || verifier.matchesNoBoundsCheck(bytes, searchPosition + 1)) {
        return SearchUtils.singleResult(searchPosition, matcher); // match found.
      }

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

    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();
  }