package eu.eudat.models.data.dmp;

import eu.eudat.data.entities.*;
import eu.eudat.logic.utilities.builders.XmlBuilder;
import eu.eudat.models.DataModel;
import eu.eudat.models.data.datasetwizard.DatasetWizardModel;
import eu.eudat.models.data.doi.Doi;
import eu.eudat.models.data.dynamicfields.DynamicFieldWithValue;
import eu.eudat.models.data.entities.xmlmodels.dmpprofiledefinition.DataManagementPlanProfile;
import eu.eudat.models.data.funder.Funder;
import eu.eudat.models.data.helpermodels.Tuple;
import eu.eudat.models.data.listingmodels.DatasetListingModel;
import eu.eudat.models.data.listingmodels.UserInfoListingModel;
import eu.eudat.models.data.grant.Grant;
import eu.eudat.models.data.project.Project;
import eu.eudat.models.data.userinfo.UserListingModel;
import net.minidev.json.JSONObject;

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

public class DataManagementPlan implements DataModel<DMP, DataManagementPlan> {
    private UUID id;
    private String label;
    private UUID groupId;
    private Tuple<UUID, String> profile;
    private int version;
    private int status;
    private boolean lockable;
    private String description;
    private List<DatasetWizardModel> datasets;
    private List<AssociatedProfile> profiles;
    private Grant grant;
    private List<Organisation> organisations;
    private List<Researcher> researchers;
    private List<UserListingModel> associatedUsers;
    private DataManagementPlanProfile definition;
    private eu.eudat.models.data.userinfo.UserInfo creator;
    private Date modified;
    private Date created;
    private List<DynamicFieldWithValue> dynamicFields;
    private Map<String, Object> properties;
    private List<UserInfoListingModel> users;
    private String doi;
    private List<Doi> dois;
    private Project project;
    private Funder funder;
    private Boolean isPublic;
    private Map<String, Object> extraProperties;

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

    public Tuple<UUID, String> getProfile() {
        return profile;
    }
    public void setProfile(Tuple<UUID, String> profile) {
        this.profile = profile;
    }

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

    public UUID getGroupId() {
        return groupId;
    }
    public void setGroupId(UUID groupId) {
        this.groupId = groupId;
    }

    public List<UserListingModel> getAssociatedUsers() {
        return associatedUsers;
    }
    public void setAssociatedUsers(List<UserListingModel> associatedUsers) {
        this.associatedUsers = associatedUsers;
    }

    public int getVersion() {
        return version;
    }
    public void setVersion(int version) {
        this.version = version;
    }

    public List<Organisation> getOrganisations() {
        return organisations;
    }
    public void setOrganisations(List<Organisation> organizations) {
        this.organisations = organizations;
    }

    public List<Researcher> getResearchers() {
        return researchers;
    }
    public void setResearchers(List<Researcher> researchers) {
        this.researchers = researchers;
    }

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

    public Grant getGrant() {
        return grant;
    }
    public void setGrant(Grant grant) {
        this.grant = grant;
    }

    public eu.eudat.models.data.userinfo.UserInfo getCreator() {
        return creator;
    }
    public void setCreator(eu.eudat.models.data.userinfo.UserInfo creator) {
        this.creator = creator;
    }

    public List<AssociatedProfile> getProfiles() {
        return profiles;
    }
    public void setProfiles(List<AssociatedProfile> profiles) {
        this.profiles = profiles;
    }

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

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

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

    public DataManagementPlanProfile getDefinition() {
        return definition;
    }
    public void setDefinition(DataManagementPlanProfile definition) {
        this.definition = definition;
    }

    public Map<String, Object> getProperties() {
        return properties;
    }
    public void setProperties(Map<String, Object> properties) {
        this.properties = properties;
    }

    public List<DynamicFieldWithValue> getDynamicFields() {
        return dynamicFields;
    }
    public void setDynamicFields(List<DynamicFieldWithValue> dynamicFields) {
        this.dynamicFields = dynamicFields;
    }

    public boolean getLockable() {
        return lockable;
    }
    public void setLockable(boolean lockable) {
        this.lockable = lockable;
    }

