/*
 * Copyright 2017-2018 Emmanuel Keller / Jaeksoft
 *  <p>
 *  Licensed 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
 *  <p>
 *  http://www.apache.org/licenses/LICENSE-2.0
 *  <p>
 *  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 com.jaeksoft.opensearchserver.services;

import com.jaeksoft.opensearchserver.model.CrawlStatus;
import com.jaeksoft.opensearchserver.model.Language;
import com.jaeksoft.opensearchserver.model.SearchResults;
import com.qwazr.search.function.IntFieldSource;
import com.qwazr.search.index.HighlighterDefinition;
import com.qwazr.search.index.QueryBuilder;
import com.qwazr.search.query.BooleanQuery;
import com.qwazr.search.query.CustomScoreQuery;
import com.qwazr.search.query.FunctionQuery;
import com.qwazr.search.query.IntDocValuesExactQuery;
import com.qwazr.search.query.MultiFieldQueryParser;
import com.qwazr.search.query.QueryParserOperator;
import com.qwazr.utils.StringUtils;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.CustomScoreProvider;

import java.util.HashMap;
import java.util.Map;

public class SearchService {

	// Optimization: They are global singleton
	private final HighlighterDefinition titleHighlighter = HighlighterDefinition.of().setMaxPassages(1).build();
	private final HighlighterDefinition contentHighlighter = HighlighterDefinition.of().setMaxPassages(5).build();
	private final FunctionQuery depthFunctionQuery = new FunctionQuery(new IntFieldSource("depth"));
	private final IntDocValuesExactQuery crawledQuery =
			new IntDocValuesExactQuery("crawlStatus", CrawlStatus.CRAWLED.code);

	// Optimization: They are per thread singleton
	private final ThreadLocal<Map<Language, MultiFieldQueryParser.Builder>> queryParserSupplier;
	private final ThreadLocal<QueryBuilder> queryBuilderSupplier;

	public SearchService() {
		queryParserSupplier = ThreadLocal.withInitial(HashMap::new);
		queryBuilderSupplier = ThreadLocal.withInitial(
				() -> new QueryBuilder().returnedField("urlStore", "imageUri", "schemaOrgType", "organizationName",
						"datePublished")
						.highlighter("title", titleHighlighter)
						.highlighter("description", contentHighlighter)
						.highlighter("content", contentHighlighter));
	}

	private MultiFieldQueryParser.Builder getQueryParser(final Language language) {
		return queryParserSupplier.get()
				.computeIfAbsent(language, lang -> MultiFieldQueryParser.of()
						.setDefaultOperator(QueryParserOperator.and)
						.addField("urlLike", "title", lang.title, "description", lang.description, "content",
								lang.content, "full", lang.full)
						.addBoost("urlLike", 13f)
						.addBoost("title", 8f)
						.addBoost(lang.title, 8f)
						.addBoost("description", 5f)
						.addBoost(lang.description, 5f)
						.addBoost("content", 3f)
						.addBoost(lang.content, 3f)
						.addBoost("full", 1f)
						.addBoost(lang.full, 1f));
	}

	public SearchResults webSearch(final IndexService indexService, final Language lang, final String keywords,
			final int start, final int rows) {

		if (StringUtils.isBlank(keywords))
			return null;

		final MultiFieldQueryParser fullTextQuery = getQueryParser(lang).setQueryString(keywords).build();

		final CustomScoreQuery customScoreQuery =
				new CustomScoreQuery(fullTextQuery, DepthScore.class.getName(), depthFunctionQuery);

		final BooleanQuery booleanQuery = BooleanQuery.of()
				.addClause(BooleanQuery.Occur.must, customScoreQuery)
				.addClause(BooleanQuery.Occur.filter, crawledQuery)
				.build();

		final QueryBuilder queryBuilder = queryBuilderSupplier.get().query(booleanQuery).start(start).rows(rows);
		lang.highlights(queryBuilder);

		return new SearchResults(start, rows, indexService.search(queryBuilder.build()), lang);
	}

	public static class DepthScore extends CustomScoreProvider {

		public DepthScore(LeafReaderContext context) {
			super(context);
		}

		public float customScore(int doc, float subQueryScore, float valSrcScore) {
			switch ((int) valSrcScore) {
			case 0:
				return subQueryScore * 2.3f;
			case 1:
				return subQueryScore * 1.8f;
			case 2:
				return subQueryScore * 1.3f;
			case 3:
				return subQueryScore * 1.2f;
			default:
				return subQueryScore;
			}
		}
	}

}

@@ -1889,45 +1889,108 @@
 nal 
-IntDocValuesExactQuery crawledQuery =
+BooleanQuery.BooleanClause crawledClause = new BooleanQuery.BooleanClause(BooleanQuery.Occur.filter,
 %0A%09%09%09
@@ -2056,16 +2056,17 @@
 ED.code)
+)
 ;%0A%0A%09// O
@@ -2264,16 +2264,94 @@
 upplier;
+%0A%09private final ThreadLocal%3CBooleanQuery.Builder%3E booleanQueryBuilderSupplier;
 %0A%0A%09publi
@@ -2702,32 +2702,32 @@
 entHighlighter)%0A
-
 %09%09%09%09%09%09.highlight
@@ -2758,24 +2758,99 @@
 hlighter));%0A
+%09%09booleanQueryBuilderSupplier = ThreadLocal.withInitial(BooleanQuery::of);%0A
 %09%7D%0A%0A%09private
@@ -3977,17 +3977,17 @@
 Query = 
-B
+b
 ooleanQu
@@ -3993,19 +3993,35 @@
 uery
-.of
+BuilderSupplier.get
 ()%0A%09%09%09%09.
 addC
@@ -4012,34 +4012,39 @@
 .get()%0A%09%09%09%09.
-add
+set
 Clause
-(
+s(new 
 BooleanQuery
@@ -4048,46 +4048,15 @@
 ery.
-Occur.must, customScoreQuery)%0A%09%09%09%09.add
+Boolean
 Clau
@@ -4077,22 +4077,39 @@
 y.Occur.
-filter
+must, customScoreQuery)
 , crawle
@@ -4109,21 +4109,22 @@
  crawled
-Query
+Clause
 )%0A%09%09%09%09.b

Use BooleanQuery.Builder singleton