package eu.dnetlib.oai.conf;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.stream.StreamSource;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import eu.dnetlib.oai.PublisherField;
import eu.dnetlib.oai.info.SetInfo;
import eu.dnetlib.rmi.provision.MDFInfo;
import eu.dnetlib.rmi.provision.OaiPublisherRuntimeException;
import eu.dnetlib.utils.MetadataReference;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Parses an XML document representing the OAI configuration profile and creates the corresponding OAIConfiguration object.
 *
 * @author alessia
 */
public class OAIConfigurationParser {

	private static final Log log = LogFactory.getLog(OAIConfigurationParser.class); // NOPMD by marko on 11/24/08 5:02 PM

	private final ThreadLocal<XMLInputFactory> factory = new ThreadLocal<XMLInputFactory>() {

		@Override
		protected XMLInputFactory initialValue() {
			return XMLInputFactory.newInstance();
		}
	};

	public OAIConfiguration getConfiguration(final String configurationProfile) throws IOException {
		log.debug(configurationProfile);
		final OAIConfiguration config = new OAIConfiguration();
		final Map<String, SetInfo> setsMap = new HashMap<>();
		final Map<String, MDFInfo> mdFormatsMap = new HashMap<>();
		final List<String> indexNames = new ArrayList<>();
		final List<PublisherField> fields = new ArrayList<>();
		try {
			final XMLStreamReader parser = this.factory.get().createXMLStreamReader(new StreamSource(new StringReader(configurationProfile)));
			while (parser.hasNext()) {
				int event = parser.next();
				if (event == XMLStreamConstants.START_ELEMENT) {
					final String localName = parser.getLocalName();
					if (localName.equals("IDSCHEME")) {
						config.setIdScheme(parser.getElementText());
					} else if (localName.equals("IDNAMESPACE")) {
						config.setIdNamespace(parser.getElementText());
					} else if (localName.equals("OAISET")) {
						boolean inSet = true;
						final SetInfo setInfo = new SetInfo();
						final String enabled = parser.getAttributeValue(null, "enabled");
						setInfo.setEnabled(Boolean.parseBoolean(enabled));
						while (parser.hasNext() && inSet) {
							event = parser.next();
							if (event == XMLStreamConstants.START_ELEMENT) {
								final String setElementName = parser.getLocalName();
								final String setElementValue = parser.getElementText();
								this.handleSetInfo(setInfo, setElementName, setElementValue);
							}
							if ((event == XMLStreamConstants.END_ELEMENT) && parser.getLocalName().equals("OAISET")) {
								inSet = false;
								setsMap.put(setInfo.getSetSpec(), setInfo);
							}
						}
					} else {
						if (localName.equals("METADATAFORMAT")) {
							boolean inMetadata = true;
							final MDFInfo mdfInfo = new MDFInfo();
							final String exportable = parser.getAttributeValue(null, "exportable");
							mdfInfo.setEnabled(Boolean.parseBoolean(exportable));
							final String mdPrefix = parser.getAttributeValue(null, "metadataPrefix");
							mdfInfo.setPrefix(mdPrefix);
							while (parser.hasNext() && inMetadata) {
								event = parser.next();
								if (event == XMLStreamConstants.START_ELEMENT) {
									final String mdfElementName = parser.getLocalName();
									if (mdfElementName.equals("SOURCE_METADATA_FORMAT")) {
										this.handleSourceMDF(mdfInfo, parser);
										config.getSourcesMDF().add(mdfInfo.getSourceMetadataReference());
									} else {
										final String mdfElementValue = parser.getElementText();
										this.handleMDFInfo(mdfInfo, mdfElementName, mdfElementValue);
									}
								}
								if ((event == XMLStreamConstants.END_ELEMENT) && parser.getLocalName().equals("METADATAFORMAT")) {
									inMetadata = false;
									mdFormatsMap.put(mdPrefix, mdfInfo);
								}
							}
						} else {
							// INDICES
							if (localName.equals("INDEX")) {
								boolean inIndex = true;
								final PublisherField publisherField = new PublisherField();
								final String indexName = parser.getAttributeValue(null, "name");
								final String repeatable = parser.getAttributeValue(null, "repeatable");
								final boolean isRepeatable = Boolean.valueOf(repeatable);
								indexNames.add(indexName);
								publisherField.setFieldName(indexName);
								publisherField.setRepeatable(isRepeatable);
								// now let's set the SOURCES
								final Multimap<String, String> fieldSources = ArrayListMultimap.create();
								while (parser.hasNext() && inIndex) {
									event = parser.next();
									if (event == XMLStreamConstants.START_ELEMENT) {
										final String currentElementName = parser.getLocalName();
										this.handleIndex(fieldSources, indexName, parser, currentElementName);
									}
									if ((event == XMLStreamConstants.END_ELEMENT) && parser.getLocalName().equals("INDEX")) {
										inIndex = false;
									}
								}
								publisherField.setSources(fieldSources);
								fields.add(publisherField);

							}
						}
					}
				}
			}
			config.setFields(fields);
			config.setFieldNames(indexNames);
			config.setMdFormatsMap(mdFormatsMap);
			config.setSetsMap(setsMap);
			return config;
		} catch (final XMLStreamException e) {
			throw new OaiPublisherRuntimeException(e);
		}

	}