    public List<DatasetWizardModel> getDatasets() {
        return datasets;
    }

    public void setDatasets(List<DatasetWizardModel> datasets) {
        this.datasets = datasets;
    }

    public List<UserInfoListingModel> getUsers() {
        return users;
    }
    public void setUsers(List<UserInfoListingModel> users) {
        this.users = users;
    }

    public String getDoi() {
        return doi;
    }
    public void setDoi(String doi) {
        this.doi = doi;
    }

    public List<Doi> getDois() {
        return dois;
    }
    public void setDois(List<Doi> dois) {
        this.dois = dois;
    }

    public Project getProject() {
        return project;
    }
    public void setProject(Project project) {
        this.project = project;
    }

    public Funder getFunder() {
        return funder;
    }
    public void setFunder(Funder funder) {
        this.funder = funder;
    }

    public Boolean getPublic() {
        return isPublic;
    }

    public void setPublic(Boolean aPublic) {
        isPublic = aPublic;
    }

    public Map<String, Object> getExtraProperties() {
        return extraProperties;
    }

    public void setExtraProperties(Map<String, Object> extraProperties) {
        this.extraProperties = extraProperties;
    }

    @Override
    public DataManagementPlan fromDataModel(DMP entity) {
        this.id = entity.getId();
        this.profile = entity.getProfile() != null ? new Tuple<UUID, String>(entity.getProfile().getId(), entity.getProfile().getLabel()) : null;
        this.organisations = entity.getOrganisations() != null ? entity.getOrganisations().stream().map(item -> new Organisation().fromDataModel(item)).collect(Collectors.toList()) : new ArrayList<>();
        this.researchers = entity.getResearchers() != null ? entity.getResearchers().stream().map(item -> new Researcher().fromDataModel(item)).collect(Collectors.toList()): new ArrayList<>();
        this.version = entity.getVersion();
        this.groupId = this.groupId == null ? null : entity.getGroupId();
        this.label = entity.getLabel();
        this.grant = new Grant();
        this.properties = entity.getProperties() != null ? new org.json.JSONObject(entity.getProperties()).toMap() : null;
        this.grant.fromDataModel(entity.getGrant());
        this.creator = new eu.eudat.models.data.userinfo.UserInfo();
        this.groupId = entity.getGroupId();
        this.lockable = entity.getDataset() != null && entity.getDataset().stream().findAny().isPresent();
        this.definition = entity.getProfile() == null ? null : new DataManagementPlanProfile().fromXml(XmlBuilder.fromXml(entity.getProfile().getDefinition()).getDocumentElement());
        if (this.definition != null && this.definition.getFields() != null && !this.definition.getFields().isEmpty() && this.properties != null) {
            this.definition.getFields().forEach(item -> {
                Optional<Map<String, Object>> fieldOptional = ((List<Map<String, Object>>) this.properties.get("fields")).stream().filter(field -> field.get("id").equals(item.getId().toString())).findFirst();
                if (fieldOptional.isPresent()) item.setValue(fieldOptional.get().get("value"));
            });
        }
        if (entity.getUsers() != null && entity.getUsers().stream().anyMatch(userDMP -> userDMP.getRole().equals(UserDMP.UserDMPRoles.OWNER.getValue())))
            this.creator.fromDataModel(entity.getUsers().stream().filter(user -> user.getRole().equals(UserDMP.UserDMPRoles.OWNER.getValue())).findFirst().get().getUser());

        if (entity.getAssociatedDmps() != null && !entity.getAssociatedDmps().isEmpty()) {
            this.profiles = new LinkedList<>();
            for (DatasetProfile datasetProfile: entity.getAssociatedDmps()) {
                AssociatedProfile associatedProfile = new AssociatedProfile().fromData(datasetProfile);
                this.profiles.add(associatedProfile);
            }
        }
        if (entity.getDataset() != null) {
            if (entity.isPublic()) {
                this.datasets = entity.getDataset().stream()
                                      .filter(dataset -> !dataset.getStatus().equals(Dataset.Status.DELETED.getValue()) && !dataset.getStatus().equals(Dataset.Status.CANCELED.getValue()) && !dataset.getStatus().equals(Dataset.Status.SAVED.getValue()))
                                      .map(x -> new DatasetWizardModel().fromDataModelNoDmp(x)).collect(Collectors.toList());
            } else {
                this.datasets = entity.getDataset().stream()
                                      .filter(dataset -> !dataset.getStatus().equals(Dataset.Status.DELETED.getValue()) && !dataset.getStatus().equals(Dataset.Status.CANCELED.getValue()))
                                      .map(x -> new DatasetWizardModel().fromDataModelNoDmp(x)).collect(Collectors.toList());
            }
        }
        this.modified = entity.getModified();
        this.created = entity.getCreated();
        this.description = entity.getDescription();
        this.status = entity.getStatus();
        this.associatedUsers = entity.getUsers() != null ? entity.getUsers().stream().map(item -> new UserListingModel().fromDataModel(item.getUser())).collect(Collectors.toList()) : new ArrayList<>();
        this.users = entity.getUsers() != null ? entity.getUsers().stream().map(item -> new UserInfoListingModel().fromDataModel(item)).collect(Collectors.toList()) : new ArrayList<>();
        this.doi = entity.getDoi();
        this.dois = entity.getDois() != null ? entity.getDois().stream().map(item -> new Doi().fromDataModel(item)).collect(Collectors.toList()): new ArrayList<>();

        if (entity.getProject() != null) {
            this.project = new Project();
            this.project = new Project().fromDataModel(entity.getProject());
        }

        if (entity.getGrant().getFunder() != null) {
            this.funder = new Funder();
            this.funder.fromDataModel(entity.getGrant().getFunder());
        }
        this.isPublic = entity.isPublic();

        this.extraProperties = entity.getExtraProperties() != null ? new org.json.JSONObject(entity.getExtraProperties()).toMap() : null;

        return this;
    }

