package eu.dnetlib.efg.mdeditor;

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

import javax.xml.ws.wsaddressing.W3CEndpointReference;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Namespace;
import org.dom4j.Node;
import org.dom4j.QName;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.annotation.Required;

import eu.dnetlib.data.information.DataSinkResolver;
import eu.dnetlib.data.information.DataSinkSourceException;
import eu.dnetlib.data.information.DataSource;
import eu.dnetlib.data.information.DataSourceResolver;
import eu.dnetlib.data.information.MDStoreDataSinkSourceDescriptorGenerator;
import eu.dnetlib.data.information.MDStoreDataSourceImpl;
import eu.dnetlib.efg.mdeditor.rmi.MetadataSubmitterService;
import eu.dnetlib.efg.mdeditor.rmi.MetadataSubmitterException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpDocumentNotFoundException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.resultset.IterableResultSetFactory;
import eu.dnetlib.enabling.tools.AbstractBaseService;
import eu.dnetlib.enabling.tools.ServiceLocator;
import eu.dnetlib.miscutils.datetime.DateUtils;

public class MetadataSubmitterServiceImpl extends AbstractBaseService implements MetadataSubmitterService {
	private ServiceLocator<ISLookUpService> lookupLocator;
	private String interpretation = "edited";
	private DataSinkResolver dataSinkResolver;
	private DataSourceResolver dataSourceResolver;
	private MDStoreDataSinkSourceDescriptorGenerator mdStoreDataSinkSourceDescriptorGenerator;
	private IterableResultSetFactory iterableResultSetFactory;


	@Override
	public boolean submit(String mdFormat, List<String> records) throws MetadataSubmitterException {
		Map<String, List<String>> map = assignRecordsToRepositories(records);
		for (Map.Entry<String, List<String>> entry : map.entrySet()) {
			W3CEndpointReference epr = iterableResultSetFactory.createIterableResultSet(entry.getValue());

			String source = obtainDataSource(mdFormat, entry.getKey());
			String sink   = transformInDataSink(source); 

			try {
				dataSinkResolver.resolve(sink).store(epr);
			} catch (DataSinkSourceException e) {
				throw new MetadataSubmitterException("Error storing epr in " + sink, e);
			}
		} 
		return true;
	}

	private Map<String, List<String>> assignRecordsToRepositories(List<String> records) throws MetadataSubmitterException {
		Map<String, List<String>> map = new HashMap<String, List<String>>();

		SAXReader reader = new SAXReader();

		for (String record : records) {
			try {
				Document doc = reader.read(new StringReader(record));
				String repoId = doc.valueOf("//*[local-name()='repositoryId']");

				if (repoId == null || repoId.isEmpty())
					throw new MetadataSubmitterException("Repository ID is missing");

				if (!map.containsKey(repoId))
					map.put(repoId, new ArrayList<String>());

				Node dateNode = doc.selectSingleNode("//*[local-name()='header']/*[local-name()='dateOfCollection']");
				if (dateNode == null) {
					Element header = (Element) doc.selectSingleNode("//*[local-name()='header']");
					dateNode = header.addElement(new QName(
							"dateOfCollection", 
							new Namespace("dri", "http://www.driver-repository.eu/namespace/dri")
					));
				}
				dateNode.setText(DateUtils.now_ISO8601());

				map.get(repoId).add(doc.asXML());
			} catch (Exception e) {
				throw new MetadataSubmitterException("Error parsing records", e);
			}
		}
		return map;
	}


	private String obtainDataSource(String mdFormat, String repoId) throws MetadataSubmitterException {
		String ds = null; 
		String query = 
			"for $x in collection('/db/DRIVER/TransformationDSResources/TransformationDSResourceType')//CONFIGURATION " +
			"where $x/REPOSITORY_SERVICE_IDENTIFIER='" + repoId + "' " +
			"and $x//SOURCE_METADATA_FORMAT/@interpretation='" + interpretation + "' " +
			"and $x//SOURCE_METADATA_FORMAT/@name='" + mdFormat + "' " +
			"return $x//DATA_SOURCE/text()";
		try {
			ds = lookupLocator.getService().getResourceProfileByQuery(query);
		} catch (ISLookUpDocumentNotFoundException e) {
			throw new MetadataSubmitterException("Mdstore not found - xquery: " + query, e);
		} catch (ISLookUpException e) {
			throw new MetadataSubmitterException("Error searching mdstore - xquery: " + query, e);
		}
		if (ds == null || ds.isEmpty())
			throw new MetadataSubmitterException("Mdstore not found - xquery: " + query);

		return ds;
	}


	private String transformInDataSink(String source) throws MetadataSubmitterException {
		DataSource ds = dataSourceResolver.resolve(source);

		if (! (ds instanceof MDStoreDataSourceImpl))
			throw new MetadataSubmitterException("Invalid MDStore Source: " + ds);

		String mdId = ((MDStoreDataSourceImpl) ds).getMdId();
		return mdStoreDataSinkSourceDescriptorGenerator.generateDataSinkDescriptor(mdId, "INCREMENTAL"); 
	}

	public String getInterpretation() {
		return interpretation;
	}

	public void setInterpretation(String interpretation) {
		this.interpretation = interpretation;
	}

	public ServiceLocator<ISLookUpService> getLookupLocator() {
		return lookupLocator;
	}

	@Required
	public void setLookupLocator(ServiceLocator<ISLookUpService> lookupLocator) {
		this.lookupLocator = lookupLocator;
	}

	public IterableResultSetFactory getIterableResultSetFactory() {
		return iterableResultSetFactory;
	}

	@Required
	public void setIterableResultSetFactory(IterableResultSetFactory iterableResultSetFactory) {
		this.iterableResultSetFactory = iterableResultSetFactory;
	}

	public DataSinkResolver getDataSinkResolver() {
		return dataSinkResolver;
	}

	@Required
	public void setDataSinkResolver(DataSinkResolver dataSinkResolver) {
		this.dataSinkResolver = dataSinkResolver;
	}

	public DataSourceResolver getDataSourceResolver() {
		return dataSourceResolver;
	}

	@Required
	public void setDataSourceResolver(DataSourceResolver dataSourceResolver) {
		this.dataSourceResolver = dataSourceResolver;
	}

	public MDStoreDataSinkSourceDescriptorGenerator getMdStoreDataSinkSourceDescriptorGenerator() {
		return mdStoreDataSinkSourceDescriptorGenerator;
	}

	@Required
	public void setMdStoreDataSinkSourceDescriptorGenerator(MDStoreDataSinkSourceDescriptorGenerator mdStoreDataSinkSourceDescriptorGenerator) {
		this.mdStoreDataSinkSourceDescriptorGenerator = mdStoreDataSinkSourceDescriptorGenerator;
	}

}
