package eu.dnetlib.functionality.index.solr;

import java.io.StringReader;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.solr.common.SolrDocument;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import com.google.common.collect.Lists;

import eu.dnetlib.functionality.index.solr.feed.IndexDocument;
import eu.dnetlib.functionality.index.solr.utils.IndexMap;

/**
 * Callable class that iterates over a queue of SolrDocuments
 * and provides to updated every document field contained in 
 * the given map (all fields if the map is empty) that satisfy 
 * the given regex, with the given replacement.
 * 
 * Returns the number of actually performed replacements.
 * 
 * @author claudio
 *
 */
public class IndexDocumentUpdater implements Callable<Integer> {
	/**
	 * logger.
	 */
	private static final Log log = LogFactory.getLog(IndexDocumentUpdater.class); //NOPMD
	
	/**
	 * number of performed replacements.
	 */
	private transient Integer nUpdates = 0;
	
	/**
	 * map containing fieldNames as keys and their relative XPaths
	 * in the result field.
	 */
	private transient Map<String, String> fieldXPath;
	
	/**
	 * The queue.
	 */
	private transient ArrayBlockingQueue<Object> queue;
	
	/**
	 * Marks the end of available objects in the queue.
	 */
	private transient Object sentinel;
	
	/**
	 * Regex used to match what should be replaced.
	 */
	private String regex;
	
	/**
	 * Replacement.
	 */
	private String replace;
	
	/**
	 * Index to operate on.
	 */
	private SolrIndex index;
	
	/**
	 * Record parser.
	 */
	private SAXReader saxReader = new SAXReader();
	
	/**
	 * IndexDocumentUpdater constructor.
	 * @param queue	 the queue
	 * @param sentinel the sentinel
	 * @param fieldXPath map containing fieldNames as keys and their relative XPaths in the result field.
	 * @param index Index to operate on
	 * @param regex Regex used to match what should be replaced
	 * @param replace Replacement
	 */
	public IndexDocumentUpdater(
			final ArrayBlockingQueue<Object> queue,
			final Object sentinel,
			final Map<String, String> fieldXPath,
			final SolrIndex index,
			final String regex,
			final String replace) {
		
		this.queue = queue;
		this.sentinel = sentinel;
		this.fieldXPath = fieldXPath;
		this.regex = regex;
		this.replace = replace;
		this.index = index;
	}
	
	@Override
	public Integer call() throws Exception {
		try {
			while (true) {
				Object object = queue.take();
				if (object.equals(sentinel))
					break;
				
				SolrDocument doc = (SolrDocument) object;
				IndexDocument newDoc = updateDocumentFields(fieldXPath, regex,	replace, index, doc);
				
				newDoc.setField(IndexMap.RESULT, updateResultDoc(newDoc));
				
				index.add(newDoc);
			}
			return nUpdates;
		} catch (InterruptedException e) {
			log.error("error in update document thread", e);
			throw new RuntimeException(e);
		} catch (Exception e) {
			log.error("error in update document thread", e);
			throw new RuntimeException(e);
		} 
	}
	
	private IndexDocument updateDocumentFields(
			final Map<String, String> fieldXPath, final String regex,
			final String replace, final SolrIndex index,
			final SolrDocument doc) {
		IndexDocument newDoc = new IndexDocument(index).setContent(doc);
		
		for(String fieldName : doc.getFieldNames()) {
			
			if (fieldXPath.containsKey(fieldName) | fieldXPath.isEmpty()) {
				Object values = doc.getFieldValuesMap().get(fieldName);

				if (values instanceof Collection) {
					Collection<String> newValues = Lists.newArrayList();
					
					for(String value : (List<String>) values) {
						
						if (value.matches(regex)) {
							newValues.add(value.replaceAll(regex, replace));
							nUpdates++;
						} else
							newValues.add(value);
					}
					newDoc.setField(fieldName, newValues);
					
				} else {
					String value = (String) values;
					if (value.matches(regex)) {
						newDoc.setField(fieldName, value.replaceAll(regex, replace));
						nUpdates++;
					}
				}
			}
		}
		return newDoc;
	}
	
	private String updateResultDoc(IndexDocument indexDocument) throws DocumentException {

		String resultXml = indexDocument.getFieldValue(IndexMap.RESULT).toString();
		Document resultDoc = saxReader.read(new StringReader(resultXml));
		
		for(Entry<String, String> fieldEntry : fieldXPath.entrySet()) {
			
			// TODO check if it's possible to execute the given xpath
			List selectNodes = resultDoc.selectNodes(fieldEntry.getValue());
			for (Object o : selectNodes) {
				Node node = (Node) o;
				String value = node.getText().trim();
				node.setText(value.replaceAll(regex, replace));
			}
		}
		
		return resultDoc.asXML();
	}	

}
