/**
 *
 */
package gr.uoa.di.web.utils.search;

import eu.dnetlib.api.data.PublisherService;
import eu.dnetlib.api.data.PublisherServiceException;
import eu.dnetlib.api.data.SearchService;
import eu.dnetlib.api.data.SearchServiceException;
import eu.dnetlib.domain.EPR;
import eu.dnetlib.domain.data.BrowseData;
import eu.dnetlib.domain.data.Document;
import eu.dnetlib.domain.functionality.LayoutField;
import gr.uoa.di.driver.data.browsedata.BrowseDataUtil;
import gr.uoa.di.driver.enabling.resultset.ResultSet;
import gr.uoa.di.driver.enabling.resultset.ResultSetFactory;
import gr.uoa.di.driver.util.ServiceLocator;
import gr.uoa.di.web.utils.search.browse.BrowseDataReader;
import gr.uoa.di.web.utils.search.query.QueryCollectionEnhancer;
import gr.uoa.di.web.utils.search.query.QueryEnhancer;

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

import org.apache.log4j.Logger;

/**
 * Manages all searches in the the web interface. All elements in query forms
 * are passed to the SearchManager and page results are retuned in a format that
 * is straightforward for jsp pages to render.
 * 
 * @author kiatrop
 */
public class SearchManager {
	public static Logger logger = Logger.getLogger(SearchManager.class);
	private static Logger tLogger = Logger.getLogger("gr.uoa.di.driver.web.interceptors.Timer");

	private CriteriaManager criteriaManager = null;
	private BrowseDataReader browseDataReader = null;
	private DocumentReader reader = null;
	private ResultSetFactory rsFactory = null;
	private String indexVersion = null;

	private ServiceLocator<SearchService> searchServiceLocator = null;
	private ServiceLocator<PublisherService> publisherServiceLocator = null;

	private QueryEnhancer enhancer = null;

	public ResultSetFactory getRsFactory() {
		return rsFactory;
	}

	public void setRsFactory(ResultSetFactory rsFactory) {
		this.rsFactory = rsFactory;
	}
	
	public DocumentPage search(String query, int pageSize, int pageNumber, String collectionId) throws SearchServiceException {
		query = enhanceWithCurrentCollection(query, collectionId);
		
		return this.search(query, pageSize, pageNumber);
	}

	/**
	 * Use an internal cache of the {@link #cacheSize} MRU query results. If the
	 * query result is invalidated (result set update fails) the query is
	 * resubmitted. The returned page points to the spacified <code>page</code>
	 * assuming <code>pageSize</code> documents per page.
	 * <p>
	 * NOTE: Cache only search query results -- never cache result set data!
	 * 
	 * @param query
	 *            The query to execute.
	 * @param pageSize
	 *            The number of documents in the page.
	 * @param page
	 *            The number of the page to return. First page is 1.
	 * @return The requested page.
	 * @throws SearchServiceException
	 */
	public DocumentPage search(String query, int pageSize, int pageNumber)
			throws SearchServiceException {
		tLogger.debug("Starting search");
		tLogger.debug("Enhancing query");
		query = enhancer.enhanceQuery(query);
		
		logger.debug("performing search with query = '" + query + "'");

		try {
			tLogger.debug("performing search");
			EPR epr = getSearchServiceLocator().getService().search(query);
			tLogger.debug("Creating rs");
			ResultSet<String> rs = rsFactory.createResultSet(epr);

			// update page from result set
			tLogger.debug("Creating document page");
			DocumentPage page = new DocumentPage(rs, getReader(), pageSize, pageNumber);

			return page;
		} catch (RuntimeException re) {
			throw new SearchServiceException(
					"Error connecting to search service.", re);
		}
	}

	public BrowseData refine(String query, String collectionId, List<String> fields)
			throws SearchServiceException {
		tLogger.debug("Starting refine");
		tLogger.debug("Enhancing query");
		query = enhanceWithCurrentCollection(query, collectionId);
		
		logger.info("running refine query: '" + query + "' for fields: " + fields);
		tLogger.debug("performing refine");
		EPR epr = searchServiceLocator.getService().refine(query, fields);
		tLogger.debug("Creating rs");
		ResultSet<String> rs = rsFactory.createResultSet(epr);

		if (logger.isDebugEnabled()) {
			logger.debug("EPR : " + epr.getEpr());
		}

		BrowseData data = new BrowseData();
		tLogger.debug("Getting " + (rs.size()) + " elements from rs");
		List<String> list = rs.getElements(1, rs.size());
		logger.debug("record list size " + list.size());
		
		tLogger.debug("Parsing browse data");
		if (indexVersion.toLowerCase().equals("new")) {
			for (int i = 0; i < list.size(); i++) {
				String record = list.get(i);
				if (record == null || record.trim().equals("")) {
					continue;
				}
				
				data.append(BrowseDataUtil.parseRows(record));
			}
			
		} else if (indexVersion.toLowerCase().equals("old")) {
			for (int i = 0; i < list.size(); i++) {
				String record = list.get(i);
				if (record == null || record.trim().equals("")) {
					continue;
				}

				int count = BrowseDataUtil.parseCount(record);
				String field = BrowseDataUtil.parseField(record);
				String value = BrowseDataUtil.parseValue(record);
	
				data.addFieldEntry(field, value, count);
			}
		}
		
		return this.browseDataReader.read(data);
	}
	
