/**
   * If there are configured {@link IndexSearcherWrapper} instances, the {@link IndexSearcher} of
   * the provided engine searcher gets wrapped and a new {@link Engine.Searcher} instances is
   * returned, otherwise the provided {@link Engine.Searcher} is returned.
   *
   * <p>This is invoked each time a {@link Engine.Searcher} is requested to do an operation. (for
   * example search)
   */
  public final Engine.Searcher wrap(EngineConfig engineConfig, Engine.Searcher engineSearcher)
      throws IOException {
    final ElasticsearchDirectoryReader elasticsearchDirectoryReader =
        ElasticsearchDirectoryReader.getElasticsearchDirectoryReader(
            engineSearcher.getDirectoryReader());
    if (elasticsearchDirectoryReader == null) {
      throw new IllegalStateException("Can't wrap non elasticsearch directory reader");
    }
    NonClosingReaderWrapper nonClosingReaderWrapper =
        new NonClosingReaderWrapper(engineSearcher.getDirectoryReader());
    DirectoryReader reader = wrap(nonClosingReaderWrapper);
    if (reader != nonClosingReaderWrapper) {
      if (reader.getCoreCacheKey() != elasticsearchDirectoryReader.getCoreCacheKey()) {
        throw new IllegalStateException(
            "wrapped directory reader doesn't delegate IndexReader#getCoreCacheKey, wrappers must override this method and delegate"
                + " to the original readers core cache key. Wrapped readers can't be used as cache keys since their are used only per request which would lead to subtle bugs");
      }
      if (ElasticsearchDirectoryReader.getElasticsearchDirectoryReader(reader)
          != elasticsearchDirectoryReader) {
        // prevent that somebody wraps with a non-filter reader
        throw new IllegalStateException(
            "wrapped directory reader hides actual ElasticsearchDirectoryReader but shouldn't");
      }
    }

    final IndexSearcher innerIndexSearcher = new IndexSearcher(reader);
    innerIndexSearcher.setQueryCache(engineConfig.getQueryCache());
    innerIndexSearcher.setQueryCachingPolicy(engineConfig.getQueryCachingPolicy());
    innerIndexSearcher.setSimilarity(engineConfig.getSimilarity());
    // TODO: Right now IndexSearcher isn't wrapper friendly, when it becomes wrapper friendly we
    // should revise this extension point
    // For example if IndexSearcher#rewrite() is overwritten than also
    // IndexSearcher#createNormalizedWeight needs to be overwritten
    // This needs to be fixed before we can allow the IndexSearcher from Engine to be wrapped
    // multiple times
    final IndexSearcher indexSearcher = wrap(engineConfig, innerIndexSearcher);
    if (reader == nonClosingReaderWrapper && indexSearcher == innerIndexSearcher) {
      return engineSearcher;
    } else {
      return new Engine.Searcher(engineSearcher.source(), indexSearcher) {
        @Override
        public void close() throws ElasticsearchException {
          try {
            reader().close();
            // we close the reader to make sure wrappers can release resources if needed....
            // our NonClosingReaderWrapper makes sure that our reader is not closed
          } catch (IOException e) {
            throw new ElasticsearchException("failed to close reader", e);
          } finally {
            engineSearcher.close();
          }
        }
      };
    }
  }