package eu.eudat.models.rda.mapper;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.eudat.logic.utilities.helpers.MyStringUtils;
import eu.eudat.logic.utilities.json.JavaToJson;
import eu.eudat.logic.utilities.json.JsonSearcher;
import eu.eudat.models.rda.Distribution;
import eu.eudat.models.rda.License;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public class DistributionRDAMapper {
	private static final Logger logger = LoggerFactory.getLogger(DistributionRDAMapper.class);
	private static final ObjectMapper mapper = new ObjectMapper();

	public static List<Distribution> toRDAList(List<JsonNode> nodes) {
		Map<String, Distribution> rdaMap = new HashMap<>();

		for (JsonNode node: nodes) {
			String rdaProperty = node.get("rdaProperty").asText();
			String rdaValue = node.get("value").asText();
			//if(rdaValue == null || rdaValue.isEmpty()){
			if(rdaValue == null || (rdaValue.isEmpty() && !node.get("value").isArray())){
				continue;
			}
			String key = node.get("numbering").asText();
			if(!key.contains("mult")){
				key = "0";
			}
			else{
				key = "" + key.charAt(4);
			}
			Distribution rda;
			if(rdaMap.containsKey(key)){
				rda = rdaMap.get(key);
			}
			else {
				rda = new Distribution();
				rdaMap.put(key, rda);
			}
			/* Distribution rda = getRelative(rdaMap, node.get("numbering").asText());
			if (!rdaMap.containsValue(rda)) {
				rdaMap.put(node.get("numbering").asText(), rda);
			} */
			for (ExportPropertyName exportPropertyName : ExportPropertyName.values()) {
				if (rdaProperty.contains(exportPropertyName.getName())) {
					switch (exportPropertyName) {
						case ACCESS_URL:
							rda.setAccessUrl(rdaValue);
							rda.setAdditionalProperty(ImportPropertyName.ACCESS_URL.getName(), node.get("id").asText());
							break;
						case AVAILABLE_UNTIL:
							rda.setAvailableUntil(rdaValue);
							rda.setAdditionalProperty(ImportPropertyName.AVAILABLE_UNTIL.getName(), node.get("id").asText());
							break;
						case DOWNLOAD_URL:
							rda.setDownloadUrl(URI.create(rdaValue));
							rda.setAdditionalProperty(ImportPropertyName.DOWNLOAD_URL.getName(), node.get("id").asText());
							break;
						case DESCRIPTION:
							if(!rdaProperty.contains("host")) {
								rda.setDescription(rdaValue);
								rda.setAdditionalProperty(ImportPropertyName.DESCRIPTION.getName(), node.get("id").asText());
							}
							break;
						case DATA_ACCESS:
							rda.setDataAccess(Distribution.DataAccess.fromValue(rdaValue));
							rda.setAdditionalProperty(ImportPropertyName.DATA_ACCESS.getName(), node.get("id").asText());
							break;
						case BYTE_SIZE:
							rda.setByteSize(Integer.parseInt(rdaValue));
							rda.setAdditionalProperty(ImportPropertyName.BYTE_SIZE.getName(), node.get("id").asText());
							break;
						case LICENSE:
							List<JsonNode> licenseNodes = nodes.stream().filter(lnode -> lnode.get("rdaProperty").asText().toLowerCase().contains("license")).collect(Collectors.toList());
							License license = LicenseRDAMapper.toRDA(licenseNodes);
							rda.setLicense(license != null? Collections.singletonList(license): new ArrayList<>());
							break;
						case FORMAT:
							if(node.get("value").isArray()){
								Iterator<JsonNode> iter = node.get("value").elements();
								List<String> formats = new ArrayList<>();
								int i = 1;
								while(iter.hasNext()) {
									JsonNode current = iter.next();
									String format = current.toString();
									try {
										Map<String, String> result = mapper.readValue(format, HashMap.class);
										format = result.get("label");
										formats.add(format);
										rda.setAdditionalProperty("format" + i++, mapper.readTree(current.toString()));
									}
									catch(JsonProcessingException e){
										logger.warn(e.getMessage());
									}
								}
								rda.setFormat(formats);
							}
							else{
								if(rda.getFormat() == null || rda.getFormat().isEmpty()){
									rda.setFormat(new ArrayList<>(Arrays.asList(rdaValue.replace(" ", "").split(","))));
								}
								else{
									rda.getFormat().addAll(Arrays.asList(rdaValue.replace(" ", "").split(",")));
								}
							}
							rda.setAdditionalProperty(ImportPropertyName.FORMAT.getName(), node.get("id").asText());
							break;
						case TITLE:
							if(!rdaProperty.contains("host")) {
								rda.setTitle(rdaValue);
								rda.setAdditionalProperty(ImportPropertyName.TITLE.getName(), node.get("id").asText());
							}
							break;
						case HOST:
							rda.setHost(HostRDAMapper.toRDA(nodes, node.get("numbering").asText()));
							break;
					}
				}
			}
		}
		return rdaMap.values().stream()
				.filter(distro -> distro.getTitle() != null).collect(Collectors.toList());
	}

	public static Map<String, String> toProperties(List<Distribution> rdas) {
		Map<String, String> properties = new HashMap<>();

		rdas.forEach(rda -> {
			rda.getAdditionalProperties().entrySet().forEach(entry -> {
				try {
					ImportPropertyName importPropertyName = ImportPropertyName.fromString(entry.getKey());
					switch (importPropertyName) {
						case ACCESS_URL:
							properties.put(entry.getValue().toString(), rda.getAccessUrl());
							break;
						case TITLE:
							properties.put(entry.getValue().toString(), rda.getTitle());
							break;
						case DESCRIPTION:
							properties.put(entry.getValue().toString(), rda.getDescription());
							break;
						case FORMAT:
							properties.put(entry.getValue().toString(), rda.getFormat().get(0));
							break;
						case BYTE_SIZE:
							properties.put(entry.getValue().toString(), rda.getByteSize().toString());
							break;
						case DATA_ACCESS:
							properties.put(entry.getValue().toString(), rda.getDataAccess().value());
							break;
						case DOWNLOAD_URL:
							properties.put(entry.getValue().toString(), rda.getDownloadUrl().toString());
							break;
						case AVAILABLE_UNTIL:
							properties.put(entry.getValue().toString(), rda.getAvailableUntil());
							break;
					}
				} catch (Exception e) {
					logger.error(e.getMessage(), e);
				}

			});
			if (rda.getHost() != null) {
				properties.putAll(HostRDAMapper.toProperties(rda.getHost()));
			}
			if (rda.getLicense() != null && !rda.getLicense().isEmpty()) {
				properties.putAll(LicenseRDAMapper.toProperties(rda.getLicense()));
			}
		});

		return properties;
	}

	public static Map<String, String> toProperties(Distribution rda, JsonNode root) {
		Map<String, String> properties = new HashMap<>();

		List<JsonNode> distributionNodes = JsonSearcher.findNodes(root, "rdaProperty", "dataset.distribution");

		for (JsonNode distributionNode: distributionNodes) {
			for (ExportPropertyName exportPropertyName: ExportPropertyName.values()) {
				if (distributionNode.get("rdaProperty").asText().contains(exportPropertyName.getName())) {
					switch (exportPropertyName) {
						case ACCESS_URL:
							properties.put(distributionNode.get("id").asText(), rda.getAccessUrl());
							break;
						case DESCRIPTION:
							properties.put(distributionNode.get("id").asText(), rda.getDescription());
							break;
						case TITLE:
							properties.put(distributionNode.get("id").asText(), rda.getTitle());
							break;
						case AVAILABLE_UNTIL:
							properties.put(distributionNode.get("id").asText(), rda.getAvailableUntil());
							break;
						case DOWNLOAD_URL:
							if (rda.getDownloadUrl() != null) {
								properties.put(distributionNode.get("id").asText(), rda.getDownloadUrl().toString());
							}
							break;
						case DATA_ACCESS:
							properties.put(distributionNode.get("id").asText(), rda.getDataAccess().value());
							break;
						case BYTE_SIZE:
							if (rda.getByteSize() != null) {
								properties.put(distributionNode.get("id").asText(), rda.getByteSize().toString());
							}
							break;
						case FORMAT:
							if (rda.getFormat() != null && !rda.getFormat().isEmpty()) {
								String style = distributionNode.get("viewStyle").get("renderStyle").asText();
								if(style.equals("combobox")) {
									if (distributionNode.get("data").get("type").asText().equals("autocomplete")) {
										Map<String, Object> additionalProperties = rda.getAdditionalProperties();
										List<Object> standardFormats = new ArrayList<>();
										rda.getAdditionalProperties().forEach((key, value) -> {
											try {
												if (key.matches("format\\d+")) {
													standardFormats.add(additionalProperties.get(key));
													properties.put(distributionNode.get("id").asText(), mapper.writeValueAsString(standardFormats));
												}
											} catch (JsonProcessingException e) {
												logger.error(e.getMessage(), e);
											}
										});
									}
								}
								else if(style.equals("freetext")){
									properties.put(distributionNode.get("id").asText(), String.join(", ", rda.getFormat()));
								}
							}
							break;
						case LICENSE:
							if (rda.getLicense() != null && !rda.getLicense().isEmpty()) {
								properties.putAll(LicenseRDAMapper.toProperties(rda.getLicense().get(0), root));
							}
							break;
						case HOST:
							if (rda.getHost() != null) {
								properties.putAll(HostRDAMapper.toProperties(rda.getHost()));
							}
							break;
					}
				}
			}
		}

		return properties;
	}

	public static Distribution toRDA(List<JsonNode> nodes) {
		Distribution rda = new Distribution();
		for (JsonNode node: nodes) {
			String rdaProperty = node.get("rdaProperty").asText();
			String rdaValue = node.get("value").asText();
			for (ExportPropertyName exportPropertyName: ExportPropertyName.values()) {
				if (rdaProperty.contains(exportPropertyName.getName())) {
					switch (exportPropertyName) {
						case ACCESS_URL:
							rda.setAccessUrl(rdaValue);
							break;
						case DESCRIPTION:
							rda.setDescription(rdaValue);
							break;
						case TITLE:
							rda.setTitle(rdaValue);
							break;
						case AVAILABLE_UNTIL:
							rda.setAvailableUntil(rdaValue);
							break;
						case DOWNLOAD_URL:
							rda.setDownloadUrl(URI.create(rdaValue));
							break;
						case DATA_ACCESS:
							rda.setDataAccess(Distribution.DataAccess.fromValue(rdaValue));
							break;
						case BYTE_SIZE:
							rda.setByteSize(Integer.parseInt(rdaValue));
							break;
						case FORMAT:
							rda.setFormat(Collections.singletonList(rdaValue));
							break;
						case LICENSE:
							List<JsonNode> licenseNodes = nodes.stream().filter(lnode -> lnode.get("rdaProperty").asText().toLowerCase().contains("license")).collect(Collectors.toList());
							rda.setLicense(Collections.singletonList(LicenseRDAMapper.toRDA(licenseNodes)));
							break;
						case HOST:
							List<JsonNode> hostNodes = nodes.stream().filter(lnode -> lnode.get("rdaProperty").asText().toLowerCase().contains("host")).collect(Collectors.toList());
							rda.setHost(HostRDAMapper.toRDA(hostNodes, "0"));
							break;
					}
				}
			}
			/*if (rdaProperty.contains("access_url")) {
				rda.setAccessUrl(rdaValue);
			} else if (rdaProperty.contains("available_util")) {
				rda.setAvailableUntil(rdaValue);
			} else if (rdaProperty.contains("byte_size")) {
				rda.setByteSize(Integer.parseInt(rdaValue));
			} else if (rdaProperty.contains("data_access")) {
				rda.setDataAccess(Distribution.DataAccess.fromValue(rdaValue));
			} else if (rdaProperty.contains("description")) {
				rda.setDescription(rdaValue);
			} else if (rdaProperty.contains("download_url")) {
				rda.setDownloadUrl(URI.create(rdaValue));
			} else if (rdaProperty.contains("format")) {
				rda.setFormat(Collections.singletonList(rdaValue));
			} else if (rdaProperty.contains("host")) {
//			rda.setHost(HostRDAMapper.toRDA(node));
			} else if (rdaProperty.contains("license")) {
				rda.setLicense(Collections.singletonList(LicenseRDAMapper.toRDA(node)));
			} else if (rdaProperty.contains("title")) {
				rda.setTitle(rdaValue);
			}*/

		}
		
		if (rda.getTitle() == null) {
			throw new IllegalArgumentException("Distribution title is missing");
		}

		if (rda.getDataAccess() == null) {
			throw new IllegalArgumentException("Distribution Data Access is missing");
		}

		return rda;
	}

	private static Distribution getRelative( Map<String, Distribution> rdaMap, String numbering) {
		return rdaMap.entrySet().stream().filter(entry -> MyStringUtils.getFirstDifference(entry.getKey(), numbering) > 0)
							  .max(Comparator.comparingInt(entry -> MyStringUtils.getFirstDifference(entry.getKey(), numbering))).map(Map.Entry::getValue).orElse(new Distribution());
	}

	private enum ExportPropertyName {
		ACCESS_URL("access_url"),
		AVAILABLE_UNTIL("available_until"),
		BYTE_SIZE("byte_size"),
		DATA_ACCESS("data_access"),
		DESCRIPTION("description"),
		DOWNLOAD_URL("download_url"),
		FORMAT("format"),
		HOST("host"),
		LICENSE("license"),
		TITLE("title");

		private final String name;

		ExportPropertyName(String name) {
			this.name = name;
		}

		public String getName() {
			return name;
		}
	}

	private enum ImportPropertyName {
		ACCESS_URL("accessurlId"),
		AVAILABLE_UNTIL("availableUtilId"),
		BYTE_SIZE("byteSizeId"),
		DATA_ACCESS("dataAccessId"),
		DESCRIPTION("descriptionId"),
		DOWNLOAD_URL("downloadUrlId"),
		FORMAT("formatId"),
		/*HOST("host"),
		LICENSE("license"),*/
		TITLE("titleId");

		private final String name;

		ImportPropertyName(String name) {
			this.name = name;
		}

		public String getName() {
			return name;
		}

		public static ImportPropertyName fromString(String name) throws Exception {
			for (ImportPropertyName importPropertyName: ImportPropertyName.values()) {
				if (importPropertyName.getName().equals(name)) {
					return importPropertyName;
				}
			}
			throw new Exception("No name available");
		}
	}
}
