package eu.dnetlib.oai.conf;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import eu.dnetlib.clients.enabling.ISLookUpClient;
import eu.dnetlib.oai.PublisherField;
import eu.dnetlib.oai.info.SetInfo;
import eu.dnetlib.rmi.common.UnimplementedException;
import eu.dnetlib.rmi.enabling.ISLookUpException;
import eu.dnetlib.rmi.provision.MDFInfo;
import eu.dnetlib.rmi.provision.OaiPublisherRuntimeException;
import eu.dnetlib.utils.MetadataReference;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Instances of this class read the configuration of the OAI publisher from the IS, by performing xquery on the Exist database.
 *
 * @author alessia
 */
public class OAIConfigurationExistReader implements OAIConfigurationReader {

	@Autowired
	private ISLookUpClient lookupClient;

	private String xpathToIdScheme = "//RESOURCE_PROFILE[.//RESOURCE_TYPE/@value = 'OAIPublisherConfigurationDSResourceType']//CONFIGURATION/IDSCHEME/text()";
	private String xpathToIdNamespace =
			"//RESOURCE_PROFILE[.//RESOURCE_TYPE/@value = 'OAIPublisherConfigurationDSResourceType']//CONFIGURATION/IDNAMESPACE/text()";

	@Override
	public List<SetInfo> getSets() {

		final String query =
				"for $x in collection('/db/DRIVER/OAIPublisherConfigurationDSResources/OAIPublisherConfigurationDSResourceType')//CONFIGURATION/OAISETS/OAISET "
						+ "return concat($x/spec, ':-:', $x/name, ':-:', $x//description , ':-:', $x//query, ':-:', $x/@enabled/string())";
		try {
			return this.lookupClient.searchAndMapToClassByConstructor(query, SetInfo.class, ":-:");
		} catch (final ISLookUpException e) {
			throw new RuntimeException(e);
		}

	}

	@Override
	public List<SetInfo> getSets(final boolean onlyEnabled) {
		if (onlyEnabled) {
			final String query =
					"for $x in collection('/db/DRIVER/OAIPublisherConfigurationDSResources/OAIPublisherConfigurationDSResourceType')//CONFIGURATION/OAISETS/OAISET "
							+ "where $x/@enabled/string() = 'true' "
							+ "return concat($x/spec, ':-:', $x/name, ':-:', $x//description , ':-:', $x//query, ':-:', $x/@enabled/string())";
			try {
				return this.lookupClient.searchAndMapToClassByConstructor(query, SetInfo.class, ":-:");
			} catch (final ISLookUpException e) {
				throw new RuntimeException(e);
			}
		} else {
			return this.getSets();
		}
	}

	@Override
	public List<String> getSetSpecs() {
		final String query =
				"for $x in collection('/db/DRIVER/OAIPublisherConfigurationDSResources/OAIPublisherConfigurationDSResourceType')//CONFIGURATION/OAISETS/OAISET "
						+ "return $x/spec/string() ";
		try {
			return this.lookupClient.search(query);
		} catch (final ISLookUpException e) {
			throw new OaiPublisherRuntimeException(e);
		}

	}

	@Override
	public List<String> getSetSpecs(final boolean onlyEnabled) {
		final String query =
				"for $x in collection('/db/DRIVER/OAIPublisherConfigurationDSResources/OAIPublisherConfigurationDSResourceType')//CONFIGURATION/OAISETS/OAISET "
						+ "where $x/@enabled/string() = '" + onlyEnabled + "' " + "return $x/spec/string() ";
		try {
			return this.lookupClient.search(query);
		} catch (final ISLookUpException e) {
			throw new OaiPublisherRuntimeException(e);
		}

	}

	@Override
	public SetInfo getSetInfo(final String setSpec) {
		final String query =
				"for $x in collection('/db/DRIVER/OAIPublisherConfigurationDSResources/OAIPublisherConfigurationDSResourceType')//CONFIGURATION/OAISETS/OAISET "
						+ "where $x/spec = '"
						+ setSpec
						+ "' "
						+ "return concat($x/spec, ':-:', $x/name, ':-:', $x//description , ':-:', $x//query, ':-:', $x/@enabled/string())";

		try {
			final List<SetInfo> info = this.lookupClient.searchAndMapToClassByConstructor(query, SetInfo.class, ":-:");
			if (info != null && info.size() > 0) { return info.get(0); }
			return null;
		} catch (final Throwable e) {
			throw new OaiPublisherRuntimeException(e);
		}
	}

