/*
 * 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.queryparser.flexible.standard;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.analysis.MockTokenizer;
import org.apache.lucene.document.DateTools.Resolution;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.flexible.core.QueryNodeException;
import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfigHandler;
import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfigHandler.Operator;
import org.apache.lucene.queryparser.util.QueryParserTestBase;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.WildcardQuery;

/**
 * Tests QueryParser.
 */
public class TestStandardQP extends QueryParserTestBase {
  
  public StandardQueryParser getParser(Analyzer a) throws Exception {
    if (a == null) a = new MockAnalyzer(random(), MockTokenizer.SIMPLE, true);
    StandardQueryParser qp = new StandardQueryParser(a);
    qp.setDefaultOperator(Operator.OR);

    return qp;
  }
  
  public Query parse(String query, StandardQueryParser qp) throws Exception {
    return qp.parse(query, getDefaultField());
  }
  
  @Override
  public CommonQueryParserConfiguration getParserConfig(Analyzer a)
      throws Exception {
    return getParser(a);
  }
  
  @Override
  public Query getQuery(String query, CommonQueryParserConfiguration cqpC)
      throws Exception {
    assert cqpC != null : "Parameter must not be null";
    assert (cqpC instanceof StandardQueryParser) : "Parameter must be instance of StandardQueryParser";
    StandardQueryParser qp = (StandardQueryParser) cqpC;
    return parse(query, qp);
  }
  
  @Override
  public Query getQuery(String query, Analyzer a) throws Exception {
    return parse(query, getParser(a));
  }
  
  @Override
  public boolean isQueryParserException(Exception exception) {
    return exception instanceof QueryNodeException;
  }

  @Override
  public void setDefaultOperatorOR(CommonQueryParserConfiguration cqpC) {
    assert (cqpC instanceof StandardQueryParser);
    StandardQueryParser qp = (StandardQueryParser) cqpC;
    qp.setDefaultOperator(Operator.OR);
  }
  
  @Override
  public void setDefaultOperatorAND(CommonQueryParserConfiguration cqpC) {
    assert (cqpC instanceof StandardQueryParser);
    StandardQueryParser qp = (StandardQueryParser) cqpC;
    qp.setDefaultOperator(Operator.AND);
  }
  
  @Override
  public void setAutoGeneratePhraseQueries(CommonQueryParserConfiguration cqpC,
      boolean value) {
    throw new UnsupportedOperationException();
  }
  
  @Override
  public void setDateResolution(CommonQueryParserConfiguration cqpC,
      CharSequence field, Resolution value) {
    assert (cqpC instanceof StandardQueryParser);
    StandardQueryParser qp = (StandardQueryParser) cqpC;
    qp.getDateResolutionMap().put(field, value);
  }
  

  
  @Override
  public void testOperatorVsWhitespace() throws Exception {
    // LUCENE-2566 is not implemented for StandardQueryParser
    // TODO implement LUCENE-2566 and remove this (override)method
    Analyzer a = new Analyzer() {
      @Override
      public TokenStreamComponents createComponents(String fieldName) {
        return new TokenStreamComponents(new MockTokenizer(MockTokenizer.WHITESPACE, false));
      }
    };
    assertQueryEquals("a - b", a, "a -b");
    assertQueryEquals("a + b", a, "a +b");
    assertQueryEquals("a ! b", a, "a -b");
  }
  
  @Override
  public void testRangeWithPhrase() throws Exception {
    // StandardSyntaxParser does not differentiate between a term and a
    // one-term-phrase in a range query.
    // Is this an issue? Should StandardSyntaxParser mark the text as
    // wasEscaped=true ?
    assertQueryEquals("[\\* TO \"*\"]", null, "[\\* TO *]");
  }
  
  @Override
  public void testEscapedVsQuestionMarkAsWildcard() throws Exception {
    Analyzer a = new MockAnalyzer(random(), MockTokenizer.WHITESPACE, false);
    assertQueryEquals("a:b\\-?c", a, "a:b-?c");
    assertQueryEquals("a:b\\+?c", a, "a:b+?c");
    assertQueryEquals("a:b\\:?c", a, "a:b:?c");
    
    assertQueryEquals("a:b\\\\?c", a, "a:b\\?c");
  }
  
  @Override
  public void testEscapedWildcard() throws Exception {
    CommonQueryParserConfiguration qp = getParserConfig( new MockAnalyzer(random(), MockTokenizer.WHITESPACE, false));
    WildcardQuery q = new WildcardQuery(new Term("field", "foo?ba?r"));//TODO not correct!!
    assertEquals(q, getQuery("foo\\?ba?r", qp));
  }
  
  @Override
  public void testAutoGeneratePhraseQueriesOn() throws Exception {
    expectThrows(UnsupportedOperationException.class, () -> {
      setAutoGeneratePhraseQueries(getParser(null), true);
      super.testAutoGeneratePhraseQueriesOn();
    });
  }
  
  @Override
  public void testStarParsing() throws Exception {
  }
    
  @Override
  public void testDefaultOperator() throws Exception {
    StandardQueryParser qp = getParser(new MockAnalyzer(random()));
    // make sure OR is the default:
    assertEquals(StandardQueryConfigHandler.Operator.OR, qp.getDefaultOperator());
    setDefaultOperatorAND(qp);
    assertEquals(StandardQueryConfigHandler.Operator.AND, qp.getDefaultOperator());
    setDefaultOperatorOR(qp);
    assertEquals(StandardQueryConfigHandler.Operator.OR, qp.getDefaultOperator());
  }
  
 
  @Override
  public void testNewFieldQuery() throws Exception {
    /** ordinary behavior, synonyms form uncoordinated boolean query */
    StandardQueryParser dumb = getParser(new Analyzer1());
    BooleanQuery.Builder expanded = new BooleanQuery.Builder();
    expanded.add(new TermQuery(new Term("field", "dogs")),
        BooleanClause.Occur.SHOULD);
    expanded.add(new TermQuery(new Term("field", "dog")),
        BooleanClause.Occur.SHOULD);
    assertEquals(expanded.build(), dumb.parse("\"dogs\"","field"));
    /** even with the phrase operator the behavior is the same */
    assertEquals(expanded.build(), dumb.parse("dogs","field"));
    
    /**
     * custom behavior, the synonyms are expanded, unless you use quote operator
     */
    //TODO test something like "SmartQueryParser()"
  }

  // TODO: Remove this specialization once the flexible standard parser gets multi-word synonym support
  @Override
  public void testQPA() throws Exception {
    super.testQPA();

    assertQueryEquals("term phrase term", qpAnalyzer, "term (phrase1 phrase2) term");

    CommonQueryParserConfiguration cqpc = getParserConfig(qpAnalyzer);
    setDefaultOperatorAND(cqpc);
    assertQueryEquals(cqpc, "field", "term phrase term", "+term +(+phrase1 +phrase2) +term");
  }
}