/* $This file is distributed under the terms of the license in LICENSE$ */

package edu.cornell.mannlib.vitro.webapp.searchengine.solr;

import static edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames.ALLTEXT;
import static edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames.ALLTEXTUNSTEMMED;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrQuery.ORDER;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.SolrInputField;

import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchFacetField;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchInputDocument;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchInputField;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery.Order;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchResponse;
import edu.cornell.mannlib.vitro.webapp.searchengine.base.BaseSearchFacetField;
import edu.cornell.mannlib.vitro.webapp.searchengine.base.BaseSearchFacetField.BaseCount;
import edu.cornell.mannlib.vitro.webapp.searchengine.base.BaseSearchResponse;

/**
 * Utility method for converting from Solr-specific instances to Search-generic
 * instances, and back.
 */
public class SolrConversionUtils {

	// ----------------------------------------------------------------------
	// Convert input documents to Solr-specific.
	// ----------------------------------------------------------------------

	static List<SolrInputDocument> convertToSolrInputDocuments(
			Collection<SearchInputDocument> docs) {
		List<SolrInputDocument> solrDocs = new ArrayList<>();
		for (SearchInputDocument doc : docs) {
			solrDocs.add(convertToSolrInputDocument(doc));
		}
		return solrDocs;
	}

	private static SolrInputDocument convertToSolrInputDocument(
			SearchInputDocument doc) {
		SolrInputDocument solrDoc = new SolrInputDocument(
				convertToSolrInputFieldMap(doc.getFieldMap()));
		solrDoc.setDocumentBoost(doc.getDocumentBoost());
		return solrDoc;
	}

	private static Map<String, SolrInputField> convertToSolrInputFieldMap(
			Map<String, SearchInputField> fieldMap) {
		Map<String, SolrInputField> solrFieldMap = new HashMap<>();
		for (String fieldName : fieldMap.keySet()) {
			solrFieldMap.put(fieldName,
					convertToSolrInputField(fieldMap.get(fieldName)));
		}
		return solrFieldMap;
	}

	private static SolrInputField convertToSolrInputField(
			SearchInputField searchInputField) {
		String name = searchInputField.getName();
		SolrInputField solrField = new SolrInputField(name);

		Collection<Object> values;
		if (name.equals(ALLTEXT) || name.equals(ALLTEXTUNSTEMMED)) {
			values = joinStringValues(searchInputField.getValues());
		} else {
			values = searchInputField.getValues();
		}

		if (values.isEmpty()) {
			// No values, nothing to do.
		} else if (values.size() == 1) {
			// One value? Insure that it is accepted as such.
			solrField.addValue(values.iterator().next(),
					searchInputField.getBoost());
		} else {
			// A collection of values? Add them.
			solrField.addValue(values, searchInputField.getBoost());
		}

		return solrField;
	}

	/**
	 * Join the String values while preserving the non-String values. It
	 * shouldn't affect the score, and it produces better snippets.
	 */
	private static Collection<Object> joinStringValues(Collection<Object> values) {
		StringBuilder buffer = new StringBuilder();
		List<Object> betterValues = new ArrayList<>();

		for (Object value : values) {
			if (value instanceof String) {
				if (buffer.length() > 0) {
					buffer.append(" ");
				}
				buffer.append((String) value);
			} else {
				betterValues.add(value);
			}
		}

		if (buffer.length() > 0) {
			betterValues.add(buffer.toString());
		}

		return betterValues;
	}

	// ----------------------------------------------------------------------
	// Convert queries to Solr-specific.
	// ----------------------------------------------------------------------

	/**
	 * Convert from a SearchQuery to a SolrQuery, so the Solr server may execute
	 * it.
	 */
	@SuppressWarnings("deprecation")
	static SolrQuery convertToSolrQuery(SearchQuery query) {
		SolrQuery solrQuery = new SolrQuery(query.getQuery());
		solrQuery.setStart(query.getStart());

		int rows = query.getRows();
		if (rows >= 0) {
			solrQuery.setRows(rows);
		}

		for (String fieldToReturn : query.getFieldsToReturn()) {
			solrQuery.addField(fieldToReturn);
		}

		Map<String, Order> sortFields = query.getSortFields();
		for (String sortField : sortFields.keySet()) {
			solrQuery.addSortField(sortField,
					convertToSolrOrder(sortFields.get(sortField)));
		}

		for (String filter : query.getFilters()) {
			solrQuery.addFilterQuery(filter);
		}

		if (!query.getFacetFields().isEmpty()) {
			solrQuery.setFacet(true);
		}

		for (String facetField : query.getFacetFields()) {
			solrQuery.addFacetField(facetField);
		}

		int facetLimit = query.getFacetLimit();
		if (facetLimit >= 0) {
			solrQuery.setFacetLimit(facetLimit);
		}

		int minCount = query.getFacetMinCount();
		if (minCount >= 0) {
			solrQuery.setFacetMinCount(minCount);
		}

		return solrQuery;
	}

	private static ORDER convertToSolrOrder(Order order) {
		if (order == Order.DESC) {
			return ORDER.desc;
		} else {
			return ORDER.asc;
		}
	}

	// ----------------------------------------------------------------------
	// Convert responses to Search-generic
	// ----------------------------------------------------------------------

	static SearchResponse convertToSearchResponse(QueryResponse response) {
		return new BaseSearchResponse(response.getHighlighting(),
				convertToSearchFacetFieldMap(response.getFacetFields()),
				new SolrSearchResultDocumentList(response.getResults()));
	}

	private static Map<String, SearchFacetField> convertToSearchFacetFieldMap(
			List<FacetField> facetFields) {
		Map<String, SearchFacetField> map = new HashMap<>();
		if (facetFields != null) {
			for (FacetField facetField : facetFields) {
				map.put(facetField.getName(),
						convertToSearchFacetField(facetField));
			}
		}
		return map;
	}

	private static SearchFacetField convertToSearchFacetField(
			FacetField facetField) {
		return new BaseSearchFacetField(facetField.getName(),
				convertToSearchFacetFieldCounts(facetField.getValues()));
	}

	private static List<BaseCount> convertToSearchFacetFieldCounts(
			List<FacetField.Count> solrCounts) {
		List<BaseCount> searchCounts = new ArrayList<>();
		if (solrCounts != null) {
			for (FacetField.Count solrCount : solrCounts) {
				searchCounts.add(new BaseCount(solrCount.getName(), solrCount
						.getCount()));
			}
		}
		return searchCounts;
	}

}
