package mtas.search.spans.util;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;

import org.apache.lucene.codecs.FieldsProducer;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReader;
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.codec.util.CodecInfo;
import mtas.search.spans.MtasSpanMatchNoneSpans;

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

  /** The query. */
  MtasSpanQuery query;

  /** The minimum left. */
  int minimumLeft;

  /** The maximum left. */
  int maximumLeft;

  /** The minimum right. */
  int minimumRight;

  /** The maximum right. */
  int maximumRight;

  /**
   * Instantiates a new mtas expand span query.
   *
   * @param query the query
   * @param minimumLeft the minimum left
   * @param maximumLeft the maximum left
   * @param minimumRight the minimum right
   * @param maximumRight the maximum right
   */
  public MtasExpandSpanQuery(MtasSpanQuery query, int minimumLeft,
      int maximumLeft, int minimumRight, int maximumRight) {
    super(null, null);
    this.query = query;
    if (minimumLeft > maximumLeft || minimumRight > maximumRight
        || minimumLeft < 0 || minimumRight < 0) {
      throw new IllegalArgumentException();
    }
    this.minimumLeft = minimumLeft;
    this.maximumLeft = maximumLeft;
    this.minimumRight = minimumRight;
    this.maximumRight = maximumRight;
    Integer minimum = query.getMinimumWidth();
    Integer maximum = query.getMaximumWidth();
    if (minimum != null) {
      minimum += minimumLeft + minimumRight;
    }
    if (maximum != null) {
      maximum += maximumLeft + maximumRight;
    }
    setWidth(minimum, maximum);
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * mtas.search.spans.util.MtasSpanQuery#createWeight(org.apache.lucene.search.
   * IndexSearcher, boolean)
   */
  @Override
  public SpanWeight createWeight(IndexSearcher searcher, boolean needsScores, float boost)
      throws IOException {
    SpanWeight subWeight = query.createWeight(searcher, needsScores, boost);
    if (maximumLeft == 0 && maximumRight == 0) {
      return subWeight;
    } else {
      return new MtasExpandWeight(subWeight, searcher, needsScores, boost);
    }
  }

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

  /*
   * (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(field) + "][" + minimumLeft + "," + maximumLeft
        + "][" + minimumRight + "," + maximumRight + "])");
    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 MtasExpandSpanQuery that = (MtasExpandSpanQuery) obj;
    boolean isEqual;
    isEqual = query.equals(that.query);
    isEqual &= minimumLeft == that.minimumLeft;
    isEqual &= maximumLeft == that.maximumLeft;
    isEqual &= minimumRight == that.minimumRight;
    isEqual &= maximumRight == that.maximumRight;
    return isEqual;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.lucene.search.Query#hashCode()
   */
  @Override
  public int hashCode() {
    int h = Integer.rotateLeft(classHash(), 1);
    h ^= query.hashCode();
    h = Integer.rotateLeft(h, minimumLeft) + minimumLeft;
    h ^= 2;
    h = Integer.rotateLeft(h, maximumLeft) + maximumLeft;
    h ^= 3;
    h = Integer.rotateLeft(h, minimumRight) + minimumRight;
    h ^= 5;
    h = Integer.rotateLeft(h, maximumRight) + maximumRight;
    return h;
  }

  /*
   * (non-Javadoc)
   * 
   * @see mtas.search.spans.util.MtasSpanQuery#rewrite(org.apache.lucene.index.
   * IndexReader)
   */
  @Override
  public MtasSpanQuery rewrite(IndexReader reader) throws IOException {
    MtasSpanQuery newQuery = query.rewrite(reader);
    if (maximumLeft == 0 && maximumRight == 0) {
      return newQuery;
    } else if (((maximumLeft == 0) || (maximumLeft == minimumLeft))
        && ((maximumRight == 0) || (maximumRight == minimumRight))) {
      MtasSpanQuery maximumExpandedQuery = new MtasMaximumExpandSpanQuery(
          newQuery, minimumLeft, maximumLeft, minimumRight, maximumRight);
      return maximumExpandedQuery.rewrite(reader);
    } else if (!query.equals(newQuery)) {
      return new MtasExpandSpanQuery(newQuery, minimumLeft, maximumLeft,
          minimumRight, maximumRight);
    } else {
      return super.rewrite(reader);
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see mtas.search.spans.util.MtasSpanQuery#disableTwoPhaseIterator()
   */
  @Override
  public void disableTwoPhaseIterator() {
    super.disableTwoPhaseIterator();
    query.disableTwoPhaseIterator();
  }
  
  @Override
  public boolean isMatchAllPositionsQuery() {
    return false;
  }

  /**
   * The Class MtasExpandWeight.
   */
  private class MtasExpandWeight extends MtasSpanWeight {

    /** The Constant METHOD_GET_DELEGATE. */
    private static final String METHOD_GET_DELEGATE = "getDelegate";

    /** The Constant METHOD_GET_POSTINGS_READER. */
    private static final String METHOD_GET_POSTINGS_READER = "getPostingsReader";

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

    /**
     * Instantiates a new mtas expand weight.
     *
     * @param subWeight the sub weight
     * @param searcher the searcher
     * @param needsScores the needs scores
     * @throws IOException Signals that an I/O exception has occurred.
     */
    public MtasExpandWeight(SpanWeight subWeight, IndexSearcher searcher,
        boolean needsScores, float boost) throws IOException {
      super(MtasExpandSpanQuery.this, searcher,
          needsScores ? getTermContexts(subWeight) : null, boost);
      this.subWeight = subWeight;
    }

    /*
     * (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 Spans getSpans(LeafReaderContext ctx, Postings requiredPostings)
        throws IOException {
      Spans spans = subWeight.getSpans(ctx, requiredPostings);
      if ((maximumLeft == 0 && maximumRight == 0) || spans == null) {
        return spans;
      } else {
        try {
          // get leafreader
          LeafReader r = ctx.reader();
          // get delegate
          Boolean hasMethod = true;
          while (hasMethod) {
            hasMethod = false;
            Method[] methods = r.getClass().getMethods();
            for (Method m : methods) {
              if (m.getName().equals(METHOD_GET_DELEGATE)) {
                hasMethod = true;
                r = (LeafReader) m.invoke(r, (Object[]) null);
                break;
              }
            }
          } // get fieldsproducer
          Method fpm = r.getClass().getMethod(METHOD_GET_POSTINGS_READER,
              (Class<?>[]) null);
          FieldsProducer fp = (FieldsProducer) fpm.invoke(r, (Object[]) null);
          // get MtasFieldsProducer using terms
          Terms t = fp.terms(field);
          if (t == null) {
            return new MtasSpanMatchNoneSpans(MtasExpandSpanQuery.this);
          } else {
            CodecInfo mtasCodecInfo = CodecInfo.getCodecInfoFromTerms(t);
            return new MtasExpandSpans(MtasExpandSpanQuery.this, mtasCodecInfo,
                query.getField(), spans);
          }
        } catch (Exception e) {
          throw new IOException("Can't get reader", e);
        }

      }
    }

    /*
     * (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);
//    }

  }

}