	/**
	 * Sets information about the indices.
	 *
	 * @param fieldSources
	 * @param indexName
	 * @param parser
	 * @param currentLocalName
	 */
	private void handleIndex(final Multimap<String, String> fieldSources, final String indexName, final XMLStreamReader parser, final String currentLocalName) {
		if (currentLocalName.equals("SOURCE")) {
			final MDFInfo indexSource = new MDFInfo();
			this.handleSourceMDF(indexSource, parser);
			final String key =
					indexSource.getSourceFormat() + "-" + indexSource.getSourceLayout() + "-" + indexSource.getSourceInterpretation();
			fieldSources.put(key, parser.getAttributeValue(null, "path"));
		} else {
			log.warn("I do not know how to handle INDEX element with name " + currentLocalName);
		}
	}

	/**
	 * Sets information about the source metadata format in the given instance of MDFInfo.
	 *
	 * @param mdfInfo
	 * @param parser
	 */
	private void handleSourceMDF(final MDFInfo mdfInfo, final XMLStreamReader parser) {
		final String interpretation = parser.getAttributeValue(null, "interpretation");
		final String layout = parser.getAttributeValue(null, "layout");
		final String name = parser.getAttributeValue(null, "name");
		MetadataReference mdref = new MetadataReference(name, layout, interpretation);
		mdfInfo.setSourceMetadataReference(mdref);
	}

	/**
	 * Sets information in the MDFInfo instance based on the element name.
	 *
	 * @param mdfInfo
	 * @param elementValue
	 * @param elementName
	 */
	private void handleMDFInfo(final MDFInfo mdfInfo, final String elementName, final String elementValue) {
		if (elementName.equals("NAMESPACE")) {
			mdfInfo.setNamespace(elementValue);
		} else {
			if (elementName.equals("SCHEMA")) {
				mdfInfo.setSchema(elementValue);
			} else {
				if (elementName.equals("TRANSFORMATION_RULE")) {
					mdfInfo.setTransformationRuleID(elementValue);
				} else {
					if (elementName.equals("BASE_QUERY")) {
						mdfInfo.setBaseQuery(elementValue);
					} else {
						log.warn("I do not know how to handle Metadata Format element with name " + elementName + " and value " + elementValue);
					}
				}
			}
		}

	}

	/**
	 * Sets information in the SetInfo instance based on the element name.
	 *
	 * @param setInfo
	 * @param elementName
	 * @param elementValue
	 */
	private void handleSetInfo(final SetInfo setInfo, final String elementName, final String elementValue) {
		if (elementName.equals("spec")) {
			setInfo.setSetSpec(elementValue);
		} else {
			if (elementName.equals("name")) {
				setInfo.setSetName(elementValue);
			} else {
				if (elementName.equals("description")) {
					setInfo.setSetDescription(elementValue);
				} else {
					if (elementName.equals("query")) {
						setInfo.setQuery(elementValue);
					} else {
						log.warn("I do not know how to handle Set element with name " + elementName + " and value " + elementValue);
					}
				}
			}
		}
	}
}