    @Override
    public DMP toDataModel() throws Exception {
        DMP dataManagementPlanEntity = new DMP();
        if (this.profile != null) {
            DMPProfile dmpProfile = new DMPProfile();
            dmpProfile.setId(this.profile.getId());
            dataManagementPlanEntity.setProfile(dmpProfile);
        }
        dataManagementPlanEntity.setId(this.id);
        if (this.organisations != null && !this.organisations.isEmpty())
            dataManagementPlanEntity.setOrganisations(new HashSet<>(this.organisations.stream().map(item -> item.toDataModel()).collect(Collectors.toList())));
        if (this.researchers != null && !this.researchers.isEmpty())
            dataManagementPlanEntity.setResearchers(new HashSet<>(this.researchers.stream().map(item -> item.toDataModel()).collect(Collectors.toList())));
        dataManagementPlanEntity.setVersion(this.version);
        dataManagementPlanEntity.setLabel(this.label);
        if (this.grant != null) dataManagementPlanEntity.setGrant(this.grant.toDataModel());
        dataManagementPlanEntity.setStatus((short) this.status);
        dataManagementPlanEntity.setDescription(this.description);
        if (this.project != null) {
            dataManagementPlanEntity.setProject(this.project.toDataModel());
        }
        if (this.profiles != null) {
           Set<DatasetProfile> datasetProfiles = new HashSet<>();
           for (AssociatedProfile profile : this.profiles) {
               datasetProfiles.add(profile.toData());
           }
           dataManagementPlanEntity.setAssociatedDmps(datasetProfiles);
        }
        dataManagementPlanEntity.setProperties(this.properties != null ? JSONObject.toJSONString(this.properties) : null);
        dataManagementPlanEntity.setGroupId(this.groupId != null ? this.groupId : UUID.randomUUID());
        dataManagementPlanEntity.setModified(this.modified != null ? this.modified : new Date());
        dataManagementPlanEntity.setCreated(this.created != null ? this.created : new Date());
        if (this.dynamicFields != null)
            dataManagementPlanEntity.setDmpProperties(JSONObject.toJSONString(this.dynamicFields.stream().filter(item -> item.getValue() != null).collect(Collectors.toMap(DynamicFieldWithValue::getId, DynamicFieldWithValue::getValue))));

        if (this.isPublic != null) {
            dataManagementPlanEntity.setPublic(this.isPublic);
        }

        dataManagementPlanEntity.setExtraProperties(this.extraProperties != null ? JSONObject.toJSONString(this.extraProperties) : null);
        return dataManagementPlanEntity;
    }

