package eu.dnetlib.index.query;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.google.common.collect.BiMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import eu.dnetlib.clients.index.query.QueryResponseParser;
import eu.dnetlib.clients.index.utils.IndexFieldUtility;
import eu.dnetlib.rmi.provision.BrowsingRow;
import eu.dnetlib.rmi.provision.GroupResult;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.FacetField.Count;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;

/**
 * The Class SolrResponseParser.
 */
public class SolrResponseParser extends QueryResponseParser {

	/**
	 * logger.
	 */
	private static final Log log = LogFactory.getLog(SolrResponseParser.class);
	/**
	 * The wrapper rank.
	 */
	protected final Function<SolrDocument, String> wrapperRank =
			doc -> addRanking(getSingleField(doc, IndexFieldUtility.RESULT), getSingleField(doc, IndexFieldUtility.SCORE_FIELD));
	/**
	 * The wrapper no rank.
	 */
	protected final Function<SolrDocument, String> wrapperNoRank = doc -> wrap(getSingleField(doc, IndexFieldUtility.RESULT));
	/**
	 * Lower level response.
	 */
	private QueryResponse queryRsp = null;

	/**
	 * The Constructor.
	 *
	 * @param highlightUtils    the highlight utils
	 * @param aliases           the aliases
	 * @param returnEmptyFields the return empty fields
	 * @param includeRanking    the include ranking
	 * @param response          the response
	 */
	public SolrResponseParser(final Function<String, String> highlightUtils, final BiMap<String, String> aliases, final boolean returnEmptyFields,
			final boolean includeRanking, final QueryResponse response) {
		super(highlightUtils, aliases, returnEmptyFields, includeRanking);
		this.queryRsp = response;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public long getNumFound() {

		return this.queryRsp.getResults().getNumFound();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int getQueryTime() {
		return queryRsp.getQTime();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public long getElapsedTime() {
		return queryRsp.getElapsedTime();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String getStatus() {
		return String.valueOf(queryRsp.getStatus());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int getCurrentSize() {
		return queryRsp.getResults().size();
	}

	/**
	 * Gets the query response.
	 *
	 * @return the query response
	 */
	public QueryResponse getQueryResponse() {
		return queryRsp;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<String> getResults() {
		return asRankedList(queryRsp.getResults());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Long getNumberOfBrowsingResults() {
		List<FacetField> ffList = queryRsp.getFacetFields();
		Long maxCount = 0L;

		if (ffList != null) {
			for (FacetField ff : ffList) {
				if (ff != null) {
					Long countFacets = countFacets(ff.getValues());
					if (countFacets > maxCount) {
						maxCount = countFacets;
					}
				}
			}
		}
		return maxCount;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<BrowsingRow> getBrowsingResults() {
		List<BrowsingRow> bresList = new ArrayList<>();
		List<GroupResult> facets = new ArrayList<>();

		final List<FacetField> ffList = queryRsp.getFacetFields();

		Long numberOfBrowsingResults = getNumberOfBrowsingResults();
		for (int i = 0; (ffList != null) && (i < numberOfBrowsingResults); i++) {
			for (FacetField ff : ffList) {

				String name = null;
				if (aliases != null) {
					name = aliases.inverse().get(ff.getName());
				}

				// fix #1456
				if (name == null) {
					name = ff.getName();
				}

				final Count facet = getFacet(ff, i);

				if ((facet != null) && (facet.getCount() > 0)) {

					final String value = facet.getName();
					final int count = (int) facet.getCount();

					if (returnEmptyFields || !value.isEmpty()) {
						facets.add(new GroupResult(name, value, count));
					}
				}
			}

			if (facets.size() > 0) {
				bresList.add(new BrowsingRow(Lists.newArrayList(facets)));
				facets.clear();
			}
		}
		if (log.isDebugEnabled()) {
			log.debug("BrowsingResult size: " + bresList.size());
		}
		return bresList;
	}

	// /////////////// helpers

	/**
	 * Gets the facet.
	 *
	 * @param ff  the ff
	 * @param pos the pos
	 * @return the facet
	 */
	private Count getFacet(final FacetField ff, final int pos) {

		if ((ff.getValues() == null) || (pos >= ff.getValues().size())) return null;
		return ff.getValues().get(pos);
	}

	/**
	 * Given SolrDocumentList, return a List of Strings, representing it.
	 *
	 * @param documentList the document list
	 * @return the list< string>
	 */
	private List<String> asRankedList(final SolrDocumentList documentList) {
		Function<SolrDocument, String> wrapper = includeRanking ? wrapperRank : wrapperNoRank;

		if (queryRsp.getHighlighting() != null) return documentList
				.stream()
				.map(doc -> {
					String score = getSingleField(doc, IndexFieldUtility.SCORE_FIELD);

					String hl = getHighlighting(getSingleField(doc, IndexFieldUtility.INDEX_RECORD_ID));
					String res = hl != null ? hl : getSingleField(doc, IndexFieldUtility.RESULT);

					return includeRanking ? addRanking(res, score) : wrap(res);
				}).map(highlightUtils)
				.collect(Collectors.toList());
		return documentList.stream().map(wrapper).collect(Collectors.toList());
	}

	/**
	 * Converts a String document to
	 * <p>
	 * <record rank="score"> [document] </record>.
	 *
	 * @param doc   the doc
	 * @param score the score
	 * @return the string
	 */
	private String addRanking(final String doc, final String score) {
		return new String("<record rank=\"" + score + "\">" + doc + "</record>");
	}

	/**
	 * Wraps the given document as <record> [document] </record>.
	 *
	 * @param doc the doc
	 * @return the string
	 */
	private String wrap(final String doc) {
		return new String("<record>" + doc + "</record>");
	}

	/**
	 * Gets the single field.
	 *
	 * @param doc       the doc
	 * @param fieldName the field name
	 * @return the single field
	 */
	@SuppressWarnings("unchecked")
	protected String getSingleField(final SolrDocument doc, final String fieldName) {
		Object value = doc.getFieldValue(fieldName);
		if (value instanceof Collection) return Iterables.getOnlyElement((Iterable<String>) value);
		return String.valueOf(value);
	}

	/**
	 * Gets the highlighting.
	 *
	 * @param docId the doc id
	 * @return the highlighting
	 */
	private String getHighlighting(final String docId) {
		final Map<String, List<String>> highlight = queryRsp.getHighlighting().get(docId);

		String result = new String();
		if ((highlight != null) && (highlight.get(IndexFieldUtility.RESULT) != null)) {
			for (String s : highlight.get(IndexFieldUtility.RESULT)) {
				result = result.concat(s);
			}
			return result;
		}
		return null;
	}

	/**
	 * helper method.
	 *
	 * @param facets the list of facets to analyze
	 * @return the number of non-empty facets in the list whose count is greater than zero
	 */
	private Long countFacets(final List<Count> facets) {

		if (facets == null) return 0L;
		return facets.stream().filter(c -> (c != null) && (c.getName() != null) && !c.getName().isEmpty() && (c.getCount() > 0)).count();
	}

	@Override
	public long getStart() {
		// TODO Auto-generated method stub
		return queryRsp.getResults().getStart();
	}

}
