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

import org.apache.lucene.index.Term;
import org.apache.solr.legacy.LegacyNumericRangeQuery;
import org.apache.lucene.search.*;
import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.SolrTestCaseJ4;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Collections;

public class TestMaxScoreQueryParser extends SolrTestCaseJ4 {
  Query q;
  BooleanClause[] clauses;

  @BeforeClass
  public static void beforeClass() throws Exception {
    initCore("solrconfig.xml", "schema.xml");
  }

  @Test
  public void testFallbackToLucene() {
    q = parse("foo");
    assertEquals(new TermQuery(new Term("text", "foo")), q);

    q = parse("foo^3.0");
    assertEquals(new BoostQuery(new TermQuery(new Term("text", "foo")), 3f), q);

    q = parse("price:[0 TO 10]");
    Class expected = LegacyNumericRangeQuery.class;
    if (Boolean.getBoolean(NUMERIC_POINTS_SYSPROP)) {
      expected = PointRangeQuery.class;
      if (Boolean.getBoolean(NUMERIC_DOCVALUES_SYSPROP)) {
        expected = IndexOrDocValuesQuery.class;
      }
    }
    assertTrue(expected + " vs actual: " + q.getClass(), expected.isInstance(q));
  }

  @Test
  public void testNoShouldClauses() {
    q = parse("+foo +bar");
    clauses = clauses(q);
    assertEquals(2, clauses.length);
    assertTrue(clauses[0].isRequired());
    assertTrue(clauses[1].isRequired());

    q = parse("+foo -bar");
    clauses = clauses(q);
    assertEquals(2, clauses.length);
    assertTrue(clauses[0].isRequired());
    assertTrue(clauses[1].isProhibited());
  }

  @Test
  public void testPureMax() {
    q = parse("foo bar");
    clauses = clauses(q);
    assertEquals(1, clauses.length);
    assertTrue(clauses[0].getQuery() instanceof DisjunctionMaxQuery);
    assertEquals(0.0, ((DisjunctionMaxQuery) clauses[0].getQuery()).getTieBreakerMultiplier(), 1e-15);
    List<Query> qa = ((DisjunctionMaxQuery) clauses[0].getQuery()).getDisjuncts();
    assertEquals(2, qa.size());
    assertEquals("text:foo", qa.get(0).toString());
  }

  @Test
  public void testMaxAndProhibited() {
    q = parse("foo bar -baz");
    clauses = clauses(q);
    assertEquals(2, clauses.length);
    assertTrue(clauses[0].getQuery() instanceof DisjunctionMaxQuery);
    assertTrue(clauses[1].getQuery() instanceof TermQuery);
    assertEquals("text:baz", clauses[1].getQuery().toString());
    assertTrue(clauses[1].isProhibited());
  }

  @Test
  public void testTie() {
    q = parse("foo bar", "tie", "0.5");
    clauses = clauses(q);
    assertEquals(1, clauses.length);
    assertTrue(clauses[0].getQuery() instanceof DisjunctionMaxQuery);
    assertEquals(0.5, ((DisjunctionMaxQuery) clauses[0].getQuery()).getTieBreakerMultiplier(), 1e-15);
  }

  @Test
  public void testBoost() {
    // Simple term query
    q = parse("foo^3.0");
    assertTrue(q instanceof BoostQuery);
    assertEquals(3.0, ((BoostQuery) q).getBoost(), 1e-15);

    // Some DMQ and one plain required
    q = parse("foo^5.0 bar^6.0 +baz^7");
    clauses = clauses(q);
    assertEquals(2, clauses.length);
    assertTrue(clauses[0].getQuery() instanceof DisjunctionMaxQuery);
    DisjunctionMaxQuery dmq = ((DisjunctionMaxQuery) clauses[0].getQuery());
    Query fooClause = ((BooleanQuery)dmq.getDisjuncts().get(0)).clauses().iterator().next().getQuery();
    assertEquals(5.0, ((BoostQuery) fooClause).getBoost(), 1e-15);
    Query barClause = ((BooleanQuery)dmq.getDisjuncts().get(1)).clauses().iterator().next().getQuery();
    assertEquals(6.0, ((BoostQuery) barClause).getBoost(), 1e-15);
    assertEquals(7.0, ((BoostQuery) clauses[1].getQuery()).getBoost(), 1e-15);
    assertFalse(q instanceof BoostQuery);

    // Grouped with parens on top level
    q = parse("(foo^2.0 bar)^3.0");
    clauses = clauses(q);
    assertEquals(1, clauses.length);
    assertTrue(clauses[0].getQuery() instanceof DisjunctionMaxQuery);
    dmq = ((DisjunctionMaxQuery) clauses[0].getQuery());
    fooClause = ((BooleanQuery)dmq.getDisjuncts().get(0)).clauses().iterator().next().getQuery();
    assertEquals(2.0, ((BoostQuery) fooClause).getBoost(), 1e-15);
    barClause = ((BooleanQuery)dmq.getDisjuncts().get(1)).clauses().iterator().next().getQuery();
    assertFalse(barClause instanceof BoostQuery);
    assertEquals(3.0, ((BoostQuery) q).getBoost(), 1e-15);
  }

  //
  // Helper methods
  //

  private Query parse(String q, String... params) {
    try {
      ModifiableSolrParams p = new ModifiableSolrParams();
      ArrayList<String> al = new ArrayList<>(Arrays.asList(params));
      while(al.size() >= 2) {
        p.add(al.remove(0), al.remove(0));
      }
      return new MaxScoreQParser(q, p, new MapSolrParams(Collections.singletonMap("df", "text")), req(q)).parse();
    } catch (SyntaxError syntaxError) {
      fail("Failed with exception "+syntaxError.getMessage());
    }
    fail("Parse failed");
    return null;
  }

  private BooleanClause[] clauses(Query q) {
    while (q instanceof BoostQuery) {
      q = ((BoostQuery) q).getQuery();
    }
    return ((BooleanQuery) q).clauses().toArray(new BooleanClause[0]);
  }
}
