package mtas.search.spans;

import java.io.IOException;
import java.util.Map;
import java.util.Set;

import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermContext;
import org.apache.lucene.index.Terms;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.spans.SpanWeight;
import org.apache.lucene.search.spans.Spans;

import mtas.search.spans.util.MtasSpanQuery;
import mtas.search.spans.util.MtasSpanWeight;
import mtas.search.spans.util.MtasSpans;

/**
 * The Class MtasSpanRecurrenceQuery.
 */
public class MtasSpanRecurrenceQuery extends MtasSpanQuery {

  /** The query. */
  private MtasSpanQuery query;

  /** The minimum recurrence. */
  private int minimumRecurrence;

  /** The maximum recurrence. */
  private int maximumRecurrence;

  /** The ignore query. */
  private MtasSpanQuery ignoreQuery;

  /** The maximum ignore length. */
  private Integer maximumIgnoreLength;

  /** The field. */
  private String field;

  /**
   * Instantiates a new mtas span recurrence query.
   *
   * @param query the query
   * @param minimumRecurrence the minimum recurrence
   * @param maximumRecurrence the maximum recurrence
   * @param ignoreQuery the ignore query
   * @param maximumIgnoreLength the maximum ignore length
   */
  public MtasSpanRecurrenceQuery(MtasSpanQuery query, int minimumRecurrence,
      int maximumRecurrence, MtasSpanQuery ignoreQuery,
      Integer maximumIgnoreLength) {
    super(null, null);
    field = query.getField();
    this.query = query;
    if (field != null && ignoreQuery != null) {
      if (ignoreQuery.getField() == null
          || field.equals(ignoreQuery.getField())) {
        this.ignoreQuery = ignoreQuery;
        if (maximumIgnoreLength == null) {
          this.maximumIgnoreLength = 1;
        } else {
          this.maximumIgnoreLength = maximumIgnoreLength;
        }
      } else {
        throw new IllegalArgumentException(
            "ignore must have same field as clauses");
      }
    } else {
      this.ignoreQuery = null;
      this.maximumIgnoreLength = null;
    }
    setRecurrence(minimumRecurrence, maximumRecurrence);
  }

  /**
   * Gets the query.
   *
   * @return the query
   */
  public MtasSpanQuery getQuery() {
    return query;
  }

  /**
   * Gets the ignore query.
   *
   * @return the ignore query
   */
  public MtasSpanQuery getIgnoreQuery() {
    return ignoreQuery;
  }

  /**
   * Gets the maximum ignore length.
   *
   * @return the maximum ignore length
   */
  public Integer getMaximumIgnoreLength() {
    return maximumIgnoreLength;
  }

  /**
   * Gets the minimum recurrence.
   *
   * @return the minimum recurrence
   */
  public int getMinimumRecurrence() {
    return minimumRecurrence;
  }

  /**
   * Gets the maximum recurrence.
   *
   * @return the maximum recurrence
   */
  public int getMaximumRecurrence() {
    return maximumRecurrence;
  }

