/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.apache.causeway.commons.internal.collections;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Supplier;
import java.util.stream.Stream;

import org.springframework.lang.Nullable;

import org.apache.causeway.commons.internal.base._Casts;
import org.apache.causeway.commons.internal.exceptions._Exceptions;

import lombok.NonNull;

/**
 * <h1>- internal use only -</h1>
 * <p>
 * Provides Map-of-List and Map-of-Set implementations
 * </p>
 * <p>
 * <b>WARNING</b>: Do <b>NOT</b> use any of the classes provided by this package! <br/>
 * These may be changed or removed without notice!
 * </p>
 *
 * @since 2.0
 */
public class _Multimaps {

    /**
     * Represents a Map of Lists.
     * @param <K>
     * @param <V>
     */
    public static interface ListMultimap<K, V> extends Map<K, List<V>> {
        /**
         * Adds {@code value} to the List stored under {@code key}.
         * (If no such List exists, a new List is created.)
         * @param key
         * @param value
         */
        public void putElement(K key, V value);

        /**
         * Returns the List to which the specified key is mapped,
         * or a new List if this map contains no mapping for the key.
         * <p> In the latter case the List is also put onto this map.
         * @param key
         */
        public List<V> getOrElseNew(K key);

        /**
         * Returns the List to which the specified key is mapped,
         * or an immutable empty List if this map contains no mapping for the key.
         * <p> In the latter case the List is not put onto this map.
         * @param key
         */
        default List<V> getOrElseEmpty(final K key) {
            return getOrDefault(key, Collections.emptyList());
        }

        default Stream<V> streamElements() {
            return values().stream()
                    .flatMap(Collection::stream);
        }

        default Stream<V> streamElements(final K key) {
            return getOrElseEmpty(key).stream();
        }

        /**
         * @return optionally the underlying map, based on whether it implements a {@link NavigableMap}
         */
        default Optional<NavigableMap<K, List<V>>> asNavigableMap() {
            return Optional.empty();
        }

        default NavigableMap<K, List<V>> asNavigableMapElseFail() {
            return asNavigableMap()
                    .orElseThrow(()->_Exceptions
                            .unrecoverable("underlying map is not an instance of NavigableMap"));
        }

    }

    /**
     * Represents a Map of Sets.
     * @param <K>
     * @param <V>
     */
    public static interface SetMultimap<K, V> extends Map<K, Set<V>> {
        /**
         * Adds {@code value} to the Set stored under {@code key}.
         * (If no such Set exists, a new Set is created.)
         * @param key
         * @param value
         */
        public void putElement(K key, V value);

        /**
         * Returns the Set to which the specified key is mapped,
         * or a new Set if this map contains no mapping for the key.
         * <p> In the latter case the Set is also put onto this map.
         * @param key
         */
        public Set<V> getOrElseNew(K key);

        /**
         * Returns the Set to which the specified key is mapped,
         * or an immutable empty Set if this map contains no mapping for the key.
         * <p> In the latter case the Set is not put onto this map.
         * @param key
         */
        default public Set<V> getOrElseEmpty(final K key) {
            return getOrDefault(key, Collections.emptySet());
        }

        default public Stream<V> streamElements() {
            return values().stream()
                    .flatMap(Collection::stream);
        }
    }

    /**
     * Represents a Map of Maps.
     * @param <K1>
     * @param <K2>
     * @param <V>
     */
    public static interface MapMultimap<K1, K2, V> extends Map<K1, Map<K2, V>> {

        /**
         * Puts {@code value} into the element-map stored under {@code key}.
         * (If no such Map exists, a new Map is created.)
         * @param key
         * @param subkey the key into the element-map where to store the {@code value}
         * @param value
         */
        public void putElement(K1 key, K2 subkey, V value);

        /**
         * Get the element-map by {@code key}, then within the element-map looks up {@code subkey}.
         * @param key
         * @param subkey
         * @return null if no such element exists
         */
        public V getElement(K1 key, K2 subkey);

        /**
         * Returns the Map to which the specified key is mapped,
         * or a new Map if this map contains no mapping for the key.
         * <p> In the latter case the Map is also put onto this map.
         * @param key
         */
        public Map<K2, V> getOrElseNew(K1 key);

        /**
         * Returns the Map to which the specified key is mapped,
         * or an immutable empty Map if this map contains no mapping for the key.
         * <p> In the latter case the Map not is put onto this map.
         * @param key
         */
        default public Map<K2, V> getOrElseEmpty(final K1 key) {
            return getOrDefault(key, Collections.emptyMap());
        }