	@Override
	public List<MetadataReference> getSourceMetadataFormats() {
		final String query =
				"for $x in collection('/db/DRIVER/OAIPublisherConfigurationDSResources/OAIPublisherConfigurationDSResourceType')//CONFIGURATION//METADATAFORMAT "
						+ "return concat($x//SOURCE_METADATA_FORMAT/@name/string(), ':-:',  $x//SOURCE_METADATA_FORMAT/@layout/string(), ':-:', $x//SOURCE_METADATA_FORMAT/@interpretation/string())";

		List<String> res;
		try {
			res = this.lookupClient.search(query);
		} catch (final ISLookUpException e) {
			throw new OaiPublisherRuntimeException(e);
		}
		final Set<MetadataReference> sources = new HashSet<>();

		res.forEach(src -> {
			final String[] splitted = src.split(":-:");
			MetadataReference mdref = new MetadataReference(splitted[0],splitted[1],splitted[2] );
			sources.add(mdref);
		});
		return sources.stream().collect(Collectors.toList());
	}

	@Override
	public List<MDFInfo> getMetadataFormatInfo() {
		final String query =
				"for $x in collection('/db/DRIVER/OAIPublisherConfigurationDSResources/OAIPublisherConfigurationDSResourceType')//CONFIGURATION//METADATAFORMAT "
						+ "return concat($x/@metadataPrefix/string(), ':-:', $x//SCHEMA , ':-:', $x//NAMESPACE,  "
						+ "':-:', $x//SOURCE_METADATA_FORMAT/@name/string(), ':-:',  $x//SOURCE_METADATA_FORMAT/@layout/string(), ':-:', $x//SOURCE_METADATA_FORMAT/@interpretation/string(), "
						+ "':-:', $x//BASE_QUERY, ':-:',  $x//TRANSFORMATION_RULE, ':-:', $x/@exportable/string() )";
		try {
			return this.lookupClient.searchAndMapToClassByConstructor(query, MDFInfo.class, ":-:");

		} catch (final Throwable e) {
			throw new OaiPublisherRuntimeException(e);
		}
	}

	@Override
	public List<MDFInfo> getMetadataFormatInfo(final boolean onlyEnabled) {
		if (onlyEnabled) {
			final String query =
					"for $x in collection('/db/DRIVER/OAIPublisherConfigurationDSResources/OAIPublisherConfigurationDSResourceType')//CONFIGURATION//METADATAFORMAT "
							+ "where $x/@exportable/string() = 'true' "
							+ "return concat($x/@metadataPrefix/string(), ':-:', $x//SCHEMA , ':-:', $x//NAMESPACE,  "
							+ "':-:', $x//SOURCE_METADATA_FORMAT/@name/string(), ':-:',  $x//SOURCE_METADATA_FORMAT/@layout/string(), ':-:', $x//SOURCE_METADATA_FORMAT/@interpretation/string(), "
							+ "':-:', $x//BASE_QUERY, ':-:',  $x//TRANSFORMATION_RULE, ':-:', $x/@exportable/string() )";
			try {
				return this.lookupClient.searchAndMapToClassByConstructor(query, MDFInfo.class, ":-:");

			} catch (final Throwable e) {
				throw new OaiPublisherRuntimeException(e);
			}
		} else {
			return getMetadataFormatInfo();
		}
	}

	@Override
	public MDFInfo getMetadataFormatInfo(final String mdPrefix) {
		final String query =
				"for $x in collection('/db/DRIVER/OAIPublisherConfigurationDSResources/OAIPublisherConfigurationDSResourceType')//CONFIGURATION//METADATAFORMAT "
						+ "where $x/@metadataPrefix/string()='"
						+ mdPrefix
						+ "' "
						+ "return concat($x/@metadataPrefix/string(), ':-:', $x//SCHEMA , ':-:', $x//NAMESPACE,  "
						+ "':-:', $x//SOURCE_METADATA_FORMAT/@name/string(), ':-:',  $x//SOURCE_METADATA_FORMAT/@layout/string(), ':-:', $x//SOURCE_METADATA_FORMAT/@interpretation/string(), "
						+ "':-:', $x//BASE_QUERY,':-:', $x//TRANSFORMATION_RULE, ':-:', $x/@exportable/string() )";
		try {
			final List<MDFInfo> mdfInfos = this.lookupClient.searchAndMapToClassByConstructor(query, MDFInfo.class, ":-:");
			if (mdfInfos != null && mdfInfos.size() > 0) { return mdfInfos.get(0); }
			return null;

		} catch (final Throwable e) {
			throw new OaiPublisherRuntimeException(e);
		}
	}

