package eu.dnetlib.data.collector.plugins.schemaorg;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.StringWriter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

public class DatasetDocument {
	private List<Identifier> identifiers;
	private List<Creator> creators;
	private List<String> titles;
	private List<String> alternativeTitles;
	private List<String> publishers;
	private List<LocalDate> publicationDates;
	private List<String> subjects;
	private List<Contributor> contributors;
	private List<LocalDate> createdDates;
	private List<LocalDate> updatedDates;
	private List<String> languages;
	private List<ResourceType> resourceTypes;
	private List<AlternateIdentifier> alternateIdentifier;
	private List<Citation> citations;
	private List<String> sizes;
	private List<String> format;
	private List<String> version;
	private List<License> licenses;
	private List<String> descriptions;
	private List<String> disambiguatingDescriptions;
	private List<SpatialCoverage> geoLocations;

	public List<Identifier> getIdentifiers() {
		return identifiers;
	}

	public void setIdentifiers(List<Identifier> identifiers) {
		this.identifiers = identifiers;
	}

	public List<Creator> getCreators() {
		return creators;
	}

	public void setCreators(List<Creator> creators) {
		this.creators = creators;
	}

	public List<String> getTitles() {
		return titles;
	}

	public void setTitles(List<String> titles) {
		this.titles = titles;
	}

	public List<String> getAlternativeTitles() {
		return alternativeTitles;
	}

	public void setAlternativeTitles(List<String> alternativeTitles) {
		this.alternativeTitles = alternativeTitles;
	}

	public List<String> getPublishers() {
		return publishers;
	}

	public void setPublishers(List<String> publishers) {
		this.publishers = publishers;
	}

	public List<LocalDate> getPublicationDates() {
		return publicationDates;
	}

	public void setPublicationDates(List<LocalDate> publicationDates) {
		this.publicationDates = publicationDates;
	}

	public List<String> getSubjects() {
		return subjects;
	}

	public void setSubjects(List<String> subjects) {
		this.subjects = subjects;
	}

	public List<Contributor> getContributors() {
		return contributors;
	}

	public void setContributors(List<Contributor> contributors) {
		this.contributors = contributors;
	}

	public List<LocalDate> getCreatedDates() {
		return createdDates;
	}

	public void setCreatedDates(List<LocalDate> createdDates) {
		this.createdDates = createdDates;
	}

	public List<LocalDate> getUpdatedDates() {
		return updatedDates;
	}

	public void setUpdatedDates(List<LocalDate> updatedDates) {
		this.updatedDates = updatedDates;
	}

	public List<String> getLanguages() {
		return languages;
	}

	public void setLanguages(List<String> languages) {
		this.languages = languages;
	}

	public List<ResourceType> getResourceTypes() {
		return resourceTypes;
	}

	public void setResourceTypes(List<ResourceType> resourceTypes) {
		this.resourceTypes = resourceTypes;
	}

	public List<AlternateIdentifier> getAlternateIdentifier() {
		return alternateIdentifier;
	}

	public void setAlternateIdentifier(List<AlternateIdentifier> alternateIdentifier) {
		this.alternateIdentifier = alternateIdentifier;
	}

	public List<Citation> getCitations() {
		return citations;
	}

	public void setCitations(List<Citation> citations) {
		this.citations = citations;
	}

	public List<String> getSizes() {
		return sizes;
	}

	public void setSizes(List<String> sizes) {
		this.sizes = sizes;
	}

	public List<String> getFormat() {
		return format;
	}

	public void setFormat(List<String> format) {
		this.format = format;
	}

	public List<String> getVersion() {
		return version;
	}

	public void setVersion(List<String> version) {
		this.version = version;
	}

	public List<License> getLicenses() {
		return licenses;
	}

	public void setLicenses(List<License> licenses) {
		this.licenses = licenses;
	}

	public List<String> getDescriptions() {
		return descriptions;
	}

	public void setDescriptions(List<String> descriptions) {
		this.descriptions = descriptions;
	}

	public List<String> getDisambiguatingDescriptions() {
		return disambiguatingDescriptions;
	}

	public void setDisambiguatingDescriptions(List<String> disambiguatingDescriptions) {
		this.disambiguatingDescriptions = disambiguatingDescriptions;
	}

	public List<SpatialCoverage> getGeoLocations() {
		return geoLocations;
	}

	public void setGeoLocations(List<SpatialCoverage> geoLocations) {
		this.geoLocations = geoLocations;
	}

