/*
 * 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.queries.spans;

import static org.apache.lucene.queries.spans.SpanTestUtil.spanQuery;

import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.PhraseQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.tests.search.SearchEquivalenceTestBase;

/** Basic equivalence tests for span queries */
public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {

  // TODO: we could go a little crazy for a lot of these,
  // but these are just simple minimal cases in case something
  // goes horribly wrong. Put more intense tests elsewhere.

  /** SpanTermQuery(A) = TermQuery(A) */
  public void testSpanTermVersusTerm() throws Exception {
    Term t1 = randomTerm();
    assertSameScores(new TermQuery(t1), spanQuery(new SpanTermQuery(t1)));
  }

  /** SpanOrQuery(A) = SpanTermQuery(A) */
  public void testSpanOrVersusTerm() throws Exception {
    Term t1 = randomTerm();
    SpanQuery term = spanQuery(new SpanTermQuery(t1));
    assertSameSet(spanQuery(new SpanOrQuery(term)), term);
  }

  /** SpanOrQuery(A, A) = SpanTermQuery(A) */
  public void testSpanOrDoubleVersusTerm() throws Exception {
    Term t1 = randomTerm();
    SpanQuery term = spanQuery(new SpanTermQuery(t1));
    assertSameSet(spanQuery(new SpanOrQuery(term, term)), term);
  }

  /** SpanOrQuery(A, B) = (A B) */
  public void testSpanOrVersusBooleanTerm() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    BooleanQuery.Builder q1 = new BooleanQuery.Builder();
    q1.add(new TermQuery(t1), Occur.SHOULD);
    q1.add(new TermQuery(t2), Occur.SHOULD);
    SpanQuery q2 =
        spanQuery(
            new SpanOrQuery(spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))));
    assertSameSet(q1.build(), q2);
  }

  /**
   * SpanOrQuery(SpanNearQuery[A B], SpanNearQuery[C D]) = (SpanNearQuery[A B], SpanNearQuery[C D])
   */
  public void testSpanOrVersusBooleanNear() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    Term t3 = randomTerm();
    Term t4 = randomTerm();
    SpanQuery near1 =
        spanQuery(
            new SpanNearQuery(
                new SpanQuery[] {
                  spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))
                },
                10,
                random().nextBoolean()));
    SpanQuery near2 =
        spanQuery(
            new SpanNearQuery(
                new SpanQuery[] {
                  spanQuery(new SpanTermQuery(t3)), spanQuery(new SpanTermQuery(t4))
                },
                10,
                random().nextBoolean()));
    BooleanQuery.Builder q1 = new BooleanQuery.Builder();
    q1.add(near1, Occur.SHOULD);
    q1.add(near2, Occur.SHOULD);
    SpanQuery q2 = spanQuery(new SpanOrQuery(near1, near2));
    assertSameSet(q1.build(), q2);
  }

  /** SpanNotQuery(A, B) ⊆ SpanTermQuery(A) */
  public void testSpanNotVersusSpanTerm() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    assertSubsetOf(
        spanQuery(
            new SpanNotQuery(spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2)))),
        spanQuery(new SpanTermQuery(t1)));
  }

  /** SpanNotQuery(A, [B C]) ⊆ SpanTermQuery(A) */
  public void testSpanNotNearVersusSpanTerm() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    Term t3 = randomTerm();
    SpanQuery near =
        spanQuery(
            new SpanNearQuery(
                new SpanQuery[] {
                  spanQuery(new SpanTermQuery(t2)), spanQuery(new SpanTermQuery(t3))
                },
                10,
                random().nextBoolean()));
    assertSubsetOf(
        spanQuery(new SpanNotQuery(spanQuery(new SpanTermQuery(t1)), near)),
        spanQuery(new SpanTermQuery(t1)));
  }

  /** SpanNotQuery([A B], C) ⊆ SpanNearQuery([A B]) */
  public void testSpanNotVersusSpanNear() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    Term t3 = randomTerm();
    SpanQuery near =
        spanQuery(
            new SpanNearQuery(
                new SpanQuery[] {
                  spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))
                },
                10,
                random().nextBoolean()));
    assertSubsetOf(spanQuery(new SpanNotQuery(near, spanQuery(new SpanTermQuery(t3)))), near);
  }

  /** SpanNotQuery([A B], [C D]) ⊆ SpanNearQuery([A B]) */
  public void testSpanNotNearVersusSpanNear() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    Term t3 = randomTerm();
    Term t4 = randomTerm();
    SpanQuery near1 =
        spanQuery(
            new SpanNearQuery(
                new SpanQuery[] {
                  spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))
                },
                10,
                random().nextBoolean()));
    SpanQuery near2 =
        spanQuery(
            new SpanNearQuery(
                new SpanQuery[] {
                  spanQuery(new SpanTermQuery(t3)), spanQuery(new SpanTermQuery(t4))
                },
                10,
                random().nextBoolean()));
    assertSubsetOf(spanQuery(new SpanNotQuery(near1, near2)), near1);
  }

  /** SpanFirstQuery(A, 10) ⊆ SpanTermQuery(A) */
  public void testSpanFirstVersusSpanTerm() throws Exception {
    Term t1 = randomTerm();
    assertSubsetOf(
        spanQuery(new SpanFirstQuery(spanQuery(new SpanTermQuery(t1)), 10)),
        spanQuery(new SpanTermQuery(t1)));
  }

  /** SpanNearQuery([A, B], 0, true) = "A B" */
  public void testSpanNearVersusPhrase() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    SpanQuery[] subquery =
        new SpanQuery[] {spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))};
    SpanQuery q1 = spanQuery(new SpanNearQuery(subquery, 0, true));
    PhraseQuery q2 = new PhraseQuery(t1.field(), t1.bytes(), t2.bytes());
    if (t1.equals(t2)) {
      assertSameSet(q1, q2);
    } else {
      assertSameScores(q1, q2);
    }
  }

  /** SpanNearQuery([A, B], ∞, false) = +A +B */
  public void testSpanNearVersusBooleanAnd() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    SpanQuery[] subquery =
        new SpanQuery[] {spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))};
    SpanQuery q1 = spanQuery(new SpanNearQuery(subquery, Integer.MAX_VALUE, false));
    BooleanQuery.Builder q2 = new BooleanQuery.Builder();
    q2.add(new TermQuery(t1), Occur.MUST);
    q2.add(new TermQuery(t2), Occur.MUST);
    assertSameSet(q1, q2.build());
  }

  /** SpanNearQuery([A B], 0, false) ⊆ SpanNearQuery([A B], 1, false) */
  public void testSpanNearVersusSloppySpanNear() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    SpanQuery[] subquery =
        new SpanQuery[] {spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))};
    SpanQuery q1 = spanQuery(new SpanNearQuery(subquery, 0, false));
    SpanQuery q2 = spanQuery(new SpanNearQuery(subquery, 1, false));
    assertSubsetOf(q1, q2);
  }

  /** SpanNearQuery([A B], 3, true) ⊆ SpanNearQuery([A B], 3, false) */
  public void testSpanNearInOrderVersusOutOfOrder() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    SpanQuery[] subquery =
        new SpanQuery[] {spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))};
    SpanQuery q1 = spanQuery(new SpanNearQuery(subquery, 3, true));
    SpanQuery q2 = spanQuery(new SpanNearQuery(subquery, 3, false));
    assertSubsetOf(q1, q2);
  }

  /** SpanNearQuery([A B], N, false) ⊆ SpanNearQuery([A B], N+1, false) */
  public void testSpanNearIncreasingSloppiness() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    SpanQuery[] subquery =
        new SpanQuery[] {spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))};
    for (int i = 0; i < 10; i++) {
      SpanQuery q1 = spanQuery(new SpanNearQuery(subquery, i, false));
      SpanQuery q2 = spanQuery(new SpanNearQuery(subquery, i + 1, false));
      assertSubsetOf(q1, q2);
    }
  }

  /** SpanNearQuery([A B C], N, false) ⊆ SpanNearQuery([A B C], N+1, false) */
  public void testSpanNearIncreasingSloppiness3() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    Term t3 = randomTerm();
    SpanQuery[] subquery =
        new SpanQuery[] {
          spanQuery(new SpanTermQuery(t1)),
          spanQuery(new SpanTermQuery(t2)),
          spanQuery(new SpanTermQuery(t3))
        };
    for (int i = 0; i < 10; i++) {
      SpanQuery q1 = spanQuery(new SpanNearQuery(subquery, i, false));
      SpanQuery q2 = spanQuery(new SpanNearQuery(subquery, i + 1, false));
      assertSubsetOf(q1, q2);
    }
  }

  /** SpanNearQuery([A B], N, true) ⊆ SpanNearQuery([A B], N+1, true) */
  public void testSpanNearIncreasingOrderedSloppiness() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    SpanQuery[] subquery =
        new SpanQuery[] {spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))};
    for (int i = 0; i < 10; i++) {
      SpanQuery q1 = spanQuery(new SpanNearQuery(subquery, i, false));
      SpanQuery q2 = spanQuery(new SpanNearQuery(subquery, i + 1, false));
      assertSubsetOf(q1, q2);
    }
  }

  /** SpanNearQuery([A B C], N, true) ⊆ SpanNearQuery([A B C], N+1, true) */
  public void testSpanNearIncreasingOrderedSloppiness3() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    Term t3 = randomTerm();
    SpanQuery[] subquery =
        new SpanQuery[] {
          spanQuery(new SpanTermQuery(t1)),
          spanQuery(new SpanTermQuery(t2)),
          spanQuery(new SpanTermQuery(t3))
        };
    for (int i = 0; i < 10; i++) {
      SpanQuery q1 = spanQuery(new SpanNearQuery(subquery, i, true));
      SpanQuery q2 = spanQuery(new SpanNearQuery(subquery, i + 1, true));
      assertSubsetOf(q1, q2);
    }
  }

  /** SpanPositionRangeQuery(A, M, N) ⊆ TermQuery(A) */
  public void testSpanRangeTerm() throws Exception {
    Term t1 = randomTerm();
    for (int i = 0; i < 5; i++) {
      for (int j = 0; j < 5; j++) {
        Query q1 =
            spanQuery(new SpanPositionRangeQuery(spanQuery(new SpanTermQuery(t1)), i, i + j));
        Query q2 = new TermQuery(t1);
        assertSubsetOf(q1, q2);
      }
    }
  }

  /** SpanPositionRangeQuery(A, M, N) ⊆ SpanFirstQuery(A, M, N+1) */
  public void testSpanRangeTermIncreasingEnd() throws Exception {
    Term t1 = randomTerm();
    for (int i = 0; i < 5; i++) {
      for (int j = 0; j < 5; j++) {
        Query q1 =
            spanQuery(new SpanPositionRangeQuery(spanQuery(new SpanTermQuery(t1)), i, i + j));
        Query q2 =
            spanQuery(new SpanPositionRangeQuery(spanQuery(new SpanTermQuery(t1)), i, i + j + 1));
        assertSubsetOf(q1, q2);
      }
    }
  }

  /** SpanPositionRangeQuery(A, 0, ∞) = TermQuery(A) */
  public void testSpanRangeTermEverything() throws Exception {
    Term t1 = randomTerm();
    Query q1 =
        spanQuery(
            new SpanPositionRangeQuery(spanQuery(new SpanTermQuery(t1)), 0, Integer.MAX_VALUE));
    Query q2 = new TermQuery(t1);
    assertSameSet(q1, q2);
  }

  /** SpanPositionRangeQuery([A B], M, N) ⊆ SpanNearQuery([A B]) */
  public void testSpanRangeNear() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    SpanQuery[] subquery =
        new SpanQuery[] {spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))};
    SpanQuery nearQuery = spanQuery(new SpanNearQuery(subquery, 10, true));
    for (int i = 0; i < 5; i++) {
      for (int j = 0; j < 5; j++) {
        Query q1 = spanQuery(new SpanPositionRangeQuery(nearQuery, i, i + j));
        Query q2 = nearQuery;
        assertSubsetOf(q1, q2);
      }
    }
  }

  /** SpanPositionRangeQuery([A B], M, N) ⊆ SpanFirstQuery([A B], M, N+1) */
  public void testSpanRangeNearIncreasingEnd() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    SpanQuery[] subquery =
        new SpanQuery[] {spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))};
    SpanQuery nearQuery = spanQuery(new SpanNearQuery(subquery, 10, true));
    for (int i = 0; i < 5; i++) {
      for (int j = 0; j < 5; j++) {
        Query q1 = spanQuery(new SpanPositionRangeQuery(nearQuery, i, i + j));
        Query q2 = spanQuery(new SpanPositionRangeQuery(nearQuery, i, i + j + 1));
        assertSubsetOf(q1, q2);
      }
    }
  }

  /** SpanPositionRangeQuery([A B], ∞) = SpanNearQuery([A B]) */
  public void testSpanRangeNearEverything() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    SpanQuery[] subquery =
        new SpanQuery[] {spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))};
    SpanQuery nearQuery = spanQuery(new SpanNearQuery(subquery, 10, true));
    Query q1 = spanQuery(new SpanPositionRangeQuery(nearQuery, 0, Integer.MAX_VALUE));
    Query q2 = nearQuery;
    assertSameSet(q1, q2);
  }

  /** SpanFirstQuery(A, N) ⊆ TermQuery(A) */
  public void testSpanFirstTerm() throws Exception {
    Term t1 = randomTerm();
    for (int i = 0; i < 10; i++) {
      Query q1 = spanQuery(new SpanFirstQuery(spanQuery(new SpanTermQuery(t1)), i));
      Query q2 = new TermQuery(t1);
      assertSubsetOf(q1, q2);
    }
  }

  /** SpanFirstQuery(A, N) ⊆ SpanFirstQuery(A, N+1) */
  public void testSpanFirstTermIncreasing() throws Exception {
    Term t1 = randomTerm();
    for (int i = 0; i < 10; i++) {
      Query q1 = spanQuery(new SpanFirstQuery(spanQuery(new SpanTermQuery(t1)), i));
      Query q2 = spanQuery(new SpanFirstQuery(spanQuery(new SpanTermQuery(t1)), i + 1));
      assertSubsetOf(q1, q2);
    }
  }

  /** SpanFirstQuery(A, ∞) = TermQuery(A) */
  public void testSpanFirstTermEverything() throws Exception {
    Term t1 = randomTerm();
    Query q1 = spanQuery(new SpanFirstQuery(spanQuery(new SpanTermQuery(t1)), Integer.MAX_VALUE));
    Query q2 = new TermQuery(t1);
    assertSameSet(q1, q2);
  }

  /** SpanFirstQuery([A B], N) ⊆ SpanNearQuery([A B]) */
  public void testSpanFirstNear() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    SpanQuery[] subquery =
        new SpanQuery[] {spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))};
    SpanQuery nearQuery = spanQuery(new SpanNearQuery(subquery, 10, true));
    for (int i = 0; i < 10; i++) {
      Query q1 = spanQuery(new SpanFirstQuery(nearQuery, i));
      Query q2 = nearQuery;
      assertSubsetOf(q1, q2);
    }
  }

  /** SpanFirstQuery([A B], N) ⊆ SpanFirstQuery([A B], N+1) */
  public void testSpanFirstNearIncreasing() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    SpanQuery[] subquery =
        new SpanQuery[] {spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))};
    SpanQuery nearQuery = spanQuery(new SpanNearQuery(subquery, 10, true));
    for (int i = 0; i < 10; i++) {
      Query q1 = spanQuery(new SpanFirstQuery(nearQuery, i));
      Query q2 = spanQuery(new SpanFirstQuery(nearQuery, i + 1));
      assertSubsetOf(q1, q2);
    }
  }

  /** SpanFirstQuery([A B], ∞) = SpanNearQuery([A B]) */
  public void testSpanFirstNearEverything() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    SpanQuery[] subquery =
        new SpanQuery[] {spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))};
    SpanQuery nearQuery = spanQuery(new SpanNearQuery(subquery, 10, true));
    Query q1 = spanQuery(new SpanFirstQuery(nearQuery, Integer.MAX_VALUE));
    Query q2 = nearQuery;
    assertSameSet(q1, q2);
  }

  /** SpanWithinQuery(A, B) ⊆ SpanNearQuery(A) */
  public void testSpanWithinVsNear() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    SpanQuery[] subquery =
        new SpanQuery[] {spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))};
    SpanQuery nearQuery = spanQuery(new SpanNearQuery(subquery, 10, true));

    Term t3 = randomTerm();
    SpanQuery termQuery = spanQuery(new SpanTermQuery(t3));
    Query q1 = spanQuery(new SpanWithinQuery(nearQuery, termQuery));
    assertSubsetOf(q1, termQuery);
  }

  /** SpanWithinQuery(A, B) = SpanContainingQuery(A, B) */
  public void testSpanWithinVsContaining() throws Exception {
    Term t1 = randomTerm();
    Term t2 = randomTerm();
    SpanQuery[] subquery =
        new SpanQuery[] {spanQuery(new SpanTermQuery(t1)), spanQuery(new SpanTermQuery(t2))};
    SpanQuery nearQuery = spanQuery(new SpanNearQuery(subquery, 10, true));

    Term t3 = randomTerm();
    SpanQuery termQuery = spanQuery(new SpanTermQuery(t3));
    Query q1 = spanQuery(new SpanWithinQuery(nearQuery, termQuery));
    Query q2 = spanQuery(new SpanContainingQuery(nearQuery, termQuery));
    assertSameSet(q1, q2);
  }
}