	@Override
	public List<PublisherField> getFields() {
		throw new UnimplementedException();
	}

	@Override
	public List<PublisherField> getFields(final String format, final String interpretation, final String layout) {
		final String query =
				"for $x in collection('/db/DRIVER/OAIPublisherConfigurationDSResources/OAIPublisherConfigurationDSResourceType')//CONFIGURATION//INDICES/INDEX "
						+ "return concat($x/@name, ':-:', $x/@repeatable, ':-:', string-join($x/SOURCE[@name='"
						+ format
						+ "' and @interpretation='"
						+ interpretation
						+ "' and @layout='" + layout + "']/@path, ':-:'))";
		List<String> res;
		try {
			res = this.lookupClient.search(query);
		} catch (final ISLookUpException e) {
			throw new OaiPublisherRuntimeException(e);
		}
		final List<PublisherField> fields = new ArrayList<>();
		for (final String index : res) {
			final String[] splitted = index.split(":-:");
			final String indexName = splitted[0];
			final String repeatable = splitted[1];
			final PublisherField field = new PublisherField();
			field.setFieldName(indexName);
			field.setRepeatable(Boolean.valueOf(repeatable));
			final Multimap<String, String> sources = ArrayListMultimap.create();
			final String mdFormat = format + "-" + layout + "-" + interpretation;
			for (int i = 2; i < splitted.length; i++) {
				sources.put(mdFormat, splitted[i]);
			}
			field.setSources(sources);
			fields.add(field);
		}
		return fields;
	}

	@Override
	public List<String> getFieldNames() {
		final String query =
				"//RESOURCE_PROFILE[.//RESOURCE_TYPE/@value = 'OAIPublisherConfigurationDSResourceType']//CONFIGURATION//INDICES/INDEX/@name/string()";
		try {
			return this.lookupClient.search(query);
		} catch (final ISLookUpException e) {
			throw new OaiPublisherRuntimeException(e);
		}
	}

	@Override
	public List<MDFInfo> getFormatsServedBy(final String sourceFormatName, final String sourceFormatLayout, final String sourceFormatInterpretation) {
		final String query =
				"for $x in collection('/db/DRIVER/OAIPublisherConfigurationDSResources/OAIPublisherConfigurationDSResourceType')//CONFIGURATION//METADATAFORMAT[.//SOURCE_METADATA_FORMAT/@name = '"
						+ sourceFormatName
						+ "' and .//SOURCE_METADATA_FORMAT/@layout = '"
						+ sourceFormatLayout
						+ "' and .//SOURCE_METADATA_FORMAT/@interpretation = '"
						+ sourceFormatInterpretation
						+ "'] "
						+ "return concat($x/@metadataPrefix/string(), ':-:', $x//SCHEMA , ':-:', $x//NAMESPACE,  "
						+ "':-:', $x//SOURCE_METADATA_FORMAT/@name/string(), ':-:',  $x//SOURCE_METADATA_FORMAT/@layout/string(), ':-:', $x//SOURCE_METADATA_FORMAT/@interpretation/string(), "
						+ "':-:', $x//BASE_QUERY, ':-:',  $x//TRANSFORMATION_RULE, ':-:', $x/@exportable/string() )";

		try {
			return this.lookupClient.searchAndMapToClassByConstructor(query, MDFInfo.class, ":-:");
		} catch (final ISLookUpException e) {
			throw new OaiPublisherRuntimeException(e);
		}

	}

	@Override
	public String getIdScheme() {
		try {
			return this.lookupClient.getResourceProfileByQuery(getXpathToIdScheme());
		} catch (final ISLookUpException e) {
			throw new OaiPublisherRuntimeException(e);
		}
	}

	@Override
	public String getIdNamespace() {
		try {
			return this.lookupClient.getResourceProfileByQuery(getXpathToIdNamespace());
		} catch (final ISLookUpException e) {
			throw new OaiPublisherRuntimeException(e);
		}
	}

	public ISLookUpClient getLookupClient() {
		return lookupClient;
	}

	public void setLookupClient(final ISLookUpClient lookupClient) {
		this.lookupClient = lookupClient;
	}

	public String getXpathToIdScheme() {
		return this.xpathToIdScheme;
	}

	public void setXpathToIdScheme(final String xpathToIdScheme) {
		this.xpathToIdScheme = xpathToIdScheme;
	}

	public String getXpathToIdNamespace() {
		return this.xpathToIdNamespace;
	}

	public void setXpathToIdNamespace(final String xpathToIdNamespace) {
		this.xpathToIdNamespace = xpathToIdNamespace;
	}

}
