package org.apache.lucene.server.handlers;

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

import java.io.IOException;
import java.text.BreakIterator;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.DoublePoint;
import org.apache.lucene.document.FloatPoint;
import org.apache.lucene.document.IntPoint;
import org.apache.lucene.document.LatLonDocValuesField;
import org.apache.lucene.document.LatLonPoint;
import org.apache.lucene.document.LongPoint;
import org.apache.lucene.expressions.Bindings;
import org.apache.lucene.expressions.Expression;
import org.apache.lucene.expressions.js.JavascriptCompiler;
import org.apache.lucene.facet.DrillDownQuery;
import org.apache.lucene.facet.DrillSideways;
import org.apache.lucene.facet.FacetResult;
import org.apache.lucene.facet.Facets;
import org.apache.lucene.facet.FacetsCollector.MatchingDocs;
import org.apache.lucene.facet.FacetsCollector;
import org.apache.lucene.facet.FacetsConfig;
import org.apache.lucene.facet.LabelAndValue;
import org.apache.lucene.facet.range.DoubleRange;
import org.apache.lucene.facet.range.DoubleRangeFacetCounts;
import org.apache.lucene.facet.range.LongRange;
import org.apache.lucene.facet.range.LongRangeFacetCounts;
import org.apache.lucene.facet.range.Range;
import org.apache.lucene.facet.sortedset.SortedSetDocValuesFacetCounts;
import org.apache.lucene.facet.sortedset.SortedSetDocValuesReaderState;
import org.apache.lucene.facet.taxonomy.FastTaxonomyFacetCounts;
import org.apache.lucene.facet.taxonomy.SearcherTaxonomyManager.SearcherAndTaxonomy;
import org.apache.lucene.facet.taxonomy.TaxonomyFacetCounts;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.ReaderUtil;
import org.apache.lucene.index.Term;
import org.apache.lucene.queries.CommonTermsQuery;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.queryparser.classic.QueryParserBase;
import org.apache.lucene.queryparser.simple.SimpleQueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.BoostQuery;
import org.apache.lucene.search.Collector;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.DisjunctionMaxQuery;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.FieldDoc;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.MultiCollector;
import org.apache.lucene.search.MultiPhraseQuery;
import org.apache.lucene.search.PhraseQuery;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ReferenceManager.RefreshListener;
import org.apache.lucene.search.RegexpQuery;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.SortedNumericSelector;
import org.apache.lucene.search.SortedNumericSortField;
import org.apache.lucene.search.SortedSetSelector;
import org.apache.lucene.search.SortedSetSortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TermRangeQuery;
import org.apache.lucene.search.TimeLimitingCollector;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopDocsCollector;
import org.apache.lucene.search.TopFieldCollector;
import org.apache.lucene.search.TopFieldDocs;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.search.TotalHits;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.search.grouping.AllGroupsCollector;
import org.apache.lucene.search.grouping.FirstPassGroupingCollector;
import org.apache.lucene.search.grouping.GroupDocs;
import org.apache.lucene.search.grouping.GroupSelector;
import org.apache.lucene.search.grouping.SearchGroup;
import org.apache.lucene.search.grouping.SecondPassGroupingCollector;
import org.apache.lucene.search.grouping.TermGroupSelector;
import org.apache.lucene.search.grouping.TopGroups;
import org.apache.lucene.search.grouping.TopGroupsCollector;
import org.apache.lucene.search.join.BitSetProducer;
import org.apache.lucene.search.join.ParentChildrenBlockJoinQuery;
import org.apache.lucene.search.join.QueryBitSetProducer;
import org.apache.lucene.search.join.ScoreMode;
import org.apache.lucene.search.join.ToParentBlockJoinQuery;
import org.apache.lucene.search.uhighlight.PassageFormatter;
import org.apache.lucene.search.uhighlight.PassageScorer;
import org.apache.lucene.search.uhighlight.UnifiedHighlighter;
import org.apache.lucene.search.uhighlight.WholeBreakIterator;
import org.apache.lucene.server.Constants;
import org.apache.lucene.server.FieldDef;
import org.apache.lucene.server.FieldDefBindings;
import org.apache.lucene.server.FinishRequest;
import org.apache.lucene.server.GlobalState;
import org.apache.lucene.server.IndexState;
import org.apache.lucene.server.MyIndexSearcher;
import org.apache.lucene.server.SVJSONPassageFormatter;
import org.apache.lucene.server.ShardState;
import org.apache.lucene.server.WholeMVJSONPassageFormatter;
import org.apache.lucene.server.params.AnyType;
import org.apache.lucene.server.params.BooleanType;
import org.apache.lucene.server.params.EnumType;
import org.apache.lucene.server.params.FloatType;
import org.apache.lucene.server.params.IntType;
import org.apache.lucene.server.params.ListType;
import org.apache.lucene.server.params.LongType;
import org.apache.lucene.server.params.OrType;
import org.apache.lucene.server.params.Param;
import org.apache.lucene.server.params.PolyType.PolyEntry;
import org.apache.lucene.server.params.PolyType;
import org.apache.lucene.server.params.Request;
import org.apache.lucene.server.params.StringType;
import org.apache.lucene.server.params.StructType;
import org.apache.lucene.server.params.Type;
import org.apache.lucene.server.params.WrapType;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.QueryBuilder;
import org.apache.lucene.util.automaton.LevenshteinAutomata;
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;

