package org.asciidoctor.internal;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyHash;
import org.jruby.RubyString;
import org.jruby.RubySymbol;
import org.jruby.javasupport.JavaEmbedUtils;
import org.jruby.runtime.builtin.IRubyObject;

public class RubyHashUtil {

    private RubyHashUtil() {
        super();
    }

    public static RubyHash convertMapToRubyHashWithSymbolsIfNecessary(Ruby rubyRuntime, Map<Object, Object> options) {

        RubyHash rubyHash = new RubyHash(rubyRuntime);

        Set<Entry<Object, Object>> optionsSet = options.entrySet();

        for (Entry<Object, Object> entry : optionsSet) {

            Object keyType = entry.getKey();

            if (isNotARubySymbol(keyType)) {
                String key = (String) keyType;
                Object value = entry.getValue();

                RubySymbol newSymbol = RubyUtils.toSymbol(rubyRuntime, key);
                IRubyObject iRubyValue = toRubyObject(rubyRuntime, value);

                rubyHash.put(newSymbol, iRubyValue);
            } else if (keyType instanceof RubySymbol) {
                Object value = entry.getValue();
                IRubyObject iRubyValue = toRubyObject(rubyRuntime, value);
                rubyHash.put(keyType, iRubyValue);
            }

        }

        return rubyHash;

    }

    public static RubyHash convertMapToRubyHashWithSymbols(Ruby rubyRuntime, Map<String, Object> options) {

        RubyHash rubyHash = new RubyHash(rubyRuntime);

        Set<Entry<String, Object>> optionsSet = options.entrySet();

        for (Entry<String, Object> entry : optionsSet) {

            String key = entry.getKey();
            Object value = entry.getValue();

            RubySymbol newSymbol = RubyUtils.toSymbol(rubyRuntime, key);
            IRubyObject iRubyValue = toRubyObject(rubyRuntime, value);

            rubyHash.put(newSymbol, iRubyValue);

        }

        return rubyHash;

    }

    private static boolean isNotARubySymbol(Object keyType) {
        return keyType instanceof String;
    }

    public static Map<Object, Object> convertRubyHashMapToMap(Map<Object, Object> rubyHashMap) {

        Map<Object, Object> map = new HashMap<Object, Object>();

        Set<Entry<Object, Object>> elements = rubyHashMap.entrySet();

        for (Entry<Object, Object> element : elements) {
            if(element.getKey() instanceof RubySymbol) {
                map.put(toJavaString((RubySymbol)element.getKey()), toJavaObject(element.getValue()));
            } else {
                map.put(toJavaObject(element.getKey()).toString(), toJavaObject(element.getValue()));
            }
        }

        return map;

    }

    public static Map<String, Object> convertRubyHashMapToStringObjectMap(Map<Object, Object> rubyHashMap) {

        Map<String, Object> map = new HashMap<String, Object>();

        Set<Entry<Object, Object>> elements = rubyHashMap.entrySet();

        for (Entry<Object, Object> element : elements) {
            if(element.getKey() instanceof RubySymbol) {
                map.put(toJavaString((RubySymbol)element.getKey()), toJavaObject(element.getValue()));
            } else if (element.getKey() instanceof RubyString) {
                map.put(((RubyString) element.getKey()).asJavaString(), toJavaObject(element.getValue()));
            } else if (element.getKey() instanceof String) {
                map.put((String) element.getKey(), toJavaObject(element.getValue()));
            }
        }

        return map;

    }

    private static String toJavaString(RubySymbol element) {
        return element.asJavaString();
    }

    private static Object toJavaObject(Object rubyObject) {
        if (rubyObject instanceof IRubyObject) {
            IRubyObject iRubyObject = (IRubyObject) rubyObject;
            return JavaEmbedUtils.rubyToJava(iRubyObject);
        }

        return rubyObject;
    }

    private static IRubyObject toRubyObject(Ruby rubyRuntime, Object value) {

        if (value instanceof List) {
            return toRubyArray(rubyRuntime, (List<Object>) value);
        } else {

            if (isNotARubySymbol(value)) {
                String stringValue = ((String) value);

                if (stringValue.startsWith(":")) {
                    return RubyUtils.toSymbol(rubyRuntime, stringValue.substring(1));
                }
            }

            IRubyObject iRubyObject = JavaEmbedUtils.javaToRuby(rubyRuntime, value);
            return iRubyObject;
        }
    }

    private static IRubyObject toRubyArray(Ruby rubyRuntime, List<Object> values) {

        RubyArray rubyArray = RubyArray.newArray(rubyRuntime, values.size());

        for (Object value : values) {
            rubyArray.add(toRubyObject(rubyRuntime, value));
        }

        return rubyArray;
    }

    public static RubyHash toNoneSymbolsRubyHash(Ruby rubyRuntime, Map<String, Object> map) {

        RubyHash rubyHash = new RubyHash(rubyRuntime);

        Set<Entry<String, Object>> entrySet = map.entrySet();

        for (Entry<String, Object> entry : entrySet) {
            rubyHash.put(toJavaObject(entry.getKey()), toJavaObject(entry.getValue()));
        }

        return rubyHash;

    }

}

@@ -2161,22 +2161,28 @@
 tanceof 
-String
+CharSequence
 ;%0A    %7D%0A
@@ -4290,22 +4290,28 @@
         
-String
+CharSequence
  stringV
@@ -4319,22 +4319,28 @@
 lue = ((
-String
+CharSequence
 ) value)
@@ -4378,23 +4378,24 @@
 lue.
-startsWith(%22:%22)
+charAt(0) == ':'
 ) %7B%0A
@@ -4472,16 +4472,51 @@
 .sub
-string(1
+Sequence(1, stringValue.length()).toString(
 ));%0A

Make RubyHashUtil.convertRubyHashMapToMap convert all CharSequence values convert to Ruby symbols, in particular Groovy GStrings.