/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */

package net.ranides.assira.collection.maps;

import java.util.EnumSet;
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.function.ToIntFunction;
import java.util.stream.Collector;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class IntMapCollectors {
    
    private IntMapCollectors() {
        /* utility class */
    }
    
    public static <R,V> Collector<R,?, IntMap<R>> unique(ToIntFunction<? super R> key) {
        return IntMapCollectors.unique(key, v -> v);
    }
    
    public static <R,V> Collector<R,?, IntMap<V>> unique(ToIntFunction<? super R> key, Function<R, V> val) {
        return unique(key, val, () -> new IntHashMap<>());
    }
    
    public static <R,V> Collector<R,?, IntMap<V>> unique(ToIntFunction<? super R> key, Function<? super R, ? extends V> val, Supplier<IntMap<V>> supplier) {
        return new UCollector<>(key, val, supplier);
    }
    
    public static <R,V> Collector<R,?, IntMultiMap<R>> multimap(ToIntFunction<? super R> key) {
        return IntMapCollectors.multimap(key, v -> v);
    }
    
    public static <R,V> Collector<R,?, IntMultiMap<V>> multimap(ToIntFunction<? super R> key, Function<R, V> val) {
        return multimap(key, val, () -> new IntHashMultiMap<>());
    }
    
    public static <R,V> Collector<R,?, IntMultiMap<V>> multimap(ToIntFunction<? super R> key, Function<? super R, ? extends V> val, Supplier<IntMultiMap<V>> supplier) {
        return new MCollector<>(key, val, supplier);
    }
    
    static IllegalArgumentException duplicate(Object key, Object previous, Object current) {
        return new IllegalArgumentException("Duplicate values (" + previous + "," + current + ") for key: " + key);
    }
    
    private static class UCollector<R,K,V> implements Collector<R,IntMap<V>,IntMap<V>> {
        
        private final ToIntFunction<? super R> key;
        private final Function<? super R, ? extends V> val;
        private final Supplier<IntMap<V>> supplier;

        public UCollector(ToIntFunction<? super R> key, Function<? super R, ? extends V> val, Supplier<IntMap<V>> supplier) {
            this.key = key;
            this.val = val;
            this.supplier = supplier;
        }
        
        @Override
        public Supplier<IntMap<V>> supplier() {
            return supplier;
        }

        @Override
        public BiConsumer<IntMap<V>, R> accumulator() {
            return this::$accumulate;
        }
        
        private void $accumulate(IntMap<V> map, R r) {
            int ik = key.applyAsInt(r);
            V iv = val.apply(r);
            if(map.containsKey(ik)) {
                throw duplicate(ik, map.get(ik), iv);
            }
            map.put(ik, iv);
        }

        @Override
        public BinaryOperator<IntMap<V>> combiner() {
            return this::$combine;
        }
        
        private IntMap<V> $combine(IntMap<V> target, IntMap<V> source) {
            for(IntMap.IntEntry<V> item : source.fastEntrySet()) {
                int ik = item.getIntKey();
                V iv = item.getValue();
                if(target.containsKey(ik)) {
                    throw duplicate(ik, target.get(ik), iv);
                }
            }
            return target;
        }

        @Override
        public Function<IntMap<V>,IntMap<V>> finisher() {
            return m -> m;
        }

        @Override
        public Set<Collector.Characteristics> characteristics() {
            return EnumSet.of(Collector.Characteristics.IDENTITY_FINISH);
        }

    }
    
    private static class MCollector<R,V> implements Collector<R,IntMultiMap<V>,IntMultiMap<V>> {
        
        private final ToIntFunction<? super R> key;
        private final Function<? super R, ? extends V> val;
        private final Supplier<IntMultiMap<V>> supplier;

        public MCollector(ToIntFunction<? super R> key, Function<? super R, ? extends V> val, Supplier<IntMultiMap<V>> supplier) {
            this.key = key;
            this.val = val;
            this.supplier = supplier;
        }
        
        @Override
        public Supplier<IntMultiMap<V>> supplier() {
            return supplier;
        }

        @Override
        public BiConsumer<IntMultiMap<V>, R> accumulator() {
            return (map, r) -> map.put(key.applyAsInt(r), val.apply(r));
        }

        @Override
        public BinaryOperator<IntMultiMap<V>> combiner() {
            return (a,b) -> { a.putAll(b); return a; };
        }

        @Override
        public Function<IntMultiMap<V>, IntMultiMap<V>> finisher() {
            return m -> m;
        }

        @Override
        public Set<Collector.Characteristics> characteristics() {
            return EnumSet.of(Collector.Characteristics.IDENTITY_FINISH);
        }

    }
    
}
