package org.infinispan.scripting.impl;

import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import org.infinispan.commons.CacheException;
import org.infinispan.commons.marshall.Marshaller;
import org.infinispan.commons.util.Immutables;

public enum DataType {
   UTF8(new Utf8Transformer()),
   DEFAULT(new DefaultTransformer());

   public final Transformer transformer;

   DataType(Transformer transformer) {
      this.transformer = transformer;
   }

   public static DataType fromMime(String mime) {
      switch (mime) {
         case "text/plain; charset=utf-8":
            return UTF8;
         default:
            return DEFAULT;
      }
   }

   interface Transformer {
      Object toDataType(Object obj, Optional<Marshaller> marshaller);
      Map<String, ?> toDataType(Map<String, ?> objs, Optional<Marshaller> marshaller);
      Object fromDataType(Object obj, Optional<Marshaller> marshaller);
   }

   static final class Utf8Transformer implements Transformer {
      public static final Charset CHARSET_UTF8 = Charset.forName("UTF-8");

      @Override
      public Object toDataType(Object obj, Optional<Marshaller> marshaller) {
         return obj instanceof byte[] ? asString(obj) : obj;
      }

      @Override
      public Map<String, ?> toDataType(Map<String, ?> objs, Optional<Marshaller> marshaller) {
         return objs.entrySet().stream().map(e -> {
            Object v = e.getValue();
            Object entryValue = v instanceof byte[] ? asString(v) : v;
            return Immutables.immutableEntry(e.getKey(), entryValue);
         }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
      }

      @Override
      @SuppressWarnings("unchecked")
      public Object fromDataType(Object obj, Optional<Marshaller> marshaller) {
         if (obj instanceof List) {
            return ((List) obj).stream()
                  .map(x -> x == null ? "" : x.toString())
                  .collect(Collectors.joining("\", \"", "[\"", "\"]"))
                  .toString().getBytes(CHARSET_UTF8);
         }  else if (obj instanceof byte[]) {
            return obj;
         }

         return Objects.isNull(obj) ? null : obj.toString().getBytes(CHARSET_UTF8);
      }

      private static String asString(Object v) {
         return new String((byte[]) v, CHARSET_UTF8);
      }
   }

   static final class DefaultTransformer implements Transformer {

      @Override
      public Object toDataType(Object obj, Optional<Marshaller> marshaller) {
         return marshaller.map(m -> obj instanceof byte[] ? fromBytes(obj, m) : obj).orElse(obj);
      }

      @Override
      public Map<String, ?> toDataType(Map<String, ?> objs, Optional<Marshaller> marshaller) {
         if (marshaller.isPresent()) {
            Marshaller m = marshaller.get();
            return objs.entrySet().stream().map(e -> {
               Object v = e.getValue();
               Object entryValue = v instanceof byte[] ? fromBytes(v, m) : v;
               return Immutables.immutableEntry(e.getKey(), entryValue);
            }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
         }

         return objs;
      }

      private static Object fromBytes(Object obj, Marshaller marshaller) {
         try {
            return marshaller.objectFromByteBuffer((byte[]) obj);
         } catch (Exception e) {
            throw new CacheException(e);
         }
      }

      @Override
      public Object fromDataType(Object obj, Optional<Marshaller> marshaller) {
         try {
            return marshaller.map(m -> toBytes(obj, m)).orElse(obj);
         } catch (Exception e) {
            throw new CacheException(e);
         }
      }

      private static Object toBytes(Object obj, Marshaller marshaller) {
         try {
            return obj instanceof byte[] ? obj : marshaller.objectToByteBuffer(obj);
         } catch (Exception e) {
            throw new CacheException(e);
         }
      }
   }

}
