package io.bit3.jsass.type;

import org.apache.commons.lang3.StringUtils;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Helper class to convert libsass to java values and vise versa.
 */
public class TypeUtils {
  /**
   * Try to convert "any" java object into a responsible sass value.
   */
  public static SassValue convertToSassValue(Object value) {
    if (null == value) {
      return new SassNull();
    }

    if (value instanceof SassValue) {
      return (SassValue) value;
    }

    Class cls = value.getClass();

    if (isaBoolean(cls)) {
      return new SassBoolean((Boolean) value);
    }

    if (isaNumber(cls)) {
      return new SassNumber(((Number) value).doubleValue(), "");
    }

    if (isaString(cls) || isaCharacter(cls)) {
      return new SassString(value.toString());
    }

    if (value instanceof Collection) {
      return new SassList(
          ((Collection<?>) value)
              .stream()
              .map(TypeUtils::convertToSassValue)
              .collect(Collectors.toList())
      );
    }

    if (value instanceof Map) {
      SassMap map = new SassMap();

      for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {
        String key = entry.getKey().toString();
        SassValue item = TypeUtils.convertToSassValue(entry.getValue());

        map.put(key, item);
      }

      return map;
    }

    if (value instanceof Throwable) {
      Throwable throwable = (Throwable) value;
      StringWriter stringWriter = new StringWriter();
      PrintWriter printWriter = new PrintWriter(stringWriter);

      String message = throwable.getMessage();
      if (StringUtils.isNotEmpty(message)) {
        printWriter.append(message).append("\n");
      }
      throwable.printStackTrace(printWriter);

      return new SassError(stringWriter.toString());
    }

    return new SassError(
        String.format(
            "Could not convert object of type %s into a sass value",
            value.getClass().toString()
        )
    );
  }

  private static boolean isaNumber(Class cls) {
    return Number.class.isAssignableFrom(cls)
        || byte.class.isAssignableFrom(cls)
        || short.class.isAssignableFrom(cls)
        || int.class.isAssignableFrom(cls)
        || long.class.isAssignableFrom(cls)
        || float.class.isAssignableFrom(cls)
        || double.class.isAssignableFrom(cls);
  }

  public static boolean isaString(Class<?> type) {
    return CharSequence.class.isAssignableFrom(type);
  }

  public static boolean isaByte(Class<?> type) {
    return Byte.class.isAssignableFrom(type) || byte.class.isAssignableFrom(type);
  }

  public static boolean isaShort(Class<?> type) {
    return Short.class.isAssignableFrom(type) || short.class.isAssignableFrom(type);
  }

  public static boolean isaInteger(Class<?> type) {
    return Integer.class.isAssignableFrom(type) || int.class.isAssignableFrom(type);
  }

  public static boolean isaLong(Class<?> type) {
    return Long.class.isAssignableFrom(type) || long.class.isAssignableFrom(type);
  }

  public static boolean isaFloat(Class<?> type) {
    return Float.class.isAssignableFrom(type) || float.class.isAssignableFrom(type);
  }

  public static boolean isaDouble(Class<?> type) {
    return Double.class.isAssignableFrom(type) || double.class.isAssignableFrom(type);
  }

  public static boolean isaCharacter(Class<?> type) {
    return Character.class.isAssignableFrom(type) || char.class.isAssignableFrom(type);
  }

  public static boolean isaBoolean(Class<?> type) {
    return Boolean.class.isAssignableFrom(type) || boolean.class.isAssignableFrom(type);
  }
}

@@ -176,16 +176,58 @@
 il.Map;%0A
+import java.util.function.BinaryOperator;%0A
 import j
@@ -1225,127 +1225,125 @@
     
-SassMap map = new SassMap();%0A%0A      for (Map.Entry%3C?, ?%3E entry : ((Map%3C?, ?%3E) value).entrySet()) %7B%0A        String key =
+return ((Map%3C?, ?%3E) value).entrySet()%0A          .stream()%0A          .collect(Collectors.toMap(%0A              entry -%3E
  ent
@@ -1364,17 +1364,17 @@
 String()
-;
+,
 %0A       
@@ -1378,24 +1378,22 @@
     
-SassValue item =
+      entry -%3E
  Typ
@@ -1435,18 +1435,17 @@
 Value())
-;%0A
+,
 %0A       
@@ -1449,53 +1449,84 @@
     
-map.put(key, item);
+      (origin, duplicate) -%3E origin,
 %0A      
-%7D%0A%0A
+ 
       
-return map
+ SassMap::new%0A          ))
 ;%0A  

Use a stream to convert arbitary Map to SassMap.