docs: public int expect(final List<Match> pairs) throws Exception {
          logger.trace("Searching for " + pairs.size() + " patterns in the reader stream");
  
          // Buckets
          EofMatch eofMatch = null;
          TimeoutMatch timeoutMatch = null;
          List<Match> patternMatches = new ArrayList<>();
  
          // Fill buckets in one swoop
          Iterator<Match> iter = pairs.iterator();
          while (iter.hasNext()) {
              Match match = iter.next();
              if (!(match instanceof Match)) {
                  logger.debug("Object " + match + " is not of type expect4j.matches.Match, cannot use as a pattern");
                  continue;
              } else if (match instanceof PatternPair) {
                  logger.trace("Searching for PatternPair " + match + " in the reader stream");
                  patternMatches.add( match );
              } else if (match instanceof TimeoutMatch) {
                  logger.trace("Registering custom TimeoutMatch handler " + match);
                  timeoutMatch = (TimeoutMatch) match;
              } else if (match instanceof EofMatch) {
                  logger.trace("Registering custom EofMatch handler " + match);
                  eofMatch = (EofMatch) match;
              } else {
                  logger.debug("Unexpected match object " + match + " found in the pattern list, ignoring");
              }
          }
  
          // if( eofMatch == null ) eofMatch = new EofMatch();
          // if( timeoutMatch == null ) // that's ok
          long timeout;
          if (timeoutMatch != null && timeoutMatch.getTimeout() != TIMEOUT_NOTSET) {
              timeout = timeoutMatch.getTimeout();
          } else {
              timeout = defaultTimeout;
          }
  
          long startTime = System.currentTimeMillis();
          long endTime = startTime + timeout;
  
          logger.debug("Timeout set to " + timeout + " milliseconds, expires at " + endTime);
  
          boolean foundTimeout = false;
          boolean foundEof = false;
          int index = RET_UNKNOWN;
  
          // New states are based on g_state and they need to see a null
          // g_state on the first match
          g_state = null;
  
          // Primary loop, which only really continues if
          // State.exp_continue() is called or no match was found
          String toMatch = null; // from last pause call
          while (true) {
              if (timeout != TIMEOUT_FOREVER && System.currentTimeMillis() >= endTime) {
                  logger.debug("Detection timeout expired");
                  foundTimeout = true;
                  break;
              }
  
              synchronized(consumer) { // while matching
                  toMatch = consumer.pause();
                  logger.trace("Size of toMatch is " + toMatch.length());
                  // make sure to resume before any break/continue
                  foundEof = consumer.foundEOF(); // has to be called before resume
  
                  input.setInput(toMatch);
  
                  logger.debug("Finding first match using >>>" + printBuffer() + "<<< as the haystack");
  
                  boolean foundMatch = false;
                  try {
                      foundMatch = runFirstMatch(patternMatches);
                  } catch (Exception e) {
                      logger.warn("Forwarding an exception that occurred in a Closure: " + e);
                      consumer.resume();
                      throw e;
                  }
  
                  // Both paths call resume.
                  if (foundMatch) {
                      int matchedWhere = g_state.getMatchedWhere();
                      int matchedLength = g_state.getMatch().length();
                      logger.debug("Matched @ " + matchedWhere + " with a length of " + matchedLength);
  
                      // resume consumer, at a later offset
                      consumer.resume(matchedWhere + matchedLength);
  
                      // find index to return
                      PatternPair singlepair = (PatternPair) patternMatches.get( g_state.getPairIndex() );
                      logger.trace("Pair found " + singlepair.getPattern().getPattern() );
                      index = pairs.indexOf( singlepair );
                      logger.trace("Index found " + index);
  
                      if (!g_state.shouldContinue()) {
                          logger.trace("NOT Continuing");
                          break;
                      } else {
                          logger.trace("Continuing");
                      }
  
                      if (g_state.shouldResetTimer()) {
                          // keep start time where it is
                          endTime = System.currentTimeMillis() + timeout;
                      }
                      continue; // skips waitForBuffer since buffer might already have what we're looking for
  
                  } else {
                      logger.trace("Nothing found, resuming consumer");
                      consumer.resume();
                      if (timeout == TIMEOUT_NEVER) {
                          // The timeout variables tells us that we shouldn't try again
                          // TODO Find out if this triggers the Timeout match
                          index = RET_TRIED_ONCE;
                          break;
                      }
                  }
                  if (foundEof) {
                      logger.debug("Found EOF");
                      break;
                  }
  
                  if (timeout == TIMEOUT_FOREVER) {
                      consumer.waitForBuffer(TIMEOUT_FOREVER);
                  } else {
                      long singleTimeout = endTime - System.currentTimeMillis();
                      logger.trace("singleTimeout: " + singleTimeout);
                      if (timeout != TIMEOUT_FOREVER && singleTimeout <= 0) {
                          // we might have gone over the timeout already
                          // restart while loop, that the typical logic takes hold
                          continue;
                      }
                      logger.debug("Waiting for more input");
                      consumer.waitForBuffer(singleTimeout);
                  }
              }
          } // end while
          logger.trace("Leaving main while loop");
  
          Match lastmile = null;
          String lastmileBuffer = null;
          if (foundTimeout) { //removed index == -1
              logger.trace("Dealing with Timeout");
              if (timeoutMatch == null)
                  index = RET_TIMEOUT; // Timeout with a Timeout match
              else
                  lastmile = timeoutMatch;
          }
  
          if (foundEof)  {
              logger.info("Dealing with EOF " + eofMatch);
              if (eofMatch != null) {
                  lastmileBuffer = toMatch;
                  lastmile = eofMatch;
              } else if (index == -1) {
                  index = RET_EOF;
              }
          }
  
          // We're in the final stretch, but we might have one last closure to run.
          if (lastmile != null) {
              logger.trace("Running last mile");
  
              //TODO provide buffer vars for EOF
              Closure closure = lastmile.getClosure();
              index = pairs.indexOf(lastmile);
  
              ExpectState state = prepareClosure(index, lastmileBuffer);
              try {
                  if (closure != null)
                      closure.run(state);
              } catch (Exception e) {
                  logger.warn("Forwarding an exception that occurred in a Closure: " + e);
                  logger.trace("Closure body: " + closure.toString());
                  throw e;
              } finally {
                  g_state = state;
              }
          }
          return index;
      }