package org.cobbzilla.util.yml;

import com.github.mustachejava.DefaultMustacheFactory;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;

import java.io.*;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * (c) Copyright 2013 Jonathan Cobb
 * This code is available under the Apache License, version 2: http://www.apache.org/licenses/LICENSE-2.0.html
 */
public class YmlMerger {

    private static final Logger LOG = LoggerFactory.getLogger(YmlMerger.class);
    public static final DefaultMustacheFactory DEFAULT_MUSTACHE_FACTORY = new DefaultMustacheFactory();

    private final Yaml yaml = new Yaml();
    private final Map<String, Object> scope = new HashMap<String, Object>();;

    public YmlMerger() {
        init(System.getenv());
    }

    public YmlMerger(Map<String, String> env) {
        init(env);
    }

    private void init(Map<String, String> env) {
        for (String varname : env.keySet()) {
            scope.put(varname, env.get(varname));
        }
    }

    public Map<String, Object> merge(String[] files) throws IOException {
        Map<String, Object> mergedResult = new LinkedHashMap<String, Object>();
        for (String file : files) {
            InputStream in = null;
            try {
                // read the file into a String
                in = new FileInputStream(file);
                final String entireFile = IOUtils.toString(in);

                // substitute variables
                final StringWriter writer = new StringWriter(entireFile.length() + 10);
                DEFAULT_MUSTACHE_FACTORY.compile(new StringReader(entireFile), "mergeyml_"+System.currentTimeMillis()).execute(writer, scope);

                // load the YML file
                final Map<String, Object> yamlContents = (Map<String, Object>) yaml.load(writer.toString());

                // merge into results map
                merge_internal(mergedResult, yamlContents);
                LOG.info("loaded YML from "+file+": "+yamlContents);

            } finally {
                if (in != null) in.close();
            }
        }
        return mergedResult;
    }

    private void merge_internal(Map<String, Object> mergedResult, Map<String, Object> yamlContents) {

        for (String key : yamlContents.keySet()) {

            Object yamlValue = yamlContents.get(key);
            if (yamlValue == null) {
                addToMergedResult(mergedResult, key, yamlValue);
                continue;
            }

            Object existingValue = mergedResult.get(key);
            if (existingValue != null) {
                if (yamlValue instanceof Map) {
                    if (existingValue instanceof Map) {
                        merge_internal((Map<String, Object>) existingValue, (Map<String, Object>)  yamlValue);
                    } else if (existingValue instanceof String) {
                        throw new IllegalArgumentException("Cannot merge complex element into a simple element: "+key);
                    } else {
                        throw unknownValueType(key, yamlValue);
                    }

                } else if (yamlValue instanceof String ||
                        yamlValue instanceof Boolean ||
                        yamlValue instanceof Double ||
                        yamlValue instanceof Integer) {
                    LOG.info("overriding value of "+key+" with value "+yamlValue);
                    addToMergedResult(mergedResult, key, yamlValue);

                } else {
                    throw unknownValueType(key, yamlValue);
                }

            } else {
                if (yamlValue instanceof Map ||
                        yamlValue instanceof String ||
                        yamlValue instanceof Boolean||
                        yamlValue instanceof Integer||
                        yamlValue instanceof Double) {
                    LOG.info("adding new key->value: "+key+"->"+yamlValue);
                    addToMergedResult(mergedResult, key, yamlValue);
                } else {
                    throw unknownValueType(key, yamlValue);
                }
            }
        }
    }

    private IllegalArgumentException unknownValueType(String key, Object yamlValue) {
        final String msg = "Cannot merge element of unknown type: " + key + ": " + yamlValue.getClass().getName();
        LOG.error(msg);
        return new IllegalArgumentException(msg);
    }

    private Object addToMergedResult(Map<String, Object> mergedResult, String key, Object yamlValue) {
        return mergedResult.put(key, yamlValue);
    }

    public String mergeToString(String[] files) throws IOException {
        return toString(merge(files));
    }

    public String toString(Map<String, Object> merged) {
        return yaml.dump(merged);
    }

}

@@ -277,32 +277,55 @@
 .LinkedHashMap;%0A
+import java.util.List;%0A
 import java.util
@@ -3199,32 +3199,75 @@
              %7D%0A%0A
+%09%09%7D else if (yamlValue instanceof List%0A    
                 
@@ -3266,27 +3266,26 @@
         
-%7D else if (
+       %7C%7C 
 yamlValu
@@ -3295,36 +3295,36 @@
 nstanceof String
- %7C%7C%0A
+%0A   
                 
@@ -3322,32 +3322,35 @@
                 
+ %7C%7C
  yamlValue insta
@@ -3362,20 +3362,20 @@
  Boolean
- %7C%7C%0A
+%0A   
         
@@ -3381,32 +3381,35 @@
                 
+ %7C%7C
  yamlValue insta
@@ -3420,20 +3420,18 @@
 f Double
- %7C%7C
 %0A
+ 
         
@@ -3437,32 +3437,37 @@
                 
+   %7C%7C
  yamlValue insta
@@ -3805,23 +3805,66 @@
 ceof Map
- %7C%7C
 %0A   
+                 %7C%7C yamlValue instanceof List%0A
         
@@ -3867,32 +3867,34 @@
                 
+%7C%7C
  yamlValue insta
@@ -3909,14 +3909,9 @@
 ring
- %7C%7C%0A  
+%0A
     
@@ -3918,33 +3918,34 @@
                 
- 
+%7C%7C
  yamlValue insta
@@ -3957,18 +3957,16 @@
  Boolean
-%7C%7C
 %0A       
@@ -3970,35 +3970,34 @@
                 
-   
+%7C%7C
  yamlValue insta
@@ -4009,18 +4009,16 @@
  Integer
-%7C%7C
 %0A       
@@ -4030,19 +4030,18 @@
         
-   
+%7C%7C
  yamlVal

support List types