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);
		}
	}

	@SuppressWarnings("unchecked")
	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(final 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
			@SuppressWarnings("rawtypes")
			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();
	}

}