// nocommit why no double range faceting?

/** Handles {@code search}. */
public class SearchHandler extends Handler {

  /** If a browse-only facet request asks for more than this
   *  count then we don't cache it. */
  private final static int TOP_FACET_CACHE_MAX_FACET_COUNT = 100;

  final static Type SORT_TYPE = new ListType(
                                    new StructType(new Param("field", "The field to sort on.  Pass <code>docid</code> for index order and <code>score</code> for relevance sort.", new StringType()),
                                                   new Param("selector", "For multi valued fields, how to select which value is used for sorting",
                                                             new EnumType("min", "Minimum value",
                                                                          "max", "Maximum value",
                                                                          "middle_min", "Middle value of the set; if there are an even number of values, the lower of the middle two is chosen",
                                                                          "middle_max", "Middle value of the set; if there are an even number of values, the upper of the middle two is chosen"),
                                                             "min"),
                                                   new Param("origin", "For distance sort, the point that we measure distance from",
                                                             new StructType(
                                                                            new Param("latitude", "Latitude of the origin", new FloatType()),
                                                                            new Param("longitude", "Longitude of the origin", new FloatType()))),
                                                   new Param("missingLast", "Whether missing values should sort last instead of first.  Note that this runs \"before\" reverse, so if you sort missing first and reverse=true then missing values will be at the end.", new BooleanType(), false),
                                                   new Param("reverse", "Sort in reverse of the field's natural order", new BooleanType(), false)));

  private final static Type BOOLEAN_OCCUR_TYPE = new EnumType("must", "Clause is required.",
                                                              "should", "Clause is optional.",
                                                              "must_not", "Clause must not match.",
                                                              "filter", "Clause is required but does not score");

  private final static WrapType FILTER_TYPE_WRAP = new WrapType();
  private final static WrapType QUERY_TYPE_WRAP = new WrapType();