	private  static String emptyXml;
	private  static Object lockEmptyXml = new Object();
	public static String emptyXml() {
		if(DatasetDocument.emptyXml!=null) return DatasetDocument.emptyXml;

		String xml = null;
		try {
			DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
			Document doc = docBuilder.newDocument();

			Element root = doc.createElement("dataset");
			doc.appendChild(root);

			TransformerFactory tf = TransformerFactory.newInstance();
			Transformer transformer = tf.newTransformer();
			StringWriter writer = new StringWriter();
			transformer.transform(new DOMSource(doc), new StreamResult(writer));
			xml = writer.getBuffer().toString();
		}catch(Exception ex){
			xml = "<dataset/>";
		}

		synchronized (DatasetDocument.lockEmptyXml) {
			if (DatasetDocument.emptyXml == null) DatasetDocument.emptyXml = xml;
		}

		return DatasetDocument.emptyXml;
	}

	public String toXml() throws Exception {
		DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
		Document doc = docBuilder.newDocument();

		Element root = doc.createElement("dataset");
		doc.appendChild(root);

		if(this.identifiers!=null){
			for(Identifier item : this.identifiers){
				item.toXml(root);
			}
		}
		if(this.creators!=null){
			Element creators = doc.createElement("creators");
			root.appendChild(creators);
			for(Creator item : this.creators){
				item.toXml(creators);
			}
		}
		if(this.titles!=null || this.alternativeTitles!=null){
			Element titles = doc.createElement("titles");
			root.appendChild(titles);
			if(this.titles!=null) {
				for (String item : this.titles) {
					Element title = doc.createElement("title");
					titles.appendChild(title);
					title.appendChild(doc.createTextNode(item));
				}
			}
			if(this.alternativeTitles!=null) {
				for (String item : this.alternativeTitles) {
					Element title = doc.createElement("title");
					titles.appendChild(title);
					title.setAttribute("titleType", "AlternativeTitle");
					title.appendChild(doc.createTextNode(item));
				}
			}
		}
		if(this.publishers!=null){
			for(String item : this.publishers){
				Element publisher = doc.createElement("publisher");
				root.appendChild(publisher);
				publisher.appendChild(doc.createTextNode(item));
			}
		}
		if(this.publicationDates!=null){
			for(LocalDate item : this.publicationDates){
				Element publicationYear = doc.createElement("publicationYear");
				root.appendChild(publicationYear);
				publicationYear.appendChild(doc.createTextNode(Integer.toString(item.getYear())));
			}
		}
		if(this.subjects!=null){
			Element subjects = doc.createElement("subjects");
			root.appendChild(subjects);
			for(String item : this.subjects){
				Element subject = doc.createElement("subject");
				subjects.appendChild(subject);
				subject.appendChild(doc.createTextNode(item));
			}
		}
		if(this.contributors!=null){
			for(Contributor item : this.contributors){
				item.toXml(root);
			}
		}
		if(this.createdDates!=null || this.updatedDates!=null){
			Element dates = doc.createElement("dates");
			root.appendChild(dates);

			DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYY-MM-DD");

			if(createdDates!=null) {
				for (LocalDate item : this.createdDates) {
					Element date = doc.createElement("date");
					root.appendChild(date);
					date.setAttribute("dateType", "Created");
					date.appendChild(doc.createTextNode(item.format(formatter)));
				}
			}
			if(updatedDates!=null) {
				for (LocalDate item : this.updatedDates) {
					Element date = doc.createElement("date");
					root.appendChild(date);
					date.setAttribute("dateType", "Updated");
					date.appendChild(doc.createTextNode(item.format(formatter)));
				}
			}
		}
		if(this.languages!=null){
			for(String item : this.languages){
				Element language = doc.createElement("language");
				root.appendChild(language);
				language.appendChild(doc.createTextNode(item));
			}
		}
		if(this.resourceTypes!=null){
			for(ResourceType item : this.resourceTypes){
				item.toXml(root);
			}
		}
		if(this.alternateIdentifier!=null){
			Element alternateIdentifiers = doc.createElement("alternateIdentifiers");
			root.appendChild(alternateIdentifiers);
			for(AlternateIdentifier item : this.alternateIdentifier){
				item.toXml(alternateIdentifiers);
			}
		}
		if(this.citations!=null){
			for(Citation item : this.citations){
				item.toXml(root);
			}
		}
		if(this.sizes!=null){
			Element sizes = doc.createElement("sizes");
			root.appendChild(sizes);
			for(String item : this.sizes){
				Element size = doc.createElement("size");
				sizes.appendChild(size);
				size.appendChild(doc.createTextNode(item));
			}
		}
		if(this.format!=null){
			Element formats = doc.createElement("formats");
			root.appendChild(formats);
			for(String item : this.format){
				Element format = doc.createElement("format");
				formats.appendChild(format);
				format.appendChild(doc.createTextNode(item));
			}
		}
		if(this.version!=null){
			for(String item : this.version){
				Element version = doc.createElement("version");
				root.appendChild(version);
				version.appendChild(doc.createTextNode(item));
			}
		}
		if(this.licenses!=null){
			Element rightsList = doc.createElement("rightsList");
			root.appendChild(rightsList);
			for(License item : this.licenses){
				item.toXml(rightsList);
			}
		}
		if(this.descriptions!=null || this.disambiguatingDescriptions!=null){
			Element descriptions = doc.createElement("descriptions");
			root.appendChild(descriptions);
			if(this.descriptions!=null) {
				for (String item : this.descriptions) {
					Element description = doc.createElement("description");
					descriptions.appendChild(description);
					description.setAttribute("descriptionType", "Abstract");
					description.appendChild(doc.createTextNode(item));
				}
			}
			if(this.disambiguatingDescriptions!=null) {
				for (String item : this.disambiguatingDescriptions) {
					Element description = doc.createElement("description");
					descriptions.appendChild(description);
					description.setAttribute("descriptionType", "Other");
					description.appendChild(doc.createTextNode(item));
				}
			}
		}
		if(this.geoLocations!=null){
			Element geoLocations = doc.createElement("geoLocations");
			root.appendChild(geoLocations);
			for(SpatialCoverage item : this.geoLocations){
				item.toXml(geoLocations);
			}
		}

		TransformerFactory tf = TransformerFactory.newInstance();
		Transformer transformer = tf.newTransformer();
		StringWriter writer = new StringWriter();
		transformer.transform(new DOMSource(doc), new StreamResult(writer));
		String xml = writer.getBuffer().toString();
		return xml;
	}

