package org.apache.lucene.search;
/**
 * Copyright 2005 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


import org.apache.lucene.index.IndexReader;

import java.io.IOException;

/**
 * Wraps another SpanFilter's result and caches it.  The purpose is to allow
 * filters to simply filter, and then wrap with this class to add caching.
 */
public class CachingSpanFilter extends SpanFilter {
  private SpanFilter filter;

  /**
   * A transient Filter cache (package private because of test)
   */
  private final CachingWrapperFilter.FilterCache<SpanFilterResult> cache;

  /**
   * New deletions always result in a cache miss, by default
   * ({@link CachingWrapperFilter.DeletesMode#RECACHE}.
   * @param filter Filter to cache results of
   */
  public CachingSpanFilter(SpanFilter filter) {
    this(filter, CachingWrapperFilter.DeletesMode.RECACHE);
  }

  /**
   * @param filter Filter to cache results of
   * @param deletesMode See {@link CachingWrapperFilter.DeletesMode}
   */
  public CachingSpanFilter(SpanFilter filter, CachingWrapperFilter.DeletesMode deletesMode) {
    this.filter = filter;
    if (deletesMode == CachingWrapperFilter.DeletesMode.DYNAMIC) {
      throw new IllegalArgumentException("DeletesMode.DYNAMIC is not supported");
    }
    this.cache = new CachingWrapperFilter.FilterCache<SpanFilterResult>(deletesMode) {
      @Override
      protected SpanFilterResult mergeDeletes(final IndexReader reader, final SpanFilterResult value) {
        throw new IllegalStateException("DeletesMode.DYNAMIC is not supported");
      }
    };
  }

  @Override
  public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
    SpanFilterResult result = getCachedResult(reader);
    return result != null ? result.getDocIdSet() : null;
  }
  
  // for testing
  int hitCount, missCount;

  private SpanFilterResult getCachedResult(IndexReader reader) throws IOException {

    final Object coreKey = reader.getCoreCacheKey();
    final Object delCoreKey = reader.hasDeletions() ? reader.getDeletesCacheKey() : coreKey;

    SpanFilterResult result = cache.get(reader, coreKey, delCoreKey);
    if (result != null) {
      hitCount++;
      return result;
    }

    missCount++;
    result = filter.bitSpans(reader);

    cache.put(coreKey, delCoreKey, result);
    return result;
  }


  @Override
  public SpanFilterResult bitSpans(IndexReader reader) throws IOException {
    return getCachedResult(reader);
  }

  @Override
  public String toString() {
    return "CachingSpanFilter("+filter+")";
  }

  @Override
  public boolean equals(Object o) {
    if (!(o instanceof CachingSpanFilter)) return false;
    return this.filter.equals(((CachingSpanFilter)o).filter);
  }

  @Override
  public int hashCode() {
    return filter.hashCode() ^ 0x1117BF25;
  }
}