  private final static StructType QUERY_TYPE = new StructType(
                                 new Param("boost", "Query boost", new FloatType(), 1.0f),
                                 new Param("field", "Which field to use for this query and any sub-queries ", new StringType()),
                                 new Param("class", "Query class",
                                     new PolyType(Query.class,
                                         new PolyEntry("DisjunctionMaxQuery", "A query that generates the union of documents produced by its subqueries, and that scores each document with the maximum score for that document as produced by any subquery, plus a tie breaking increment for any additional matching subqueries (see @lucene:core:org.apache.lucene.search.DisjunctionMaxQuery)",
                                                       new Param("subQueries", "Queries to OR/max together.", new ListType(QUERY_TYPE_WRAP)),
                                                       new Param("tieBreakMultiplier", "Tie break score.", new FloatType(), 0.0f)),
                                         new PolyEntry("BooleanQuery", "A Query that matches documents matching boolean combinations of other queries, e.g (see @lucene:core:org.apache.lucene.search.BooleanQuery)",
                                                       new Param("subQueries", "Queries to OR/max together.",
                                                           new ListType(
                                                               new StructType(
                                                                   new Param("occur", "Occur.", BOOLEAN_OCCUR_TYPE),
                                                                   new Param("query", "Query for this clause", QUERY_TYPE_WRAP)))),
                                                       //new Param("disableCoord", "If true, coord factors will not be used", new BooleanType(), false),
                                                       new Param("minimumNumberShouldMatch", "Minimum number of should clauses for a match.", new IntType(), 0)),
                                         new PolyEntry("CommonTermsQuery", "A query that executes high-frequency terms in a optional sub-query to prevent slow queries due to common terms like stopwords (see @lucene:queries:org.apache.lucene.queries.CommonTermsQuery)",
                                                       new Param("terms", "List of terms", new ListType(new StringType())),
                                                       new Param("lowFreqOccur", "BooleanClause.Occur used for low frequency terms", BOOLEAN_OCCUR_TYPE),
                                                       new Param("highFreqOccur", "BooleanClause.Occur used for high frequency terms", BOOLEAN_OCCUR_TYPE),
                                                       new Param("maxTermFrequency", "a value from [0.0, 1.0) or an absolutely number >= 1 representing the maximum threshold of a terms document frequency to be considered a low frequency term", new FloatType())),
                                         new PolyEntry("ConstantScoreQuery", "A query that wraps another query or a filter and simply returns a constant score equal to the query boost for every document that matches the filter or query (see @lucene:core:org.apache.lucene.search.ConstantScoreQuery)",
                                                       new Param("query", "Wrapped query", QUERY_TYPE_WRAP)),
                                         new PolyEntry("FuzzyQuery", "Implements the fuzzy search query (see @lucene:core:org.apache.lucene.search.FuzzyQuery)",
                                                       new Param("term", "Term text", new StringType()),
                                                       new Param("maxEdits", "Maximum number of edits; must be 1 or 2", new IntType(), FuzzyQuery.defaultMaxEdits),
                                                       new Param("prefixLength", "Length of the common (non-fuzzy) prefix", new IntType(), FuzzyQuery.defaultPrefixLength),
                                                       new Param("maxExpansions", "Maximum number of terms to match", new IntType(), FuzzyQuery.defaultMaxExpansions),
                                                       new Param("transpositions", "Whether transposition counts as a single edit", new BooleanType(), FuzzyQuery.defaultTranspositions)),
                                         new PolyEntry("BooleanFieldQuery", "Matches true values from a boolean type field",
                                                       new Param("field", "Which boolean field to query", new StringType())),
                                         new PolyEntry("MatchAllDocsQuery", "A query that matches all documents. (see @lucene:core:org.apache.lucene.search.MatchAllDocsQuery)"),
                                         new PolyEntry("MultiPhraseQuery", "MultiPhraseQuery is a generalized version of PhraseQuery, with an added method #add(Term[]) (see @lucene:core:org.apache.lucene.search.MultiPhraseQuery)",
                                                       new Param("terms", "List of terms/positions in the phrase",
                                                                 new ListType(new OrType(new StringType(),
                                                                                         new StructType(
                                                                                                        new Param("term", "Term(s) text", new OrType(new ListType(new StringType()),
                                                                                                                                                  new StringType())),
                                                                                                        new Param("position", "Which position this term should appear at", new IntType())),
                                                                                         new ListType(new StringType())))),
                                                       new Param("slop", "The number of other words permitted between words in the phrase.  If this is 0 (the default) then the phrase must be an exact match.", new IntType(), 0)),
                                         new PolyEntry("NumericRangeQuery", "Matches documents with a numeric field in the specified range",
                                                       new Param("min", "Minimum value", new AnyType()),
                                                       new Param("max", "Maximum value", new AnyType())),
                                         new PolyEntry("PhraseQuery", "A Query that matches documents containing a particular sequence of terms (see @lucene:core:org.apache.lucene.search.PhraseQuery)",
                                                       new Param("terms", "List of terms in the phrase", new ListType(new StringType())),
                                                       new Param("slop", "The number of other words permitted between words in the phrase.  If this is 0 (the default) then the phrase must be an exact match.", new IntType(), 0)),
                                         new PolyEntry("PrefixQuery", "A Query that matches documents containing terms with a specified prefix (see @lucene:core:org.apache.lucene.search.PrefixQuery)",
                                                       new Param("term", "Prefix text", new StringType())),
                                         new PolyEntry("RegexpQuery", "A fast regular expression query based on the org.apache.lucene.util.automaton package (see @lucene:core:org.apache.lucene.search.RegexpQuery)",
                                                       new Param("regexp", "Regular expression text", new StringType())),
                                         new PolyEntry("TermRangeQuery", "A Query that matches documents within an range of terms (see @lucene:core:org.apache.lucene.search.TermRangeQuery)",
                                                       new Param("lowerTerm", "Lower term", new StringType()),
                                                       new Param("includeLower", "True if lower term is included", new BooleanType()),
                                                       new Param("upperTerm", "Upper term", new StringType()),
                                                       new Param("includeUpper", "True if upper term is included", new BooleanType())),
                                         new PolyEntry("TermQuery", "A Query that matches documents containing a term (see @lucene:core:org.apache.lucene.search.TermQuery)",
                                                       new Param("term", "Term text", new StringType())),
                                         new PolyEntry("ToParentBlockJoinQuery", "A parent query that wraps another (child) query and joins matches from the child to the parent documents (see @lucene:join:org.apache.lucene.search.ToParentBlockJoinQuery)",
                                                       new Param("childQuery", "Child query", QUERY_TYPE_WRAP),
                                                       new Param("parentsFilter", "Filter identifying parent documents", QUERY_TYPE_WRAP),
                                                       new Param("scoreMode", "How scores are propogated from children to parent hit",
                                                                 new EnumType(
                                                                              "Avg", "Average all child scores",
                                                                              "Max", "Max of all child scores",
                                                                              "Total", "Sum of all child scores",
                                                                              "None", "Child scores are ignored"), "Max"),
                                                       new Param("childHits", "Whether and how to return child hits for each parent hit",
                                                                 new StructType(
                                                                     new Param("sort", "How to sort child hits", SORT_TYPE),
                                                                     new Param("maxChildren", "Maximum number of children to retrieve per parent", new IntType(), Integer.MAX_VALUE),
                                                                     new Param("trackScores", "Whether to compute scores", new BooleanType(), true)
                                                                     //new Param("trackMaxScore", "Whether to compute max score", new BooleanType(), true)))),
                                                                                ))),
                                         new PolyEntry("text", "Parse text into query using default QueryParser.",
                                                       new Param("text", "Query text to parse", new StringType()),
                                                       new Param("defaultField", "Default field for QueryParser", new StringType())),
                                         new PolyEntry("LatLonBoxQuery", "A Query that matches documents containing a LatLonPoint inside a box (see @lucene:sandbox:org.apache.lucene.document.LatLonPoint#newBoxQuery)",
                                                       new Param("minLatitude", "Minimum latitude, inclusive", new FloatType()),
                                                       new Param("maxLatitude", "Maximum latitude, inclusive", new FloatType()),
                                                       new Param("minLongitude", "Minimum longitude, inclusive", new FloatType()),
                                                       new Param("maxLongitude", "Maximum longitude, inclusive", new FloatType())),
                                         new PolyEntry("LatLonDistanceQuery", "A Query that matches documents containing a LatLonPoint within a specified distance of an origin point (see @lucene:sandbox:org.apache.lucene.document.LatLonPoint#newDistanceQuery)",
                                                       new Param("latitude", "Latitude of the origin", new FloatType()),
                                                       new Param("longitude", "Longitude of the origin", new FloatType()),
                                                       new Param("radiusMeters", "Radius in meters", new FloatType())),
                                         new PolyEntry("LatLonPolygonQuery", "A Query that matches documents containing a LatLonPoint within a specified polygon (see @lucene:sandbox:org.apache.lucene.document.LatLonPoint#newPolygonQuery)",
                                                       new Param("geojson", "GEOJson string describing the polygon (an alternative to vertices+holes)",
                                                                 new StringType()),
                                                       new Param("polygons", "List of polygons to search",
                                                           new ListType(
                                                               new StructType(
                                                                   new Param("vertices", "Array of latitude/longitude points",
                                                                             new ListType(new ListType(new FloatType()))),
                                                                   new Param("holes", "Array of array of latitude/longitude points for the holes to exclude hits within the polygon",
                                                                             new ListType(new ListType(new ListType(new FloatType())))))))),
                                         new PolyEntry("WildcardQuery", "Implements the wildcard search query (see @lucene:core:org.apache.lucene.search.WildcardQuery)",
                                                       new Param("term", "Wildcard text", new StringType())))));

