package eu.eudat.models.data.user.components.datasetprofile;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.eudat.models.data.components.commons.DefaultValue;
import eu.eudat.models.data.components.commons.Multiplicity;
import eu.eudat.models.data.components.commons.ViewStyle;
import eu.eudat.models.data.components.commons.Visibility;
import eu.eudat.models.data.properties.PropertiesGenerator;
import eu.eudat.models.data.user.composite.PropertiesModelBuilder;
import eu.eudat.logic.utilities.interfaces.ViewStyleDefinition;
import eu.eudat.logic.utilities.builders.ModelBuilder;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Field implements Comparable, PropertiesModelBuilder, ViewStyleDefinition<eu.eudat.models.data.entities.xmlmodels.datasetprofiledefinition.Field>, PropertiesGenerator {
    private static final Logger logger = LoggerFactory.getLogger(Field.class);
    private String id;
    private Integer ordinal;
    private Object value;
    private ViewStyle viewStyle;
    private String datatype;
    private String numbering;
    private int page;
    private DefaultValue defaultValue;
    private Multiplicity multiplicity;
    private Object data;
    private List<Field> multiplicityItems;
    private List<eu.eudat.models.data.admin.components.datasetprofile.Field.ValidationType> validations;
    private Visibility visible;
    private String rdaProperty;

    private Boolean export;

    public List<Field> getMultiplicityItems() {
        return multiplicityItems;
    }

    public void setMultiplicityItems(List<Field> multiplicityItems) {
        this.multiplicityItems = multiplicityItems;
    }

    public void setOrdinal(Integer ordinal) {
        this.ordinal = ordinal;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Integer getOrdinal() {
        return ordinal;
    }

    public void setOrdinal(int ordinal) {
        this.ordinal = ordinal;
    }

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }

    public ViewStyle getViewStyle() {
        return viewStyle;
    }

    public void setViewStyle(ViewStyle viewStyle) {
        this.viewStyle = viewStyle;
    }

    public int getPage() {
        return page;
    }

    public void setPage(int page) {
        this.page = page;
    }

    public DefaultValue getDefaultValue() {
        return defaultValue;
    }

    public void setDefaultValue(DefaultValue defaultValue) {
        this.defaultValue = defaultValue;
    }

    public String getDatatype() {
        return datatype;
    }

    public void setDatatype(String datatype) {
        this.datatype = datatype;
    }

    public Multiplicity getMultiplicity() {
        return multiplicity;
    }

    public void setMultiplicity(Multiplicity multiplicity) {
        this.multiplicity = multiplicity;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public Visibility getVisible() {
        return visible;
    }

    public void setVisible(Visibility visible) {
        this.visible = visible;
    }

    public List<Integer> getValidations() {
        if(this.validations == null) {
            return null;
        }
        return this.validations.stream().map(item -> (int) item.getValue()).collect(Collectors.toList());
    }

    public void setValidations(List<Integer> validations) {
        this.validations = eu.eudat.models.data.admin.components.datasetprofile.Field.ValidationType.fromIntegers(validations);
    }

    public String getNumbering() {
        return numbering;
    }

    public void setNumbering(String numbering) {
        this.numbering = numbering;
    }

    public String getRdaProperty() {
        return rdaProperty;
    }

    public void setRdaProperty(String rdaProperty) {
        this.rdaProperty = rdaProperty;
    }

    public Boolean getExport() {
        return export;
    }

    public void setExport(Boolean export) {
        this.export = export;
    }

    Field cloneForMultiplicity(String key, Map<String, Object> properties, int index) {
        Field newField = new Field();
        newField.id = key;
        newField.ordinal = this.ordinal;
        newField.value = properties.containsKey(key)? properties.get(key): null;
        newField.viewStyle = this.viewStyle;
        newField.datatype = this.datatype;
        newField.page = this.page;
        newField.defaultValue = this.defaultValue;
        newField.data = this.data;
        newField.validations = this.validations;
        newField.rdaProperty = this.rdaProperty;
        newField.numbering = "mult" + index + "_" + this.numbering;
        newField.export = this.export;
        return newField;
    }

    @Override
    public eu.eudat.models.data.entities.xmlmodels.datasetprofiledefinition.Field toDatabaseDefinition(eu.eudat.models.data.entities.xmlmodels.datasetprofiledefinition.Field field) {
        field.setId(this.id);
        field.setOrdinal(this.ordinal);
        field.setViewStyle(this.viewStyle);
        field.setData(new ModelBuilder().toFieldData(data, this.viewStyle.getRenderStyle()));
        field.setDefaultValue(this.defaultValue);
        field.setVisible(this.visible);
        field.setValidations(this.validations);
        field.setRdaCommonStandard(this.rdaProperty);
        field.setExport(this.export);
        return field;
    }

    @Override
    public void fromDatabaseDefinition(eu.eudat.models.data.entities.xmlmodels.datasetprofiledefinition.Field item) {
        this.id = item.getId();
        this.ordinal = item.getOrdinal();
        this.viewStyle = item.getViewStyle();
        this.numbering = item.getNumbering();
        this.data = item.getData();
        this.defaultValue = item.getDefaultValue();
        this.visible = item.getVisible();
        this.validations = item.getValidations();
        this.rdaProperty = item.getRdaCommonStandard();
        this.export = item.getExport();
    }

    @Override
    public void fromJsonObject(Map<String, Object> properties) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            List<String> stringList = mapper.readValue(properties.get(this.id).toString(), LinkedList.class);
            this.value = stringList;
        } catch (JSONException | NullPointerException | IOException e) {
            try {
                this.value = (String) properties.get(this.id);
            } catch (ClassCastException ce) {
                this.value = properties.get(this.id);
            }
        }
        this.multiplicityItems = new LinkedList<>();
        List<String> compositeKeys = properties.keySet().stream().filter(keys -> keys.startsWith("multiple_" + this.getId())).collect(Collectors.toList());
        int index = 1;
        for (String key : compositeKeys) {
            this.multiplicityItems.add(this.cloneForMultiplicity(key, properties, index));
        }
    }

    @Override
    public int compareTo(Object o) {
        Field comparedField = (Field) o;
        if(this.ordinal != null) {
            return this.ordinal.compareTo(comparedField.getOrdinal());
        } else if (comparedField.getOrdinal() != null) {
            return comparedField.getOrdinal().compareTo(this.ordinal);
        } else {
            return 0;
        }
    }

    @Override
    public void fromJsonObject(Map<String, Object> properties, String path) {
        this.value = (String) properties.get(path);
    }

    @Override
    public void toMap(Map<String, Object> fieldValues) {
        if (this.value != null) {
            if ((this.viewStyle != null && this.viewStyle.getRenderStyle().equals("datasetIdentifier") || this.value instanceof Collection)) {
                ObjectMapper mapper = new ObjectMapper();
                String valueString = null;
                try {
                    valueString = mapper.writeValueAsString(this.value);
                    fieldValues.put(this.id, valueString);
                } catch (JsonProcessingException e) {
                    logger.error(e.getMessage(), e);
                }
            } /*else if (this.value instanceof Collection) {
                Collection valueCollection = (Collection) this.value;
                StringBuilder valueBuilder = new StringBuilder();
                valueBuilder.append("[");
               for (int i = 0; i < valueCollection.size(); i++) {
                   valueBuilder.append("\"").append(valueCollection.toArray()[i]).append("\"");
                   if (i < valueCollection.size() - 1) {
                       valueBuilder.append(", ");
                   }
               }
               valueBuilder.append("]");
               fieldValues.put(this.id, valueBuilder.toString());
            }*/
            else if ((this.viewStyle != null && this.viewStyle.getRenderStyle().equals("upload"))) {
                fieldValues.put(this.id, this.value);
            }
            else {
                fieldValues.put(this.id, this.value.toString());
            }
        } else {
            fieldValues.put(this.id, "");
        }
    }

    @Override
    public void toMap(Map<String, Object> fieldValues, int index) {
        fieldValues.put(this.id, this.value);
    }
}