package org.infinispan.stream;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;

import org.infinispan.commons.marshall.Externalizer;
import org.infinispan.commons.marshall.SerializeWith;
import org.infinispan.util.function.SerializableSupplier;

/**
 * Helper class designed to be used to create a serializable Collector for use with
 * {@link org.infinispan.CacheStream#collect(Collector)} from a supplier of a collector.  The problem is that the
 * standard {@link java.util.stream.Collectors} class doesn't provide Serializable Collectors and no way to extend
 * their functionality, so this class is used instead.
 */
public class CacheCollectors {
   private CacheCollectors() { }

   /**
    * Creates a collector that is serializable and will upon usage create a collector using the serializable supplier
    * provided by the user.
    * @param supplier The supplier to crate the collector that is specifically serializable
    * @param <T> The input type of the collector
    * @param <R> The resulting type of the collector
    * @return the collector which is serializable
    * @see SerializableSupplier
    */
   public static <T, R> Collector<T, ?, R> serializableCollector(SerializableSupplier<Collector<T, ?, R>> supplier) {
      return new CollectorSupplier<>(supplier);
   }

   /**
    * Similar to {@link CacheCollectors#serializableCollector(SerializableSupplier)} except that the supplier provided
    * must be marshable through ISPN marshalling techniques.  Note this is not detected until runtime.
    * @param supplier The marshallable supplier of collectors
    * @param <T> The input type of the collector
    * @param <R> The resulting type of the collector
    * @return the collector which is serializable
    * @see Externalizer
    * @see org.infinispan.commons.marshall.AdvancedExternalizer
    */
   public static <T, R> Collector<T, ?, R> collector(Supplier<Collector<T, ?, R>> supplier) {
      return new CollectorSupplier<>(supplier);
   }

   @SerializeWith(value = CollectorSupplier.CollectorSupplierExternalizer.class)
   private static final class CollectorSupplier<T, R> implements Collector<T, Object, R> {
      private final Supplier<Collector<T, ?, R>> supplier;
      private transient Collector<T, Object, R> collector;

      private Collector<T, Object, R> getCollector() {
         if (collector == null) {
            collector = (Collector<T, Object, R>) supplier.get();
         }
         return collector;
      }

      CollectorSupplier(Supplier<Collector<T, ?, R>> supplier) {
         this.supplier = supplier;
      }

      @Override
      public Supplier<Object> supplier() {
         return getCollector().supplier();
      }

      @Override
      public BiConsumer<Object, T> accumulator() {
         return getCollector().accumulator();
      }

      @Override
      public BinaryOperator<Object> combiner() {
         return getCollector().combiner();
      }

      @Override
      public Function<Object, R> finisher() {
         return getCollector().finisher();
      }

      @Override
      public Set<Characteristics> characteristics() {
         return getCollector().characteristics();
      }

      public static final class CollectorSupplierExternalizer implements Externalizer<CollectorSupplier<?, ?>> {

         @Override
         public void writeObject(ObjectOutput output, CollectorSupplier object) throws IOException {
            output.writeObject(object.supplier);
         }

         @Override
         public CollectorSupplier readObject(ObjectInput input) throws IOException, ClassNotFoundException {
            return new CollectorSupplier((Supplier<Collector>) input.readObject());
         }
      }
   }
}