  static {
    QUERY_TYPE_WRAP.set(QUERY_TYPE);
  }

  public static final StructType SEARCHER_VERSION_TYPE = new StructType(
                new Param("indexGen", "Search a generation previously returned by an indexing operation such as #addDocument.  Use this to search a non-committed (near-real-time) view of the index.", new LongType()),
                new Param("snapshot", "Search a snapshot previously created with #createSnapshot", new StringType()),
                new Param("version", "Search a specific searcher version.  This is typically used by follow-on searches (e.g., user clicks next page, drills down, or changes sort, etc.) to get the same searcher used by the original search.", new LongType()));

  final static Type LOCALE_TYPE = new StructType(new Param("language", "Locale language", new StringType()),
                                                 new Param("country", "Locale country", new StringType()),
                                                 new Param("variant", "Locale variant", new StringType()));

  private final static Type QUERY_PARSER_TYPE = new StructType(
                      new Param("defaultOperator", "Whether terms are OR'd or AND'd by default.",
                                new EnumType("or", "or", "and", "and"), "or"),
                      new Param("fuzzyMinSim", "Minimum similarity for fuzzy queries", new IntType(), LevenshteinAutomata.MAXIMUM_SUPPORTED_DISTANCE),
                      new Param("fuzzyPrefixLength", "Prefix length for fuzzy queries", new IntType(), FuzzyQuery.defaultPrefixLength),
                      new Param("phraseSlop", "Default slop for phrase queries", new IntType(), 0),
                      new Param("enablePositionIncrements", "When set, phrase and multi-phrase queries will be aware of position increments", new BooleanType(), true),
                      new Param("locale", "Locale to be used by date range parsing, lowercasing and other locale-sensitive operations", LOCALE_TYPE),
                      new Param("class", "Which QueryParser to use.",
                                new PolyType(QueryParser.class,
                                             new PolyEntry("SimpleQueryParser", "@lucene:queryparser:org.apache.lucene.queryparser.simple.SimpleQueryParser is used to parse human readable query syntax.",
                                                 new Param("operators", "Which operators to allow",
                                                     new ListType(
                                                         new EnumType("AND", "Enable the AND operator with +",
                                                                      "OR", "Enable the OR operator with |",
                                                                      "NOT", "Enable the NOT operator with -",
                                                                      "PREFIX", "Enable the PREFIX operator with *",
                                                                      "PHRASE", "Enable the PHRASE operator with \"",
                                                                      "PRECEDENCE", "Enable the PRECEDENCE operator with ( and )",
                                                                      "ESCAPE", "Enable the ESCAPE operator with \\",
                                                                      "WHITESPACE", "Enable the WHITESPACE operator with ' ', '\\n', '\\r, or '\\t'")),
                                                     new ArrayList<Object>()),
                                                 new Param("fields", "Which fields/boosts to query against",
                                                     new ListType(new OrType(new StringType(),
                                                          new StructType(
                                                              new Param("field", "Field name", new StringType()),
                                                              new Param("boost", "Field boost", new FloatType())))))),
                                             new PolyEntry("MultiFieldQueryParser", "",
                                                           new Param("fields", "Which fields/boosts to query against",
                                                                     new ListType(new OrType(new StringType(),
                                                                                             new StructType(
                                                                                                            new Param("field", "Field name", new StringType()),
                                                                                                            new Param("boost", "Field boost", new FloatType())))))),
                                             new PolyEntry("classic", "Classic QueryParser.",
                                                           new Param("defaultField", "Default field to query against.", new StringType()))),
                                "classic")
                      );