    public DataManagementPlan fromDataModelNoDatasets(DMP entity) {
        this.id = entity.getId();
        this.profile = entity.getProfile() != null ? new Tuple<UUID, String>(entity.getProfile().getId(), entity.getProfile().getLabel()) : null;
        this.organisations = entity.getOrganisations() != null ? entity.getOrganisations().stream().map(item -> new Organisation().fromDataModel(item)).collect(Collectors.toList()) : new ArrayList<>();
        this.researchers = entity.getResearchers() != null ? entity.getResearchers().stream().filter(Objects::nonNull).map(item -> new Researcher().fromDataModel(item)).collect(Collectors.toList()) : new ArrayList<>();
        this.version = entity.getVersion();
        this.label = entity.getLabel();
        this.grant = new Grant();
        this.properties = entity.getProperties() != null ? new org.json.JSONObject(entity.getProperties()).toMap() : null;
        this.creator = new eu.eudat.models.data.userinfo.UserInfo();
        this.groupId = entity.getGroupId();
        this.lockable = entity.getDataset() != null && entity.getDataset().stream().findAny().isPresent();
        this.definition = entity.getProfile() == null ? null : new DataManagementPlanProfile().fromXml(XmlBuilder.fromXml(entity.getProfile().getDefinition()).getDocumentElement());
        if (this.definition != null && this.definition.getFields() != null && !this.definition.getFields().isEmpty() && this.properties != null) {
            this.definition.getFields().forEach(item -> {
                Optional<Map<String, Object>> fieldOptional = ((List<Map<String, Object>>) this.properties.get("fields")).stream().filter(field -> field.get("id").equals(item.getId().toString())).findFirst();
                fieldOptional.ifPresent(stringObjectMap -> item.setValue(stringObjectMap.get("value")));
            });
        }
        if (entity.getUsers() != null && entity.getUsers().stream().anyMatch(userDMP -> userDMP.getRole().equals(UserDMP.UserDMPRoles.OWNER.getValue())))
            this.creator.fromDataModel(entity.getUsers().stream().filter(user -> user.getRole().equals(UserDMP.UserDMPRoles.OWNER.getValue())).findFirst().get().getUser());

        if (entity.getAssociatedDmps() != null && !entity.getAssociatedDmps().isEmpty()) {
            this.profiles = new LinkedList<>();
            for (DatasetProfile datasetProfile: entity.getAssociatedDmps()) {
                AssociatedProfile associatedProfile = new AssociatedProfile().fromData(datasetProfile);
                this.profiles.add(associatedProfile);
            }
        }
        this.modified = entity.getModified();
        this.created = entity.getCreated();
        this.description = entity.getDescription();
        this.status = entity.getStatus();
        this.associatedUsers = entity.getUsers() != null ? entity.getUsers().stream().map(item -> new UserListingModel().fromDataModel(item.getUser())).collect(Collectors.toList()) : new ArrayList<>();
        this.users = entity.getUsers() != null ? entity.getUsers().stream().map(item -> new UserInfoListingModel().fromDataModel(item)).collect(Collectors.toList()) : new ArrayList<>();
        this.doi = entity.getDoi();
        this.dois = entity.getDois() != null ? entity.getDois().stream().map(item -> new Doi().fromDataModel(item)).collect(Collectors.toList()): new ArrayList<>();
        this.grant.fromDataModel(entity.getGrant());

        if (entity.getProject() != null) {
            this.project = new Project();
            this.project = new Project().fromDataModel(entity.getProject());
        }
        if (entity.getGrant().getFunder() != null) {
            this.funder = new Funder();
            this.funder.fromDataModel(entity.getGrant().getFunder());
        }
        this.isPublic = entity.isPublic();

        this.extraProperties = entity.getExtraProperties() != null ? new org.json.JSONObject(entity.getExtraProperties()).toMap() : null;

        return this;
    }

    @Override
    public String getHint() {
        return "fullyDetailed";
    }
}