	public static class SpatialCoverage{
		public static class Point{
			public String latitude;
			public String longitude;

			public Point() {}

			public Point(String latitude, String longitude){
				this.latitude = latitude;
				this.longitude = longitude;
			}
		}
		public String name;
		public List<Point> points;
		public List<String> boxes;

		public SpatialCoverage() {}

		public SpatialCoverage(String name, List<Point> points, List<String> boxes ) {
			this.name = name;
			this.points = points;
			this.boxes = boxes;
		}

		public void toXml(Element parent){
			Element node = parent.getOwnerDocument().createElement("geoLocation");
			parent.appendChild(node);

			if(this.points!=null) {
				for(Point point : this.points) {
					if(point.latitude == null || point.longitude == null) continue;
					Element geoLocationPoint = parent.getOwnerDocument().createElement("geoLocationPoint");
					geoLocationPoint.appendChild(parent.getOwnerDocument().createTextNode(String.format("%s %s", point.latitude, point.longitude)));
					node.appendChild(geoLocationPoint);
				}
			}
			if(this.boxes!=null) {
				for(String box : this.boxes) {
					if(box == null) continue;
					Element geoLocationBox = parent.getOwnerDocument().createElement("geoLocationBox");
					geoLocationBox.appendChild(parent.getOwnerDocument().createTextNode(box));
					node.appendChild(geoLocationBox);
				}
			}
			if(this.name!=null) {
				Element geoLocationPlace = parent.getOwnerDocument().createElement("geoLocationPlace");
				geoLocationPlace.appendChild(parent.getOwnerDocument().createTextNode(this.name));
				node.appendChild(geoLocationPlace);
			}
		}
	}

	public static class License{
		public String name;
		public String url;

		public License() {}

		public License(String name, String url) {
			this.name = name;
			this.url = url;
		}

		public void toXml(Element parent){
			Element node = parent.getOwnerDocument().createElement("rights");
			parent.appendChild(node);

			if(this.url!=null) {
				node.setAttribute("rightsURI", this.url);
			}
			if(this.name!=null) {
				node.appendChild(parent.getOwnerDocument().createTextNode(this.name));
			}
		}
	}

	public static class Citation{
		public enum CitationIdentifierType{
			ARK, arXiv, bibcode, DOI, EAN13, EISSN, Handle, ISBN, ISSN, ISTC, LISSN, LSID, PMID,
			PURL, UPC, URL, URN
		}

		public CitationIdentifierType type;
		public String value;

		public Citation() {}

		public Citation(String value, CitationIdentifierType type) {
			this.value = value;
			this.type = type;
		}