	private String enhanceWithCurrentCollection(String query,
			String collectionId) {
		
		if (collectionId!= null && !collectionId.isEmpty() && enhancer instanceof QueryCollectionEnhancer) {
			if (!((QueryCollectionEnhancer) enhancer).getCollectionIds().contains(collectionId)) {
				QueryCollectionEnhancer collectionEnhancer = new QueryCollectionEnhancer();
				collectionEnhancer.setCollectionIds(new ArrayList<String>());
				collectionEnhancer.addCollectionId(collectionId);
				query = collectionEnhancer.enhanceQuery(query);
			}
		}
		return query;
	}

	public BrowseData browse(String prefix, String field)
			throws SearchServiceException {
		logger.info("running browse query for field " + field);
		BrowseData data = searchServiceLocator.getService().browse(prefix,
				field);
			
		return this.browseDataReader.read(data);

	}

	protected DocumentReader getReader() throws SearchServiceException {
		if (reader == null) {
			LayoutManager lm = criteriaManager.getLayoutManager();
			// use all index fields
			List<LayoutField> fields = lm.getIndexLayoutManager()
					.getResultFields();

			// map index field names to searchable names
			Map<String, String> nameMap = new HashMap<String, String>();
			for (LayoutField field : fields) {
				String name = field.getName();
				nameMap.put(name, lm.getNameFromIndexType(name));
			}

			// hardcoded header searchables -- not part of index layout
			LayoutField idField = new LayoutField();
			idField.setIndexable(false);
			idField.setName("id");
			idField.setResult(true);
			idField.setStat(false);
			idField.setTokenizable(false);
			idField.setType("objIdentifier");
			idField.setXpath("//dri:objIdentifier");
			fields.add(idField);
			LayoutField dateField = new LayoutField();
			dateField.setIndexable(false);
			dateField.setName("dateCollected");
			dateField.setResult(true);
			dateField.setStat(false);
			dateField.setTokenizable(false);
			dateField.setType("dateOfCollection");
			dateField.setXpath("//dri:dateOfCollection");
			fields.add(dateField);

			//logger.debug("vocabulary map: " + vocabularyMap.keySet());
			reader = new DocumentReader(fields, nameMap);
		}
		
		return reader;
	}

	public Document retrieveDocument(String id) throws SearchServiceException {
		try {
			PublisherService publisher = getPublisherServiceLocator()
					.getService();
			return getReader().read(publisher.getResourceById(id, "DMF"));

		} catch (Throwable t) {
			throw new SearchServiceException(
					"Cannot retrieve document with id: " + id, t);
		}
	}

	public List<Document> retrieveDocuments(List<String> ids)
			throws SearchServiceException {
		List<Document> documents = new ArrayList<Document>();
		PublisherService publisher = getPublisherServiceLocator().getService();

		for (String id : ids) {
			try {
				String xmlDoc = publisher.getResourceById(id, "DMF");
				if (xmlDoc != null)
					documents.add(getReader().read(xmlDoc));
				else 
					logger.warn("Document " + id + " not found in publisher");
			} catch (Exception t) {
				throw new SearchServiceException("Cannot retrieve document with id: " + id, t);
			}
		}
		
		return documents;
	}

	public DocumentPage retrieveDocuments(String id, List<String> ids,
			int page, int size) throws PublisherServiceException, SearchServiceException {

		if(ids==null || ids.isEmpty()){
			return DocumentPage.EMPTY_PAGE;
		}
		
		PublisherService publisher = getPublisherServiceLocator().getService();
		EPR epr = publisher.getResourcesByIds(ids, "DMF");
		ResultSet<String> rs = rsFactory.createResultSet(epr);

		if (logger.isDebugEnabled()) {
			logger.debug("EPR : " + epr.getEpr());
		}

		DocumentPage documentPage = 
			new DocumentPage(rs, getReader(), size, page);

		return documentPage;
	}

	@Deprecated
	public int getCacheSize() {
		return 0;
	}

	@Deprecated
	public void setCacheSize(int cacheSize) {
	}

	public CriteriaManager getCriteriaManager() {
		return criteriaManager;
	}

	public void setCriteriaManager(CriteriaManager criteriaManager) {
		this.criteriaManager = criteriaManager;
	}

	@Deprecated
	public boolean isEnableQueryCache() {
		return false;
	}

	@Deprecated
	public void setEnableQueryCache(boolean enableQueryCache) {
	}

	public String getIndexVersion() {
		return indexVersion;
	}

	public void setIndexVersion(String indexVersion) {
		this.indexVersion = indexVersion;
	}
	
	public BrowseDataReader getBrowseDataReader() {
		return browseDataReader;
	}

	public void setBrowseDataReader(BrowseDataReader browseDataReader) {
		this.browseDataReader = browseDataReader;
	}

	public ServiceLocator<PublisherService> getPublisherServiceLocator() {
		return publisherServiceLocator;
	}

	public void setPublisherServiceLocator(
			ServiceLocator<PublisherService> publisherServiceLocator) {
		this.publisherServiceLocator = publisherServiceLocator;
	}

	public ServiceLocator<SearchService> getSearchServiceLocator() {
		return searchServiceLocator;
	}

	public void setSearchServiceLocator(
			ServiceLocator<SearchService> searchServiceLocator) {
		this.searchServiceLocator = searchServiceLocator;
	}

	public QueryEnhancer getEnhancer() {
		return enhancer;
	}

	public void setEnhancer(QueryEnhancer enhancer) {
		this.enhancer = enhancer;
	}
}