package eu.eudat.data.entities;


import eu.eudat.data.converters.DateToUTCConverter;
import eu.eudat.data.entities.helpers.EntityBinder;
import eu.eudat.queryable.queryableentity.DataEntity;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Type;

import javax.persistence.*;
import java.util.*;
import java.util.stream.Collectors;

@Entity
@Table(name = "\"Dataset\"")
@NamedEntityGraphs({
		@NamedEntityGraph(
				name = "datasetListingModel",
				attributeNodes = {/*@NamedAttributeNode("services"), @NamedAttributeNode(value = "datasetDataRepositories", subgraph = "datasetDataRepositories"),
						@NamedAttributeNode(value = "datasetExternalDatasets", subgraph = "datasetExternalDatasets"), @NamedAttributeNode("registries"),*/
						@NamedAttributeNode(value = "dmp", subgraph = "dmp"), @NamedAttributeNode(value = "profile", subgraph = "profile"), @NamedAttributeNode("creator")},
				subgraphs = {
						@NamedSubgraph(name = "dmp", attributeNodes = {@NamedAttributeNode("creator"), @NamedAttributeNode("users"), @NamedAttributeNode("grant"), @NamedAttributeNode("organisations")}),
						@NamedSubgraph(name = "datasetDataRepositories", attributeNodes = {@NamedAttributeNode("dataRepository")}),
						@NamedSubgraph(name = "datasetExternalDatasets", attributeNodes = {@NamedAttributeNode("externalDataset")}),
						@NamedSubgraph(name = "profile", attributeNodes = {@NamedAttributeNode("label")})
				}),

		@NamedEntityGraph(
				name = "datasetWizardModel",
				attributeNodes = {@NamedAttributeNode("services"), @NamedAttributeNode("datasetDataRepositories"), @NamedAttributeNode("datasetExternalDatasets"), @NamedAttributeNode("registries"),
						@NamedAttributeNode("dmp"), @NamedAttributeNode("profile"), @NamedAttributeNode("creator")}),
		@NamedEntityGraph(
				name = "datasetRecentActivity",
				attributeNodes = {@NamedAttributeNode(value = "dmp", subgraph = "dmp")},
				subgraphs = @NamedSubgraph(name = "dmp", attributeNodes = {@NamedAttributeNode("users")})),
		@NamedEntityGraph(
				name = "datasetDataRepositories",
				attributeNodes = {@NamedAttributeNode(value = "dmp", subgraph = "dmp"), @NamedAttributeNode("creator")},
				subgraphs = @NamedSubgraph(name = "dmp", attributeNodes = {@NamedAttributeNode("creator"), @NamedAttributeNode("users")})),
		@NamedEntityGraph(
				name = "recentDatasetModel",
				attributeNodes = {@NamedAttributeNode("services"), @NamedAttributeNode(value = "datasetDataRepositories", subgraph = "datasetDataRepositories"),
						@NamedAttributeNode(value = "datasetExternalDatasets", subgraph = "datasetExternalDatasets"), @NamedAttributeNode("registries"),
						@NamedAttributeNode(value = "dmp", subgraph = "dmp"), @NamedAttributeNode("profile"), @NamedAttributeNode("creator")},
				subgraphs = {
						@NamedSubgraph(name = "dmp", attributeNodes = {@NamedAttributeNode("creator"), @NamedAttributeNode("users"), @NamedAttributeNode("grant"), @NamedAttributeNode("organisations")}),
						@NamedSubgraph(name = "datasetDataRepositories", attributeNodes = {@NamedAttributeNode("dataRepository")}),
						@NamedSubgraph(name = "datasetExternalDatasets", attributeNodes = {@NamedAttributeNode("externalDataset")})
				})
})
public class Dataset implements DataEntity<Dataset, UUID> {

	public static Set<String> getHints() {
		return hints;
	}

	private static final Set<String> hints = new HashSet<>(Arrays.asList("datasetListingModel"));

	public enum Status {
		SAVED((short) 0), FINALISED((short) 1), CANCELED((short) 2), DELETED((short) 99),;

		private short value;