		public void toXml(Element parent){
			Element node = parent.getOwnerDocument().createElement("relatedIdentifier");
			parent.appendChild(node);

			node.setAttribute("relatedIdentifierType", this.type.toString());
			node.setAttribute("relationType", "Cites");
			node.appendChild(parent.getOwnerDocument().createTextNode(this.value));
		}
	}

	public static class Contributor{
		public enum ContributorType{
			ContactPerson, DataCollector, DataCurator, DataManager, Distributor, Editor, Funder, HostingInstitution,
			Producer, ProjectLeader, ProjectManager, ProjectMember, RegistrationAgency, RegistrationAuthority,
			RelatedPerson, Researcher, ResearchGroup, RightsHolder, Sponsor, Supervisor, WorkPackageLeader, Other
		}

		public String name;
		public List<String> affiliations;
		public ContributorType type;

		public Contributor() {
		}

		public Contributor(String name) {
			this.name = name;
		}

		public Contributor(String name, List<String> affiliations) {
			this.name = name;
			this.affiliations = affiliations;
		}

		public Contributor(String name, List<String> affiliations, ContributorType type) {
			this.name = name;
			this.affiliations = affiliations;
			this.type = type;
		}

		public void toXml(Element parent){
			Element node = parent.getOwnerDocument().createElement("contributor");
			parent.appendChild(node);

			node.setAttribute("contributorType", this.type.toString());

			if(this.name!=null) {
				Element contributorName = parent.getOwnerDocument().createElement("contributorName");
				node.appendChild(contributorName);
				contributorName.appendChild(parent.getOwnerDocument().createTextNode(this.name));
			}
			if(this.affiliations!=null) {
				for(String item : this.affiliations) {
					Element affiliation = parent.getOwnerDocument().createElement("affiliation");
					node.appendChild(affiliation);
					affiliation.appendChild(parent.getOwnerDocument().createTextNode(item));
				}
			}
		}
	}

	public static class AlternateIdentifier{
		public String identifier;
		public String type;

		public AlternateIdentifier() {}

		public AlternateIdentifier(String identifier, String type) {
			this.identifier = identifier;
			this.type = type;
		}

		public void toXml(Element parent){
			Element node = parent.getOwnerDocument().createElement("alternateIdentifier");
			parent.appendChild(node);

			if(this.type!=null) {
				node.setAttribute("alternateIdentifierType", this.type);
			}
			if(this.identifier!=null) {
				node.appendChild(parent.getOwnerDocument().createTextNode(this.identifier));
			}
		}
	}

	public static class ResourceType{
		public enum ResourceTypeGeneralType {
			Audiovisual, Collection, Dataset, Event, Image, InteractiveResource, Model, PhysicalObject, Service,
			Software, Sound, Text, Workflow, Other
		}

		public ResourceTypeGeneralType type;

		public ResourceType() {}

		public ResourceType(ResourceTypeGeneralType type) {
			this.type = type;
		}

		public void toXml(Element parent){
			Element node = parent.getOwnerDocument().createElement("resourceType");
			parent.appendChild(node);

			if(this.type!=null) {
				node.setAttribute("resourceTypeGeneral", this.type.toString());
			}
		}
	}

	public static class Creator {
		public String name;
		public List<String> affiliations;

		public Creator() {
		}

		public Creator(String name) {
			this.name = name;
		}

		public Creator(String name, List<String> affiliations) {
			this.name = name;
			this.affiliations = affiliations;
		}

		public void toXml(Element parent){
			Element node = parent.getOwnerDocument().createElement("creator");
			parent.appendChild(node);

			if(this.name!=null) {
				Element creatorName = parent.getOwnerDocument().createElement("creatorName");
				node.appendChild(creatorName);
				creatorName.appendChild(parent.getOwnerDocument().createTextNode(this.name));
			}
			if(this.affiliations!=null) {
				for(String item : this.affiliations) {
					Element affiliation = parent.getOwnerDocument().createElement("affiliation");
					node.appendChild(affiliation);
					affiliation.appendChild(parent.getOwnerDocument().createTextNode(item));
				}
			}
		}
	}

	public static class Identifier {
		public enum IdentifierType {
			ARK, DOI, Handle, PURL, URN, URL
		}

		public String value;
		public IdentifierType type;

		public Identifier() {
		}

		public Identifier(IdentifierType type, String value) {
			this.type = type;
			this.value = value;
		}

		public void toXml(Element parent){
			Element node = parent.getOwnerDocument().createElement("identifier");
			parent.appendChild(node);

			node.setAttribute("identifierType", this.type.toString());
			if(this.value!=null) {
				node.appendChild(parent.getOwnerDocument().createTextNode(this.value));
			}
		}
	}
}
