/**
   * Advance the internal document iterator to the specified document, or beyond if it doesn't.
   *
   * @param docid An internal document id.
   */
  public void docIteratorAdvanceTo(int docid) {

    for (Qry q_i : this.args) {
      q_i.docIteratorAdvanceTo(docid);
    }

    this.docIteratorClearMatchCache();
  }
  /**
   * An instantiation of docIteratorHasMatch that is true if the query has a document that matches
   * the first query argument; some subclasses may choose to use this implementation.
   *
   * @param r The retrieval model that determines what is a match
   * @return True if the query matches, otherwise false.
   */
  protected boolean docIteratorHasMatchFirst(RetrievalModel r) {

    Qry q_0 = this.args.get(0);

    if (q_0.docIteratorHasMatch(r)) {
      int docid = q_0.docIteratorGetMatch();
      this.docIteratorSetMatchCache(docid);
      return true;
    } else {
      return false;
    }
  }
  /**
   * An instantiation of docIteratorHasMatch that is true if the query has a document that matches
   * all query arguments; some subclasses may choose to use this implementation.
   *
   * @param r The retrieval model that determines what is a match
   * @return True if the query matches, otherwise false.
   */
  protected boolean docIteratorHasMatchAll(RetrievalModel r) {

    boolean matchFound = false;

    // Keep trying until a match is found or no match is possible.

    while (!matchFound) {

      // Get the docid of the first query argument.

      Qry q_0 = this.args.get(0);

      if (!q_0.docIteratorHasMatch(r)) {
        return false;
      }

      int docid_0 = q_0.docIteratorGetMatch();

      // Other query arguments must match the docid of the first query
      // argument.

      matchFound = true;

      for (int i = 1; i < this.args.size(); i++) {
        Qry q_i = this.args.get(i);

        q_i.docIteratorAdvanceTo(docid_0);

        if (!q_i.docIteratorHasMatch(r)) { // If any argument is exhausted
          return false; // there are no more matches.
        }

        int docid_i = q_i.docIteratorGetMatch();

        if (docid_0 != docid_i) { // docid_0 can't match.  Try again.
          q_0.docIteratorAdvanceTo(docid_i);
          matchFound = false;
          break;
        }
      }

      if (matchFound) {
        docIteratorSetMatchCache(docid_0);
      }
    }

    return true;
  }
  /**
   * An instantiation of docIteratorHasMatch that is true if the query has a document that matches
   * at least one query argument; the match is the smallest docid to match; some subclasses may
   * choose to use this implementation.
   *
   * @param r The retrieval model that determines what is a match
   * @return True if the query matches, otherwise false.
   */
  protected boolean docIteratorHasMatchMin(RetrievalModel r) {

    int minDocid = Qry.INVALID_DOCID;

    for (int i = 0; i < this.args.size(); i++) {
      Qry q_i = this.args.get(i);

      if (q_i.docIteratorHasMatch(r)) {
        int q_iDocid = q_i.docIteratorGetMatch();

        if ((minDocid > q_iDocid) || (minDocid == Qry.INVALID_DOCID)) {
          minDocid = q_iDocid;
        }
      }
    }

    if (minDocid != Qry.INVALID_DOCID) {
      docIteratorSetMatchCache(minDocid);
      return true;
    } else {
      return false;
    }
  }
  /**
   * Append an argument to the list of query operator arguments.
   *
   * @param q The query argument (query operator) to append.
   * @throws IllegalArgumentException q is an invalid argument
   */
  public void appendArg(Qry q) throws IllegalArgumentException {

    //  The query parser and query operator type system are too simple
    //  to detect some kinds of query syntax errors.  appendArg does
    //  additional syntax checking while creating the query tree.  It
    //  also inserts SCORE operators between QrySop operators and QryIop
    //  arguments, and propagates field information from QryIop
    //  children to parents.  Basically, it creates a well-formed
    //  query tree.

    if (this instanceof QryIopTerm) {
      throw new IllegalArgumentException("The TERM operator has no arguments.");
    }

    //  SCORE operators can have only a single argument of type QryIop.

    if (this instanceof QrySopScore) {
      if (this.args.size() > 0) {
        throw new IllegalArgumentException("Score operators can have only one argument");
      } else if (!(q instanceof QryIop)) {
        throw new IllegalArgumentException(
            "The argument to a SCORE operator must be of type QryIop.");
      } else {
        this.args.add(q);
        return;
      }
    }

    //  Check whether it is necessary to insert an implied SCORE
    //  operator between a QrySop operator and a QryIop argument.

    if ((this instanceof QrySop) && (q instanceof QryIop)) {
      Qry impliedOp = new QrySopScore();
      impliedOp.setDisplayName("#SCORE");
      impliedOp.appendArg(q);
      this.args.add(impliedOp);
      return;
    }

    //  QryIop operators must have QryIop arguments in the same field.

    if ((this instanceof QryIop) && (q instanceof QryIop)) {

      if (this.args.size() == 0) {
        ((QryIop) this).field = new String(((QryIop) q).getField());
      } else {
        if (!((QryIop) this).field.equals(((QryIop) q).getField())) {
          throw new IllegalArgumentException(
              "Arguments to QryIop operators must be in the same field.");
        }
      }

      this.args.add(q);
      return;
    }

    //  QrySop operators and their arguments must be of the same type.

    if ((this instanceof QrySop) && (q instanceof QrySop)) {
      this.args.add(q);
      return;
    }

    throw new IllegalArgumentException(
        "Objects of type "
            + q.getClass().getName()
            + " cannot be an argument to a query operator of type "
            + this.getClass().getName());
  }
  @Override
  protected void evaluate() throws IOException {

    this.invertedList = new InvList(this.getField());
    // no argument
    if (args.size() == 0) return;
    if (args.size() == 1) {
      this.invertedList = ((QryIop) this.args.get(0)).invertedList;
      return;
    }

    // iterate each doc, greedy algorithms
    while (true) {
      //  init doc_id
      int doc_id = Qry.INVALID_DOCID;

      if (this.docIteratorHasMatchAll(null)) doc_id = this.args.get(0).docIteratorGetMatch();
      // fail to find a match all
      if (doc_id == Qry.INVALID_DOCID) break;
      // the last argument's position in doc
      List<Integer> positions = new ArrayList<Integer>();

      // iterate each location
      while (true) {
        boolean success = false;
        int position = 0;

        // index from 1 to n-1
        for (int i = 1; i < this.args.size(); i++) {
          // get previous pos
          if (!((QryIop) this.args.get(i - 1)).locIteratorHasMatch()) break;
          int position1 = ((QryIop) this.args.get(i - 1)).locIteratorGetMatch();

          // go beyond pre pos
          ((QryIop) this.args.get(i)).locIteratorAdvancePast(position1);
          if (!((QryIop) this.args.get(i)).locIteratorHasMatch()) break;
          int position2 = ((QryIop) this.args.get(i)).locIteratorGetMatch();

          // near condition satisfied?
          if (position2 > position1 && position2 - position1 <= this.dist) {
            position = position2;
            if (i == this.args.size() - 1) success = true;
          } else {
            success = false;
            break;
          }
        }

        // find a legal position
        if (success) {
          positions.add(position);
          // move each loc pointer
          for (Qry q_i : this.args) {
            ((QryIop) q_i).locIteratorAdvance();
          }
        } else {
          // move first arg
          ((QryIop) this.args.get(0)).locIteratorAdvance();
          // if no more match position for the first arg
          if (!((QryIop) this.args.get(0)).locIteratorHasMatch()) break;
          // reset other pointers to beginning
          for (int i = 1; i < this.args.size(); i++) {
            ((QryIop) this.args.get(i)).locIteratorReset();
            int preposition = ((QryIop) this.args.get(0)).locIteratorGetMatch();
            ((QryIop) this.args.get(i)).locIteratorAdvancePast(preposition);
          }
        }
      }

      // move doc pointer
      for (Qry q_i : this.args) q_i.docIteratorAdvancePast(doc_id);
      // success
      if (positions.size() > 0) this.invertedList.appendPosting(doc_id, positions);
    }
  }