/**
 * 
 */
package de.bstly.board.businesslogic;

import java.util.List;

import javax.persistence.EntityManager;
import javax.transaction.Transactional;

import org.hibernate.search.engine.search.query.SearchResult;
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.massindexing.MassIndexer;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import com.google.common.collect.Lists;

import de.bstly.board.model.Comment;
import de.bstly.board.model.Entry;
import de.bstly.board.model.support.Types;

/**
 * The Class SearchManager.
 */
@Component
public class SearchManager implements SmartInitializingSingleton {

	private Logger logger = LoggerFactory.getLogger(SearchManager.class);

	@Autowired
	private EntityManager em;
	@Autowired
	private SettingsManager settingsManager;
	private SearchSession searchSession;

	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#
	 * afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#
	 * afterSingletonsInstantiated()
	 */
	@Override
	public void afterSingletonsInstantiated() {
		searchSession = Search.session(em);
	}

	/**
	 * On application event.
	 *
	 * @param event the event
	 */
	@EventListener(ContextRefreshedEvent.class)
	@Transactional
	public void onApplicationEvent(ContextRefreshedEvent event) {
		MassIndexer indexer = searchSession.massIndexer().idFetchSize(150)
				.batchSizeToLoadObjects(25).threadsToLoadObjects(12);
		try {
			logger.info("start indexing!");
			indexer.startAndWait();
			logger.info("finished indexing!");
		} catch (InterruptedException e) {
			logger.error("error on indexing!", e);
			Thread.currentThread().interrupt();
		}
	}

	@Transactional
	public SearchResult<Object> search(List<Types> types, String search, int page, int size,
			boolean asc, boolean sortByDate) {
		List<Class<? extends Object>> classes = Lists.newArrayList();
		for (Types type : types) {
			switch (type) {
			case comment:
				classes.add(Comment.class);
				break;
			case entry:
				classes.add(Entry.class);
				break;
			default:
				break;
			}
		}

		if (classes.contains(Entry.class)) {
			return searchSession.search(classes)
					.where(f -> f.bool()
							.should(f.match().fields("title", "title_de").matching(search)
									.boost(settingsManager.getGravity()))
							.should(f.match().fields("text", "text_de").matching(search)))
					.sort(f -> sortByDate
							? (asc ? f.field("created").asc() : f.field("created").desc())
							: (asc ? f.score().asc() : f.score().desc()))
					.fetch(page * size, size);
		} else {
			return searchSession.search(classes).where(
					f -> f.bool().should(f.match().fields("text", "text_de").matching(search)))
					.sort(f -> sortByDate
							? (asc ? f.field("created").asc() : f.field("created").desc())
							: (asc ? f.score().asc() : f.score().desc()))
					.fetch(page * size, size);
		}
	}

}