  /**
   * Sets the recurrence.
   *
   * @param minimumRecurrence the minimum recurrence
   * @param maximumRecurrence the maximum recurrence
   */
  public void setRecurrence(int minimumRecurrence, int maximumRecurrence) {
    if (minimumRecurrence > maximumRecurrence) {
      throw new IllegalArgumentException(
          "minimumRecurrence > maximumRecurrence");
    } else if (minimumRecurrence < 1) {
      throw new IllegalArgumentException("minimumRecurrence < 1 not supported");
    } else if (query == null) {
      throw new IllegalArgumentException("no clause");
    }
    this.minimumRecurrence = minimumRecurrence;
    this.maximumRecurrence = maximumRecurrence;
    // set minimum/maximum
    Integer minimum = null;
    Integer maximum = null;
    if (query.getMinimumWidth() != null) {
      minimum = minimumRecurrence * query.getMinimumWidth();
    }
    if (query.getMaximumWidth() != null) {
      maximum = maximumRecurrence * query.getMaximumWidth();
      if (ignoreQuery != null && maximumIgnoreLength != null) {
        if (ignoreQuery.getMaximumWidth() != null) {
          maximum += (maximumRecurrence - 1) * maximumIgnoreLength
              * ignoreQuery.getMaximumWidth();
        } else {
          maximum = null;
        }
      }
    }
    setWidth(minimum, maximum);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.lucene.search.spans.SpanQuery#getField()
   */
  @Override
  public String getField() {
    return field;
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.apache.lucene.search.Query#rewrite(org.apache.lucene.index.IndexReader)
   */
  @Override
  public MtasSpanQuery rewrite(IndexReader reader) throws IOException {
    MtasSpanQuery newQuery = query.rewrite(reader);
    if (maximumRecurrence == 1) {
      return newQuery;
    } else {
      MtasSpanQuery newIgnoreQuery = (ignoreQuery != null)
          ? ignoreQuery.rewrite(reader) : null;
      if (newQuery instanceof MtasSpanRecurrenceQuery) {
        // for now too difficult, possibly merge later
      }
      if (!newQuery.equals(query)
          || (newIgnoreQuery != null && !newIgnoreQuery.equals(ignoreQuery))) {
        return new MtasSpanRecurrenceQuery(newQuery, minimumRecurrence,
            maximumRecurrence, newIgnoreQuery, maximumIgnoreLength)
                .rewrite(reader);
      } else {
        return super.rewrite(reader);
      }
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.lucene.search.Query#toString(java.lang.String)
   */
  @Override
  public String toString(String field) {
    StringBuilder buffer = new StringBuilder();
    buffer.append(this.getClass().getSimpleName() + "([");
    buffer.append(query.toString(query.getField()));
    buffer.append("," + minimumRecurrence + "," + maximumRecurrence);
    buffer.append(", ");
    buffer.append(ignoreQuery);
    buffer.append("])");
    return buffer.toString();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.lucene.search.Query#equals(java.lang.Object)
   */
  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    final MtasSpanRecurrenceQuery other = (MtasSpanRecurrenceQuery) obj;
    boolean result;
    result = query.equals(other.query);
    result &= minimumRecurrence == other.minimumRecurrence;
    result &= maximumRecurrence == other.maximumRecurrence;
    if (result) {
      boolean subResult;
      subResult = ignoreQuery == null && other.ignoreQuery == null;
      subResult |= ignoreQuery != null && other.ignoreQuery != null
          && ignoreQuery.equals(other.ignoreQuery);
      return subResult;
    } else {
      return false;
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.lucene.search.Query#hashCode()
   */
  @Override
  public int hashCode() {
    int h = this.getClass().getSimpleName().hashCode();
    h = (h * 7) ^ query.hashCode();
    h = (h * 11) ^ minimumRecurrence;
    h = (h * 13) ^ maximumRecurrence;
    return h;
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.apache.lucene.search.spans.SpanQuery#createWeight(org.apache.lucene.
   * search.IndexSearcher, boolean)
   */
  @Override
  public MtasSpanWeight createWeight(IndexSearcher searcher,
      boolean needsScores, float boost) throws IOException {
    SpanWeight subWeight = query.createWeight(searcher, false, boost);
    SpanWeight ignoreWeight = null;
    if (ignoreQuery != null) {
      ignoreWeight = ignoreQuery.createWeight(searcher, false, boost);
    }
    return new SpanRecurrenceWeight(subWeight, ignoreWeight,
        maximumIgnoreLength, searcher,
        needsScores ? getTermContexts(subWeight) : null, boost);
  }

  /*
   * (non-Javadoc)
   * 
   * @see mtas.search.spans.util.MtasSpanQuery#disableTwoPhaseIterator()
   */
  @Override
  public void disableTwoPhaseIterator() {
    super.disableTwoPhaseIterator();
    query.disableTwoPhaseIterator();
    if (ignoreQuery != null) {
      ignoreQuery.disableTwoPhaseIterator();
    }
  }

  /**
   * The Class SpanRecurrenceWeight.
   */
  protected class SpanRecurrenceWeight extends MtasSpanWeight {

    /** The sub weight. */
    final SpanWeight subWeight;

    /** The ignore weight. */
    final SpanWeight ignoreWeight;

    /** The maximum ignore length. */
    final Integer maximumIgnoreLength;

    /**
     * Instantiates a new span recurrence weight.
     *
     * @param subWeight the sub weight
     * @param ignoreWeight the ignore weight
     * @param maximumIgnoreLength the maximum ignore length
     * @param searcher the searcher
     * @param terms the terms
     * @throws IOException Signals that an I/O exception has occurred.
     */
    public SpanRecurrenceWeight(SpanWeight subWeight, SpanWeight ignoreWeight,
        Integer maximumIgnoreLength, IndexSearcher searcher,
        Map<Term, TermContext> terms, float boost) throws IOException {
      super(MtasSpanRecurrenceQuery.this, searcher, terms, boost);
      this.subWeight = subWeight;
      this.ignoreWeight = ignoreWeight;
      this.maximumIgnoreLength = maximumIgnoreLength;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.apache.lucene.search.spans.SpanWeight#extractTermContexts(java.util.
     * Map)
     */
    @Override
    public void extractTermContexts(Map<Term, TermContext> contexts) {
      subWeight.extractTermContexts(contexts);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.apache.lucene.search.spans.SpanWeight#getSpans(org.apache.lucene.
     * index.LeafReaderContext,
     * org.apache.lucene.search.spans.SpanWeight.Postings)
     */
    @Override
    public MtasSpans getSpans(LeafReaderContext context,
        Postings requiredPostings) throws IOException {
      if (field == null) {
        return null;
      } else {
        Terms terms = context.reader().terms(field);
        if (terms == null) {
          return null; // field does not exist
        }
        Spans subSpans = subWeight.getSpans(context, requiredPostings);
        if (subSpans == null) {
          return null;
        } else {
          Spans ignoreSpans = null;
          if (ignoreWeight != null) {
            ignoreSpans = ignoreWeight.getSpans(context, requiredPostings);
          }
          return new MtasSpanRecurrenceSpans(MtasSpanRecurrenceQuery.this,
              subSpans, minimumRecurrence, maximumRecurrence, ignoreSpans,
              maximumIgnoreLength);
        }
      }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.lucene.search.Weight#extractTerms(java.util.Set)
     */
    @Override
    public void extractTerms(Set<Term> terms) {
      subWeight.extractTerms(terms);
    }
    
//    @Override
//    public boolean isCacheable(LeafReaderContext arg0) {
//      return subWeight.isCacheable(arg0) && (ignoreWeight==null || ignoreWeight.isCacheable(arg0));
//    }

  }
  
  @Override
  public boolean isMatchAllPositionsQuery() {
    return false;
  }

}