		private Status(short value) {
			this.value = value;
		}

		public short getValue() {
			return value;
		}

		public static Status fromInteger(int value) {
			switch (value) {
				case 0:
					return SAVED;
				case 1:
					return FINALISED;
				case 2:
					return CANCELED;
				case 99:
					return DELETED;
				default:
					throw new RuntimeException("Unsupported Dataset Status");
			}
		}
	}

	@Id
	@GeneratedValue
	@GenericGenerator(name = "uuid2", strategy = "uuid2")
	@Column(name = "\"ID\"", updatable = false, nullable = false, columnDefinition = "BINARY(16)")
	private UUID id;

	@Column(name = "\"Label\"", nullable = false)
	private String label;

	@ManyToOne(fetch = FetchType.LAZY)
//	@Cascade(value=org.hibernate.annotations.CascadeType.ALL)
	@JoinColumn(name = "\"DMP\"", nullable = false)
	private DMP dmp;

	@Column(name = "\"Uri\"")
	private String uri;

	@Type(type = "eu.eudat.configurations.typedefinition.XMLType")
	@Column(name = "\"Properties\"", columnDefinition = "xml")
	private String properties;

	@ManyToOne(fetch = FetchType.LAZY)
	//@Cascade(value=org.hibernate.annotations.CascadeType.ALL)
	@JoinColumn(name = "\"Profile\"")
	private DatasetProfile profile;

	@Type(type = "eu.eudat.configurations.typedefinition.XMLType")
	@Column(name = "\"Reference\"", columnDefinition = "xml")
	private String reference;

	@OneToMany(fetch = FetchType.LAZY)
	@JoinTable(name = "\"DatasetRegistry\"",
			joinColumns = {@JoinColumn(name = "\"Dataset\"", referencedColumnName = "\"ID\"")},
			inverseJoinColumns = {@JoinColumn(name = "\"Registry\"", referencedColumnName = "\"ID\"")}
	)
	private Set<Registry> registries;

	@OneToMany(mappedBy = "dataset", cascade = CascadeType.ALL, orphanRemoval = true)
	private Set<DatasetDataRepository> datasetDataRepositories;

	@OneToMany(mappedBy = "dataset", cascade = CascadeType.ALL, orphanRemoval = true)
	private Set<DatasetService> services;

	@OneToMany(mappedBy = "dataset", cascade = CascadeType.ALL, orphanRemoval = true)
	private Set<DatasetExternalDataset> datasetExternalDatasets;

	@Column(name = "\"Status\"", nullable = false)
	private Short status;


	@Column(name = "\"Created\"", nullable = false)
	@Convert(converter = DateToUTCConverter.class)
	private Date created = null;

