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

import static org.apache.lucene.queries.intervals.Intervals.containedBy;
import static org.apache.lucene.queries.intervals.Intervals.containing;
import static org.apache.lucene.queries.intervals.Intervals.fixField;
import static org.apache.lucene.queries.intervals.Intervals.maxgaps;
import static org.apache.lucene.queries.intervals.Intervals.maxwidth;
import static org.apache.lucene.queries.intervals.Intervals.notContainedBy;
import static org.apache.lucene.queries.intervals.Intervals.notContaining;
import static org.apache.lucene.queries.intervals.Intervals.or;
import static org.apache.lucene.queries.intervals.Intervals.ordered;
import static org.apache.lucene.queries.intervals.Intervals.phrase;
import static org.apache.lucene.queries.intervals.Intervals.term;

import org.apache.lucene.tests.util.LuceneTestCase;

public class TestDisjunctionRewrites extends LuceneTestCase {

  public void testDisjunctionSuffix() {

    // BLOCK(a,or(b, BLOCK(b, c))) => or(BLOCK(a, b), BLOCK(a, b, c))
    IntervalsSource actual =
        Intervals.phrase(
            Intervals.term("a"), Intervals.or(Intervals.term("b"), Intervals.phrase("b", "c")));
    IntervalsSource expected =
        Intervals.or(Intervals.phrase("a", "b"), Intervals.phrase("a", "b", "c"));
    assertEquals(expected, actual);
  }

  public void testPhraseDisjunctionWithDifferentLengthClauses() {

    // BLOCK(a, or(b, BLOCK(b, c)), d) => or(BLOCK(a, b, d), BLOCK(a, b, c, d))

    IntervalsSource actual =
        Intervals.phrase(
            Intervals.term("a"),
            Intervals.or(
                Intervals.term("b"), Intervals.phrase(Intervals.term("b"), Intervals.term("c"))),
            Intervals.term("d"));

    IntervalsSource expected =
        Intervals.or(Intervals.phrase("a", "b", "d"), Intervals.phrase("a", "b", "c", "d"));

    assertEquals(expected, actual);
  }

  public void testPhraseDisjunctionWithNestedDifferentLengthClauses() {

    // BLOCK(a, or(ORDERED(or(b, c), d), b, p, q), f, g)
    //  => or(BLOCK(a, or(b, p, q), f, g), BLOCK(a, ORDERED(or(b, c), d), f, g))

    IntervalsSource expected =
        Intervals.or(
            Intervals.phrase(
                Intervals.term("a"),
                Intervals.or(Intervals.term("b"), Intervals.term("p"), Intervals.term("q")),
                Intervals.term("f"),
                Intervals.term("g")),
            Intervals.phrase(
                Intervals.term("a"),
                Intervals.ordered(
                    Intervals.or(Intervals.term("b"), Intervals.term("c")), Intervals.term("d")),
                Intervals.term("f"),
                Intervals.term("g")));

    IntervalsSource actual =
        Intervals.phrase(
            Intervals.term("a"),
            Intervals.or(
                Intervals.ordered(
                    Intervals.or(Intervals.term("b"), Intervals.term("c")), Intervals.term("d")),
                Intervals.term("b"),
                Intervals.term("p"),
                Intervals.term("q")),
            Intervals.term("f"),
            Intervals.term("g"));

    assertEquals(expected, actual);
  }

  public void testDisjunctionRewritePreservesFilters() {

    // BLOCK(a, MAXGAPS/3(OR(BLOCK(a, b), BLOCK(c, d))), c)
    // => or(BLOCK(a, MAXGAPS/3(BLOCK(a, b)), c), BLOCK(a, MAXGAPS/3(BLOCK(c, d)), c))

    IntervalsSource actual =
        Intervals.phrase(
            Intervals.term("a"),
            Intervals.maxgaps(
                3, Intervals.or(Intervals.phrase("a", "b"), Intervals.phrase("c", "d"))),
            Intervals.term("c"));

    IntervalsSource expected =
        Intervals.or(
            Intervals.phrase(
                Intervals.term("a"),
                Intervals.maxgaps(3, Intervals.phrase("a", "b")),
                Intervals.term("c")),
            Intervals.phrase(
                Intervals.term("a"),
                Intervals.maxgaps(3, Intervals.phrase("c", "d")),
                Intervals.term("c")));

    assertEquals(expected, actual);
  }

  public void testNestedMaxGaps() {
    // MAXGAPS/3(ORDERED(MAXGAPS/4(ORDERED(a, or(b, BLOCK(c, d)))), e)
    // => or(MAXGAPS/3(ORDERED(MAXGAPS/4(ORDERED(a, b)), e)), MAXGAPS/3(ORDERED(MAXGAPS/4(ORDERED(a,
    // BLOCK(c, d))), e)))
    IntervalsSource actual =
        maxgaps(
            3, ordered(maxgaps(4, ordered(term("a"), or(term("b"), phrase("c", "d")))), term("e")));
    IntervalsSource expected =
        or(
            maxgaps(3, ordered(maxgaps(4, ordered(term("a"), term("b"))), term("e"))),
            maxgaps(3, ordered(maxgaps(4, ordered(term("a"), phrase("c", "d"))), term("e"))));
    assertEquals(expected, actual);
  }