  private static final StructType NUMERIC_RANGE_TYPE = new StructType(new Param("label", "Label for this range", new StringType()),
                                                           new Param("min", "Min value for the range", new OrType(new LongType(), new FloatType())),
                                                           new Param("minInclusive", "True if the min value is inclusive", new BooleanType()),
                                                           new Param("max", "Max value for the range", new OrType(new LongType(), new FloatType())),
                                                           new Param("maxInclusive", "True if the max value is inclusive", new BooleanType()));

  private final static StructType TYPE =
    new StructType(
        new Param("indexName", "Which index to search", new StringType()),
        new Param("timeStamp", "The 'current' timestamp to use, e.g. for blended sorting; typically this should be fixed and reused from the returned value from the first query in a session so that as the user does follow-on paging, drill-downs, etc., the results don't unexpectedly changing.", new LongType()),
        new Param("queryText", "Query text to parse using the specified QueryParser.", new StringType()),
        new Param("queryParser", "Which QueryParser to use; by default MultiFieldQUeryParser searching all indexed fields will be used", QUERY_PARSER_TYPE),
        new Param("highlighter", "Highlighter configuration to use when highlighting matches.",
                  new StructType(
                      new Param("maxPassages", "Maximum number of passages to extract.", new IntType(), 2),
                      new Param("class", "Which implementation to use",
                                new PolyType(Object.class,
                                             new PolyEntry("UnifiedHighlighter", "UnifiedHighlighter",
                                                           new Param("maxLength", "Only highlight the first N bytes of each item.", new IntType(), UnifiedHighlighter.DEFAULT_MAX_LENGTH),
                                                           new Param("maxSnippetLength", "Maximum length (in chars) for each text snippet chunk.", new IntType(), 100),
                                                           new Param("passageScorer.b", "Scoring factor b in PassageScorer", new FloatType(), 0.75f),