        default public Stream<V> streamElements() {
            return values().stream()
                    .map(Map::values)
                    .flatMap(Collection::stream);
        }
    }

    public static <K, V> ListMultimap<K, V> newListMultimap(
            final @NonNull Supplier<Map<K, List<V>>> mapFactory,
            final @NonNull Supplier<List<V>> elementCollectionFactory){

        return new ListMultimap<K, V>() {

            final Map<K, List<V>> delegate = mapFactory.get();

            @Override public int size() { return delegate.size(); }
            @Override public boolean isEmpty() { return delegate.isEmpty();	}
            @Override public boolean containsKey(final Object key) { return delegate.containsKey(key); }
            @Override public boolean containsValue(final Object value) { return delegate.containsValue(value); }
            @Override public List<V> get(final Object key) { return delegate.get(key); }
            @Override public List<V> put(final K key, final List<V> value) { return delegate.put(key, value); }
            @Override public List<V> remove(final Object key) { return delegate.remove(key); }
            @Override public void putAll(final Map<? extends K, ? extends List<V>> m) {	delegate.putAll(m);	}
            @Override public void clear() {	delegate.clear(); }
            @Override public Set<K> keySet() { return delegate.keySet(); }
            @Override public Collection<List<V>> values() { return delegate.values();	}
            @Override public Set<Entry<K, List<V>>> entrySet() { return delegate.entrySet(); }

            @Override
            public void putElement(final K key, final V value) {
                getOrElseNew(key).add(value);
            }

            @Override
            public List<V> getOrElseNew(final K key) {
                var collection = delegate.computeIfAbsent(key, __->elementCollectionFactory.get());
                return collection;
            }

            @Override
            public Optional<NavigableMap<K, List<V>>> asNavigableMap() {
                return delegate instanceof NavigableMap
                        ? Optional.of(_Casts.uncheckedCast(delegate))
                        : Optional.empty();
            }

        };
    }

    public static <K, V, S extends Set<V>> SetMultimap<K, V> newSetMultimap(
            final @NonNull Supplier<? extends Map<K, S>> mapFactory,
            final @NonNull Supplier<S> elementCollectionFactory){

        return new SetMultimap<K, V>() {

            final Map<K, Set<V>> delegate = _Casts.uncheckedCast(mapFactory.get());

            @Override public int size() { return delegate.size(); }
            @Override public boolean isEmpty() { return delegate.isEmpty();	}
            @Override public boolean containsKey(final Object key) { return delegate.containsKey(key); }
            @Override public boolean containsValue(final Object value) { return delegate.containsValue(value); }
            @Override public Set<V> get(final Object key) { return delegate.get(key); }
            @Override public Set<V> put(final K key, final Set<V> value) { return delegate.put(key, value); }
            @Override public Set<V> remove(final Object key) { return delegate.remove(key); }
            @Override public void putAll(final Map<? extends K, ? extends Set<V>> m) {	delegate.putAll(m);	}
            @Override public void clear() {	delegate.clear(); }
            @Override public Set<K> keySet() { return delegate.keySet(); }
            @Override public Collection<Set<V>> values() { return delegate.values();	}
            @Override public Set<Entry<K, Set<V>>> entrySet() { return delegate.entrySet(); }

            @Override
            public void putElement(final K key, final V value) {
                getOrElseNew(key).add(value);
            }

            @Override
            public Set<V> getOrElseNew(final K key) {
                var collection = delegate.computeIfAbsent(key, __->elementCollectionFactory.get());
                return collection;
            }

        };
    }