  public void testNestedMaxWidth() {
    // maxwidth does not automatically pull up disjunctions at construction time, so we need to
    // check
    // that it does the right thing if wrapped by something that does need exact internal accounting
    // PHRASE(a, MAXWIDTH(4, OR(ORDERED(b, c), ORDERED(d, e))), f)
    // => or(PHRASE(a, MAXWIDTH(4, ORDERED(b, c)), f), PHRASE(a, MAXWIDTH(4, ORDERED(d, e)), f))
    IntervalsSource actual =
        phrase(
            term("a"),
            maxwidth(4, or(ordered(term("b"), term("c")), ordered(term("d"), term("e")))),
            term("f"));
    IntervalsSource expected =
        or(
            phrase(term("a"), maxwidth(4, ordered(term("b"), term("c"))), term("f")),
            phrase(term("a"), maxwidth(4, ordered(term("d"), term("e"))), term("f")));
    assertEquals(expected, actual);
  }

  public void testNestedFixField() {
    // PHRASE(a, FIXFIELD(field, or(PHRASE(a, b), b)), c)
    // => or(PHRASE(a, FIXFIELD(PHRASE(a, b)), c), PHRASE(a, FIXFIELD(b), c))
    IntervalsSource actual =
        phrase(term("a"), fixField("field", or(phrase("a", "b"), term("b"))), term("c"));
    IntervalsSource expected =
        or(
            phrase(term("a"), fixField("field", phrase("a", "b")), term("c")),
            phrase(term("a"), fixField("field", term("b")), term("c")));
    assertEquals(expected, actual);
  }

  public void testContainedBy() {
    // the 'big' interval should not be minimized, the 'small' one should be
    // CONTAINED_BY(or("s", BLOCK("s", "t")), MAXGAPS/4(or(ORDERED("a", "b"), ORDERED("c", "d"))))
    // => or(CONTAINED_BY(or("s", BLOCK("s", "t")), MAXGAPS/4(ORDERED("a", "b"))),
    //       CONTAINED_BY(or("s", BLOCK("s", "t")), MAXGAPS/4(ORDERED("c", "d"))))
    IntervalsSource actual =
        containedBy(
            or(term("s"), phrase("s", "t")),
            maxgaps(4, or(ordered(term("a"), term("b")), ordered(term("c"), term("d")))));
    IntervalsSource expected =
        or(
            containedBy(or(term("s"), phrase("s", "t")), maxgaps(4, ordered(term("a"), term("b")))),
            containedBy(
                or(term("s"), phrase("s", "t")), maxgaps(4, ordered(term("c"), term("d")))));
    assertEquals(expected, actual);
  }

  public void testContaining() {
    // the 'big' interval should not be minimized, the 'small' one should be
    // CONTAINING(MAXGAPS/4(or(ORDERED("a", "b"), ORDERED("c", "d"))), or("s", BLOCK("s", "t")))
    // => or(CONTAINING(MAXGAPS/4(ORDERED("a", "b")), or("s", BLOCK("s", "t"))),
    //       CONTAINING(MAXGAPS/4(ORDERED("c", "d")), or("s", BLOCK("s", "t"))))
    IntervalsSource actual =
        containing(
            maxgaps(4, or(ordered(term("a"), term("b")), ordered(term("c"), term("d")))),
            or(term("s"), phrase("s", "t")));
    IntervalsSource expected =
        or(
            containing(maxgaps(4, ordered(term("a"), term("b"))), or(term("s"), phrase("s", "t"))),
            containing(maxgaps(4, ordered(term("c"), term("d"))), or(term("s"), phrase("s", "t"))));
    assertEquals(expected, actual);
  }

  public void testNotContainedBy() {
    // the 'big' interval should not be minimized, the 'small' one should be
    // NOT_CONTAINED_BY(or(BLOCK("a", "b"), "a"), or(BLOCK("c", "d"), "d"))
    // => or(NOT_CONTAINED_BY(or(BLOCK("a", "b"), "a"), BLOCK("c", "d"))),
    // NOT_CONTAINED_BY(or(BLOCK("a", "b"), "a"), "d"))
    IntervalsSource actual =
        notContainedBy(or(phrase("a", "b"), term("a")), or(phrase("c", "d"), term("d")));
    IntervalsSource expected =
        or(
            notContainedBy(or(phrase("a", "b"), term("a")), phrase("c", "d")),
            notContainedBy(or(phrase("a", "b"), term("a")), term("d")));
    assertEquals(expected, actual);
  }

  public void testNotContaining() {
    // the 'big' interval should not be minimized, the 'small' one should be
    // NOT_CONTAINING(or(BLOCK("a", "b"), "a"), or(BLOCK("c", "d"), "d"))
    // => or(NOT_CONTAINING(BLOCK("a", "b"), or(BLOCK("c", "d"), "d")), NOT_CONTAINING("a",
    // or(BLOCK("c", "d"), "d")))
    IntervalsSource actual =
        notContaining(or(phrase("a", "b"), term("a")), or(phrase("c", "d"), term("d")));
    IntervalsSource expected =
        or(
            notContaining(phrase("a", "b"), or(phrase("c", "d"), term("d"))),
            notContaining(term("a"), or(phrase("c", "d"), term("d"))));
    assertEquals(expected, actual);
  }

  public void testBlockedRewrites() {
    IntervalsSource actual = phrase(term("a"), or(false, phrase("b", "c"), term("c")));
    IntervalsSource ifRewritten = or(phrase("a", "b", "c"), phrase("a", "c"));
    assertNotEquals(ifRewritten, actual);
  }
}
