import java.util.AbstractMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;

import static java.util.stream.Collectors.*;

final class Collectors {

  private Collectors() {
  }

  public static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingByFlatKeys(Function<? super T, Stream<? extends K>> classifier,
                                                                                        Supplier<M> mapFactory,
                                                                                        Collector<? super T, A, D> downstream) {
    return flatMapping(t -> classifier.apply(t).map(k -> new AbstractMap.SimpleEntry<>(k, t)),
                       groupingBy(Map.Entry::getKey, mapFactory, mapping(Map.Entry::getValue, downstream)));
  }

  public static <T, K, U> Collector<T, ?, Map<K, U>> toMapByFlatKeys(Function<? super T, Stream<? extends K>> keyMapper,
                                                                     Function<? super T, ? extends U> valueMapper) {
    return flatMapping(t -> keyMapper.apply(t).map(k -> new AbstractMap.SimpleEntry<>(k, t)),
                       toMap(Map.Entry::getKey, valueMapper.compose(Map.Entry::getValue)));
  }

  public static <T, U, A, R> Collector<T, ?, R> flatMapping(Function<? super T, Stream<? extends U>> mapper,
                                                            Collector<? super U, A, R> downstream) {
    BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
    return Collector.of(downstream.supplier(),
                        (r, t) -> mapper.apply(t).forEach(v -> downstreamAccumulator.accept(r, v)),
                        downstream.combiner(),
                        downstream.finisher(),
                        downstream.characteristics().toArray(new Collector.Characteristics[downstream.characteristics().size()]));
  }
}

class SimplifiedTest {

  public static void a(Stream<Integer> classifier) {
    flatMapping(classifier.map(k -> new AbstractMap.SimpleEntry<Integer, String>(k,k.toString())), Map.Entry::getKey);
  }

  public static <U, R> void flatMapping(Stream<U> stream, Function<U, R> downstream) {}
}