	@Column(name = "\"Modified\"", nullable = false)
	@Convert(converter = DateToUTCConverter.class)
	private Date modified = new Date();

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "\"Creator\"")
	private UserInfo creator;

	@Column(name = "\"Description\"")
	private String description;

	@Column(name= "\"FinalizedAt\"")
	@Convert(converter = DateToUTCConverter.class)
	private Date finalizedAt;


	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}


	public UserInfo getCreator() {
		return creator;
	}
	public void setCreator(UserInfo creator) {
		this.creator = creator;
	}

	public Short getStatus() {
		return status;
	}
	public void setStatus(Short status) {
		this.status = status;
	}


	public Date getCreated() {
		return created;
	}
	public void setCreated(Date created) {
		this.created = created;
	}


	public Date getModified() {
		return modified;
	}
	public void setModified(Date modified) {
		this.modified = modified;
	}


	public Set<Registry> getRegistries() {
		return registries;
	}
	public void setRegistries(Set<Registry> registries) {
		this.registries = registries;
	}

	public Set<DatasetService> getServices() {
		return services;
	}
	public void setServices(Set<DatasetService> services) {
		this.services = services;
	}

	public UUID getId() {
		return id;
	}
	public void setId(UUID id) {
		this.id = id;
	}


	public String getLabel() {
		return label;
	}
	public void setLabel(String label) {
		this.label = label;
	}


	public DMP getDmp() {
		return dmp;
	}
	public void setDmp(DMP dmp) {
		this.dmp = dmp;
	}


	public String getUri() {
		return uri;
	}
	public void setUri(String uri) {
		this.uri = uri;
	}


	public String getProperties() {
		return properties;
	}
	public void setProperties(String properties) {
		this.properties = properties;
	}


	public DatasetProfile getProfile() {
		return profile;
	}
	public void setProfile(DatasetProfile profile) {
		this.profile = profile;
	}


	public Set<DatasetDataRepository> getDatasetDataRepositories() {
		return datasetDataRepositories;
	}
	public void setDatasetDataRepositories(Set<DatasetDataRepository> datasetDataRepositories) {
		this.datasetDataRepositories = datasetDataRepositories;
	}

	public String getReference() {
		return reference;
	}
	public void setReference(String reference) {
		this.reference = reference;
	}

	public Set<DatasetExternalDataset> getDatasetExternalDatasets() {
		return datasetExternalDatasets;
	}
	public void setDatasetExternalDatasets(Set<DatasetExternalDataset> datasetExternalDatasets) {
		this.datasetExternalDatasets = datasetExternalDatasets;
	}

	public Date getFinalizedAt() {
		return finalizedAt;
	}
	public void setFinalizedAt(Date finalizedAt) {
		this.finalizedAt = finalizedAt;
	}

	@Override
	public void update(Dataset entity) {
		this.setUri(entity.getUri());
		this.setDescription(entity.getDescription());
		this.setLabel(entity.getLabel());
		this.setProperties(entity.getProperties());

		if (entity.getDatasetDataRepositories() == null || entity.getDatasetDataRepositories().size() < 1) {
			if (this.getDatasetDataRepositories() != null) this.getDatasetDataRepositories().removeAll(this.getDatasetDataRepositories());
		} else {
			if (this.getDatasetDataRepositories() != null) {
				this.getDatasetDataRepositories().removeAll(this.getDatasetDataRepositories());
			} else {
				this.setDatasetDataRepositories(new HashSet<>());
			}
			this.getDatasetDataRepositories().addAll(entity.getDatasetDataRepositories().stream().peek(item -> item.setDataset(this)).collect(Collectors.toList()));
		}

		if (entity.getDatasetExternalDatasets() == null || entity.getDatasetExternalDatasets().size() < 1) {
			if (this.getDatasetExternalDatasets() != null) this.getDatasetExternalDatasets().removeAll(this.getDatasetExternalDatasets());
		} else {
			if (this.getDatasetExternalDatasets() != null) {
				this.getDatasetExternalDatasets().removeAll(this.getDatasetExternalDatasets());
			} else {
				this.setDatasetExternalDatasets(new HashSet<>());
			}
			this.getDatasetExternalDatasets().addAll(entity.getDatasetExternalDatasets().stream().peek(item -> item.setDataset(this)).collect(Collectors.toList()));
		}

		if (entity.getServices() == null || entity.getServices().size() < 1) {
			if (this.getServices() != null) this.getServices().removeAll(this.getServices());
		} else {
			if (this.getServices() != null) {
				this.getServices().removeAll(this.getServices());
			} else {
				this.setServices(new HashSet<>());
			}
			this.getServices().addAll(entity.getServices().stream().peek(item -> item.setDataset(this)).collect(Collectors.toList()));
		}

		this.setRegistries(entity.getRegistries());

		this.setDmp(entity.getDmp());
		this.setStatus(entity.getStatus());
		this.setProfile(entity.getProfile());
		this.setModified(new Date());
		if (entity.getStatus().equals(Status.FINALISED.getValue())) this.setFinalizedAt(new Date());
		if (entity.getCreator() != null) this.creator = entity.getCreator();
	}

	@Override
	public UUID getKeys() {
		return this.id;
	}

	@Override
	public Dataset buildFromTuple(List<Tuple> tuple, List<String> fields, String base) {
		String currentBase = base.isEmpty() ? "" : base + ".";
		if (fields.contains(currentBase + "id")) this.id = EntityBinder.fromTuple(tuple, currentBase + "id");
		return this;
	}
}
