/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */
package org.apache.lucene.analysis.commongrams;

import static org.apache.lucene.analysis.commongrams.CommonGramsFilter.GRAM_TYPE;

import java.io.IOException;
import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
import org.apache.lucene.analysis.tokenattributes.PositionLengthAttribute;
import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
import org.apache.lucene.util.IgnoreRandomChains;

/**
 * Wrap a CommonGramsFilter optimizing phrase queries by only returning single words when they are
 * not a member of a bigram.
 *
 * <p>Example:
 *
 * <ul>
 *   <li>query input to CommonGramsFilter: "the rain in spain falls mainly"
 *   <li>output of CommomGramsFilter/input to CommonGramsQueryFilter: |"the, "the-rain"|"rain"
 *       "rain-in"|"in, "in-spain"|"spain"|"falls"|"mainly"
 *   <li>output of CommonGramsQueryFilter:"the-rain", "rain-in" ,"in-spain", "falls", "mainly"
 * </ul>
 */

/*
 * See:http://hudson.zones.apache.org/hudson/job/Lucene-trunk/javadoc//all/org/apache/lucene/analysis/TokenStream.html and
 * http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/src/java/org/apache/lucene/analysis/package.html?revision=718798
 */
@IgnoreRandomChains(reason = "TODO: doesn't handle graph inputs")
public final class CommonGramsQueryFilter extends TokenFilter {

  private final TypeAttribute typeAttribute = addAttribute(TypeAttribute.class);
  private final PositionIncrementAttribute posIncAttribute =
      addAttribute(PositionIncrementAttribute.class);
  private final PositionLengthAttribute posLengthAttribute =
      addAttribute(PositionLengthAttribute.class);

  private State previous;
  private String previousType;
  private boolean exhausted;

  /**
   * Constructs a new CommonGramsQueryFilter based on the provided CommomGramsFilter
   *
   * @param input CommonGramsFilter the QueryFilter will use
   */
  public CommonGramsQueryFilter(CommonGramsFilter input) {
    super(input);
  }

  @Override
  public void reset() throws IOException {
    super.reset();
    previous = null;
    previousType = null;
    exhausted = false;
  }

  /**
   * Output bigrams whenever possible to optimize queries. Only output unigrams when they are not a
   * member of a bigram. Example:
   *
   * <ul>
   *   <li>input: "the rain in spain falls mainly"
   *   <li>output:"the-rain", "rain-in" ,"in-spain", "falls", "mainly"
   * </ul>
   */
  @Override
  public boolean incrementToken() throws IOException {
    while (!exhausted && input.incrementToken()) {
      State current = captureState();

      if (previous != null && !isGramType()) {
        restoreState(previous);
        previous = current;
        previousType = typeAttribute.type();

        if (isGramType()) {
          posIncAttribute.setPositionIncrement(1);
          // We must set this back to 1 (from e.g. 2 or higher) otherwise the token graph is
          // disconnected:
          posLengthAttribute.setPositionLength(1);
        }
        return true;
      }

      previous = current;
    }

    exhausted = true;

    if (previous == null || GRAM_TYPE.equals(previousType)) {
      return false;
    }

    restoreState(previous);
    previous = null;

    if (isGramType()) {
      posIncAttribute.setPositionIncrement(1);
      // We must set this back to 1 (from e.g. 2 or higher) otherwise the token graph is
      // disconnected:
      posLengthAttribute.setPositionLength(1);
    }
    return true;
  }

  /**
   * Convenience method to check if the current type is a gram type
   *
   * @return {@code true} if the current type is a gram type, {@code false} otherwise
   */
  public boolean isGramType() {
    return GRAM_TYPE.equals(typeAttribute.type());
  }
}
