package com.yacl4j.core.util;

import java.io.InputStream;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;

import yacl4j.repackaged.com.fasterxml.jackson.core.JsonFactory;
import yacl4j.repackaged.com.fasterxml.jackson.core.JsonPointer;
import yacl4j.repackaged.com.fasterxml.jackson.databind.DeserializationFeature;
import yacl4j.repackaged.com.fasterxml.jackson.databind.JsonNode;
import yacl4j.repackaged.com.fasterxml.jackson.databind.ObjectMapper;
import yacl4j.repackaged.com.fasterxml.jackson.databind.node.ObjectNode;
import yacl4j.repackaged.com.fasterxml.jackson.databind.node.TextNode;
import yacl4j.repackaged.com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import yacl4j.repackaged.com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import yacl4j.repackaged.com.fasterxml.jackson.module.mrbean.MrBeanModule;

public class ConfigurationUtils {

	private ConfigurationUtils() { }
	
	private static final ObjectMapper DEFAULT_OBJECT_MAPPER = Yaml.YAML_OBJECT_MAPPER;
	
	private static ObjectMapper objectMapper(JsonFactory jsonFactory) {
		return new ObjectMapper(jsonFactory)
				.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
				.registerModule(new MrBeanModule())
				.registerModule(new Jdk8Module());
	}
	
	public static JsonNode emptyConfiguration() {
		return DEFAULT_OBJECT_MAPPER.createObjectNode();
	}
	
	public static JsonNode fromString(String configuration) {
		try {
			return DEFAULT_OBJECT_MAPPER.readValue(configuration, JsonNode.class);
		} catch (Exception exception) {
			return TextNode.valueOf(configuration);
		}
	}
	
	public static <T> T toValue(JsonNode configuration, Class<T> configurationClass) {
		try {
			return DEFAULT_OBJECT_MAPPER.treeToValue(configuration, configurationClass);
		} catch (Exception exception) {
			throw new IllegalStateException(exception);
		}
	}
	
	public static JsonNode merge(JsonNode mainNode, JsonNode updateNode) {
		Iterator<String> fieldNames = updateNode.fieldNames();
		while (fieldNames.hasNext()) {
			String fieldName = fieldNames.next();
			JsonNode jsonNode = mainNode.get(fieldName);
			if (jsonNode != null && jsonNode.isObject()) {
				merge(jsonNode, updateNode.get(fieldName));
			} else {
				if (mainNode instanceof ObjectNode) {
					JsonNode value = updateNode.get(fieldName);
					((ObjectNode) mainNode).set(fieldName, value);
				}
			}
		}
		return mainNode;
	}
	
	public static class Yaml {
		
		private Yaml() { }
		
		private static final ObjectMapper YAML_OBJECT_MAPPER = objectMapper(new YAMLFactory());
		
		public static JsonNode fromInputStream(InputStream configuration) {
			try {
				// we need to use this ugly syntax because ObjectMapper::readTree throws in case of empty files (https://github.com/FasterXML/jackson-databind/issues/1406)
				return Optional.<JsonNode> ofNullable(YAML_OBJECT_MAPPER.getFactory().createParser(configuration).readValueAsTree())
						.orElseGet(ConfigurationUtils::emptyConfiguration);
			} catch (Exception exception) {
				throw new IllegalStateException(exception);
			}
		}
		
		public static JsonNode fromString(String configuration) {
			try {
				return (configuration.isEmpty()) ?
						emptyConfiguration() :
						YAML_OBJECT_MAPPER.readTree(configuration);
			} catch (Exception exception) {
				throw new IllegalStateException(exception);
			}
		}
		
	}
	
	public static class Json {
		
		private Json() { }
		
		private static final ObjectMapper JSON_OBJECT_MAPPER = objectMapper(null);
		
		public static JsonNode fromInputStream(InputStream configuration) {
			try {
				// we need to use this ugly syntax because ObjectMapper::readTree throws in case of empty files (https://github.com/FasterXML/jackson-databind/issues/1406)
				return Optional.<JsonNode> ofNullable(JSON_OBJECT_MAPPER.getFactory().createParser(configuration).readValueAsTree())
						.orElseGet(ConfigurationUtils::emptyConfiguration);
			} catch (Exception exception) {
				throw new IllegalStateException(exception);
			}
		}
		
		public static JsonNode fromString(String configuration) {
			try {
				return (configuration.isEmpty()) ?
						emptyConfiguration() :
						JSON_OBJECT_MAPPER.readTree(configuration);
			} catch (Exception exception) {
				throw new IllegalStateException(exception);
			}
		}
		
	}
	
	public static class Properties {
		
		private Properties() { }
		
		public static JsonNode fromInputStream(InputStream configuration) {
			return fromProperties(PropertiesUtils.fromInputStream(configuration));
		}
		
		public static JsonNode fromString(String configuration) {
			return fromProperties(PropertiesUtils.fromString(configuration));
		}
		
		public static JsonNode fromMap(Map<String, String> map) {
			return fromProperties(PropertiesUtils.fromMap(map));
		}
		
		public static JsonNode fromProperties(java.util.Properties properties) {
			ObjectNode configuration = (ObjectNode) ConfigurationUtils.emptyConfiguration();
			for (String propertyKey : properties.stringPropertyNames()) {
				JsonPointer propertyKeyAsJsonPointer = JsonPointerUtils.fromProperty(propertyKey);
				addNode(configuration, propertyKeyAsJsonPointer, properties.getProperty(propertyKey));
			}
			return configuration;
		}
		
		private static void addNode(ObjectNode root, JsonPointer keyAsJsonPointer, String valueAsString) {
			ObjectNode currentNode = root;
			for (JsonPointer head : JsonPointerUtils.heads(keyAsJsonPointer)) {
				JsonNode headAsJsonNode = root.at(head);
				if (headAsJsonNode.isMissingNode() || headAsJsonNode.isValueNode()) {
					currentNode = currentNode.putObject(head.last().getMatchingProperty());
				} else {
					currentNode = (ObjectNode) headAsJsonNode;					
				}
			}
			if (!currentNode.has(keyAsJsonPointer.last().getMatchingProperty())) {
				JsonNode propertyValueAsJsonNode = ConfigurationUtils.fromString(valueAsString);
				currentNode.set(keyAsJsonPointer.last().getMatchingProperty(), propertyValueAsJsonNode);
			}
		}
		
	}
	
}

@@ -935,50 +935,53 @@
 tic 
-final ObjectMapper DEFAULT_OBJECT_MAPPER =
+ObjectMapper defaultObjectMapper() %7B%0A%09%09return
  Yam
@@ -1002,16 +1002,19 @@
 MAPPER;%0A
+%09%7D%0A
 %09%0A%09priva
@@ -1036,17 +1036,22 @@
 tMapper 
-o
+buildO
 bjectMap
@@ -1333,37 +1333,37 @@
 %09return 
-DEFAULT_OBJECT_MAPPER
+defaultObjectMapper()
 .createO
@@ -1457,37 +1457,37 @@
 %09return 
-DEFAULT_OBJECT_MAPPER
+defaultObjectMapper()
 .readVal
@@ -1713,29 +1713,29 @@
 urn 
-DEFAULT_OBJECT_MAPPER
+defaultObjectMapper()
 .tre
@@ -2516,33 +2516,38 @@
 OBJECT_MAPPER = 
-o
+buildO
 bjectMapper(new 
@@ -3477,17 +3477,22 @@
 APPER = 
-o
+buildO
 bjectMap

Fixed NullPointerException with IBM SDK 8