    public static <K1, K2, V> MapMultimap<K1, K2, V> newMapMultimap(
            final @NonNull Supplier<Map<K1, Map<K2, V>>> mapFactory,
            final @NonNull Supplier<Map<K2, V>> elementMapFactory){

        return new MapMultimap<K1, K2, V>() {

            final Map<K1, Map<K2, V>> delegate = mapFactory.get();

            @Override public int size() { return delegate.size(); }
            @Override public boolean isEmpty() { return delegate.isEmpty();	}
            @Override public boolean containsKey(final Object key) { return delegate.containsKey(key); }
            @Override public boolean containsValue(final Object value) { return delegate.containsValue(value); }
            @Override public Map<K2, V> get(final Object key) { return delegate.get(key); }
            @Override public Map<K2, V> put(final K1 key, final Map<K2, V> value) { return delegate.put(key, value); }
            @Override public Map<K2, V> remove(final Object key) { return delegate.remove(key); }
            @Override public void putAll(final Map<? extends K1, ? extends Map<K2, V>> m) {	delegate.putAll(m);	}
            @Override public void clear() {	delegate.clear(); }
            @Override public Set<K1> keySet() { return delegate.keySet(); }
            @Override public Collection<Map<K2, V>> values() { return delegate.values();	}
            @Override public Set<Entry<K1, Map<K2, V>>> entrySet() { return delegate.entrySet(); }

            @Override
            public void putElement(final K1 key, final K2 subkey, final V value) {
                getOrElseNew(key).put(subkey, value);
            }

            @Override
            public V getElement(final K1 key, final K2 subkey) {
                final Map<K2, V> elementMap = delegate.get(key);
                return elementMap!=null ? elementMap.get(subkey) : null;
            }

            @Override
            public Map<K2, V> getOrElseNew(final K1 key) {
                var elementMap = delegate.computeIfAbsent(key, __->elementMapFactory.get());
                return elementMap;
            }

        };
    }

    // -- SHORTCUTS

    /**
     * @return HashMap of Lists with given listFactory
     */
    public static <K, V> ListMultimap<K, V> newListMultimap(final Supplier<List<V>> listFactory){
        return newListMultimap(HashMap<K, List<V>>::new, listFactory);
    }

    /**
     *
     * @return HashMap of Sets with given setFactory
     */
    public static <K, V> SetMultimap<K, V> newSetMultimap(final Supplier<Set<V>> setFactory){
        return newSetMultimap(HashMap<K, Set<V>>::new, setFactory);
    }

    // -- CONVENIENT DEFAULTS

    /**
     * @return HashMap of ArrayLists
     */
    public static <K, V> ListMultimap<K, V> newListMultimap(){
        return newListMultimap(HashMap<K, List<V>>::new, ArrayList::new);
    }

    /**
     * @return TreeMap of ArrayLists
     */
    public static <K, V> ListMultimap<K, V> newListTreeMultimap(){
        return newListMultimap(TreeMap<K, List<V>>::new, ArrayList::new);
    }

    /**
     * @return ConcurrentHashMap of CopyOnWriteArrayList (fully concurrent)
     */
    public static <K, V> ListMultimap<K, V> newConcurrentListMultimap(){
        return newListMultimap(ConcurrentHashMap<K, List<V>>::new, CopyOnWriteArrayList::new);
    }

    /**
     * @return ConcurrentSkipListMap of CopyOnWriteArrayList (fully concurrent)
     */
    public static <K, V> ListMultimap<K, V> newSortedConcurrentListMultimap(){
        return newListMultimap(ConcurrentSkipListMap<K, List<V>>::new, CopyOnWriteArrayList::new);
    }

    /**
     * @return HashMap of HashSets
     */
    public static <K, V> SetMultimap<K, V> newSetMultimap(){
        return newSetMultimap(HashMap<K, Set<V>>::new, HashSet::new);
    }

    /**
     * @return ConcurrentHashMap of ConcurrentHashMap-key-sets (fully concurrent)
     */
    public static <K, V> SetMultimap<K, V> newConcurrentSetMultimap(){
        return newSetMultimap(ConcurrentHashMap<K, Set<V>>::new, _Sets::newConcurrentHashSet);
    }

    /**
     * @param keyComparator - if {@code null} uses natural ordering
     * @param elementComparator - if {@code null} uses natural ordering
     * @return TreeMap of TreeSets
     */
    public static <K, V> SetMultimap<K, V> newSortedSetMultimap(
            final @Nullable Comparator<K> keyComparator,
            final @Nullable Comparator<V> elementComparator){

        final Supplier<SortedMap<K, SortedSet<V>>> mapFactory = ()->new TreeMap<K, SortedSet<V>>(keyComparator);
        final Supplier<SortedSet<V>> elementSetFactory = ()->new TreeSet<V>(elementComparator);
        return newSetMultimap(mapFactory, elementSetFactory);
    }

    /**
     * @return HashMap of HashMaps
     */
    public static <K1, K2, V> MapMultimap<K1, K2, V> newMapMultimap(){
        return newMapMultimap(HashMap<K1, Map<K2, V>>::new, HashMap::new);
    }

    /**
     * @return ConcurrentHashMap of HashMaps (not fully concurrent)
     */
    public static <K1, K2, V> MapMultimap<K1, K2, V> newConcurrentMapMultimap(){
        return newMapMultimap(ConcurrentHashMap<K1, Map<K2, V>>::new, HashMap::new);
    }

}
