/*
 * Copyright 2015, 2024 StreamEx contributors
 *
 * Licensed 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 one.util.streamex;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.function.*;
import java.util.stream.Collector;
import java.util.stream.Collector.Characteristics;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static one.util.streamex.Internals.PairBox;
import static one.util.streamex.Internals.checkLength;

/**
 * A {@link Stream} of {@link Entry} objects which provides additional specific
 * functionality.
 *
 * <p>
 * While {@code EntryStream} implements {@code Iterable}, it is not a
 * general-purpose {@code Iterable} as it supports only a single
 * {@code Iterator}; invoking the {@link #iterator iterator} method to obtain a
 * second or subsequent iterator throws {@code IllegalStateException}.
 *
 * @param <K> the type of {@code Entry} keys
 * @param <V> the type of {@code Entry} values
 */
@NullMarked
public final class EntryStream<K extends @Nullable Object, V extends @Nullable Object> extends
        AbstractStreamEx<Entry<K, V>, EntryStream<K, V>> {
    EntryStream(Stream<? extends Entry<K, V>> stream, StreamContext context) {
        super(stream, context);
    }

    EntryStream(Spliterator<? extends Entry<K, V>> spliterator, StreamContext context) {
        super(spliterator, context);
    }

    @Override
    EntryStream<K, V> supply(Stream<Map.Entry<K, V>> stream) {
        return new EntryStream<>(stream, context);
    }

    @Override
    EntryStream<K, V> supply(Spliterator<Entry<K, V>> spliterator) {
        return new EntryStream<>(spliterator, context);
    }

    static <K extends @Nullable Object, V extends @Nullable Object> Consumer<? super Entry<K, V>> toConsumer(
            BiConsumer<? super K, ? super V> action) {
        return entry -> action.accept(entry.getKey(), entry.getValue());
    }

    static <K extends @Nullable Object, V extends @Nullable Object, M extends Map<K, V>> Consumer<? super Entry<K, V>> toMapConsumer(M map) {
        return entry -> addToMap(map, entry.getKey(), Objects.requireNonNull(entry.getValue()));
    }

    BiPredicate<? super Entry<K, V>, ? super Entry<K, V>> equalKeys() {
        return (e1, e2) -> Objects.equals(e1.getKey(), e2.getKey());
    }

    static <K extends @Nullable Object, V extends @Nullable Object> @Nullable Stream<Entry<K, V>> withValue(
            @Nullable Stream<? extends K> s, V value) {
        return s == null ? null : s.map(key -> new SimpleImmutableEntry<>(key, value));
    }

    static <K extends @Nullable Object, V extends @Nullable Object> @Nullable Stream<Entry<K, V>> withKey(
            K key, @Nullable Stream<? extends V> s) {
        return s == null ? null : s.map(value -> new SimpleImmutableEntry<>(key, value));
    }

    static <K extends @Nullable Object, V extends @Nullable Object, R extends @Nullable Object> Function<? super Entry<K, V>, ? extends R> toFunction(
            BiFunction<? super K, ? super V, ? extends R> mapper) {
        return entry -> mapper.apply(entry.getKey(), entry.getValue());
    }

    /**
     * Returns a {@link StreamEx} of strings which are created joining the keys
     * and values of the current stream using the specified delimiter.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate</a>
     * operation.
     *
     * @param delimiter the delimiter to be used between key and value
     * @return the new stream
     * @since 0.2.2
     */
    public StreamEx<String> join(CharSequence delimiter) {
        return map(entry -> String.valueOf(entry.getKey()) + delimiter + entry.getValue());
    }

    /**
     * Returns a {@link StreamEx} of strings which are created joining the keys
     * and values of the current stream using the specified delimiter, with the
     * specified prefix and suffix.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate</a>
     * operation.
     *
     * @param delimiter the delimiter to be used between key and value
     * @param prefix the sequence of characters to be used at the beginning of
     *        each resulting string
     * @param suffix the sequence of characters to be used at the end of each
     *        resulting string
     * @return the new stream
     * @since 0.2.2
     */
    public StreamEx<String> join(CharSequence delimiter, CharSequence prefix, CharSequence suffix) {
        return map(entry -> String.valueOf(prefix) + entry.getKey() + delimiter + entry.getValue() + suffix);
    }

    /**
     * Returns an {@code EntryStream} consisting of the entries whose keys are
     * results of replacing source keys with the contents of a mapped stream
     * produced by applying the provided mapping function to each source key and
     * values are left intact. Each mapped stream is
     * {@link java.util.stream.BaseStream#close() closed} after its contents
     * have been placed into this stream. (If a mapped stream is {@code null} an
     * empty stream is used, instead.)
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @param <KK> The type of new keys
     * @param mapper a <a
     *        href="package-summary.html#NonInterference">non-interfering </a>,
     *        <a href="package-summary.html#Statelessness">stateless</a>
     *        function to apply to each key which produces a stream of new keys
     * @return the new stream
     */
    public <KK extends @Nullable Object> EntryStream<KK, V> flatMapKeys(Function<? super K, ? extends @Nullable Stream<? extends KK>> mapper) {
        return new EntryStream<>(stream().flatMap(e -> withValue(mapper.apply(e.getKey()), e.getValue())), context);
    }

    /**
     * Returns an {@code EntryStream} consisting of the entries whose keys are
     * results of replacing source keys with the contents of a mapped stream
     * produced by applying the provided mapping function and values are left
     * intact. Each mapped stream is {@link java.util.stream.BaseStream#close()
     * closed} after its contents have been placed into this stream. (If a
     * mapped stream is {@code null} an empty stream is used, instead.)
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @param <KK> The type of new keys
     * @param mapper a <a
     *        href="package-summary.html#NonInterference">non-interfering </a>,
     *        <a href="package-summary.html#Statelessness">stateless</a>
     *        function to apply to each key and value which produces a stream of
     *        new keys
     * @return the new stream
     * @since 0.5.2
     */
    public <KK extends @Nullable Object> EntryStream<KK, V> flatMapToKey(BiFunction<? super K, ? super V, ? extends @Nullable Stream<? extends KK>> mapper) {
        return new EntryStream<>(
                stream().flatMap(e -> withValue(mapper.apply(e.getKey(), e.getValue()), e.getValue())), context);
    }

    /**
     * Returns an {@code EntryStream} consisting of the entries whose values are
     * results of replacing source values with the contents of a mapped stream
     * produced by applying the provided mapping function to each source value
     * and keys are left intact. Each mapped stream is
     * {@link java.util.stream.BaseStream#close() closed} after its contents
     * have been placed into this stream. (If a mapped stream is {@code null} an
     * empty stream is used, instead.)
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @param <VV> The type of new values
     * @param mapper a <a
     *        href="package-summary.html#NonInterference">non-interfering </a>,
     *        <a href="package-summary.html#Statelessness">stateless</a>
     *        function to apply to each value which produces a stream of new
     *        values
     * @return the new stream
     */
    public <VV extends @Nullable Object> EntryStream<K, VV> flatMapValues(Function<? super V, ? extends @Nullable Stream<? extends VV>> mapper) {
        return new EntryStream<>(stream().flatMap(e -> withKey(e.getKey(), mapper.apply(e.getValue()))), context);
    }

    /**
     * Returns an {@code EntryStream} consisting of the entries whose values are
     * results of replacing source values with the contents of a mapped stream
     * produced by applying the provided mapping function and keys are left
     * intact. Each mapped stream is {@link java.util.stream.BaseStream#close()
     * closed} after its contents have been placed into this stream. (If a
     * mapped stream is {@code null} an empty stream is used, instead.)
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @param <VV> The type of new values
     * @param mapper a <a
     *        href="package-summary.html#NonInterference">non-interfering </a>,
     *        <a href="package-summary.html#Statelessness">stateless</a>
     *        function to apply to each key and value which produces a stream of
     *        new values
     * @return the new stream
     * @since 0.5.2
     */
    public <VV extends @Nullable Object> EntryStream<K, VV> flatMapToValue(
            BiFunction<? super K, ? super V, ? extends @Nullable Stream<? extends VV>> mapper) {
        return new EntryStream<>(stream().flatMap(e -> withKey(e.getKey(), mapper.apply(e.getKey(), e.getValue()))),
                context);
    }

    /**
     * Returns a stream consisting of the results of replacing each element of
     * this stream with the contents of a mapped stream produced by applying the
     * provided mapping function to each key-value pair. Each mapped stream is
     * closed after its contents have been placed into this stream. (If a mapped
     * stream is {@code null} an empty stream is used, instead.)
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate</a>
     * operation.
     *
     * @param <R> The element type of the new stream
     * @param mapper a non-interfering, stateless function to apply to each
     *        key-value pair which produces a stream of new values
     * @return the new stream
     * @since 0.3.0
     */
    public <R extends @Nullable Object> StreamEx<R> flatMapKeyValue(BiFunction<? super K, ? super V,
            ? extends @Nullable Stream<? extends R>> mapper) {
        return this.<R>flatMap(toFunction(mapper));
    }

    /**
     * Returns a new {@code EntryStream} which is a concatenation of this stream
     * and the stream created from the supplied map entries.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a>.
     *
     * <p>
     * May return this if the supplied map is empty and non-concurrent.
     *
     * @param map the map to prepend to the stream
     * @return the new stream
     * @since 0.2.1
     */
    public EntryStream<K, V> append(Map<K, V> map) {
        return appendSpliterator(null, map.entrySet().spliterator());
    }

    /**
     * Returns a new {@code EntryStream} which is a concatenation of this stream
     * and the supplied key-value pair.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a>.
     *
     * @param key the key of the new {@code Entry} to append to this stream
     * @param value the value of the new {@code Entry} to append to this stream
     * @return the new stream
     */
    public EntryStream<K, V> append(K key, V value) {
        return appendSpliterator(null, new ConstSpliterator.OfRef<>(new SimpleImmutableEntry<>(key, value), 1, true));
    }

    /**
     * Returns a new {@code EntryStream} which is a concatenation of this stream
     * and two supplied key-value pairs.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a>.
     *
     * @param k1 the key of the first {@code Entry} to append to this stream
     * @param v1 the value of the first {@code Entry} to append to this stream
     * @param k2 the key of the second {@code Entry} to append to this stream
     * @param v2 the value of the second {@code Entry} to append to this stream
     * @return the new stream
     * @since 0.2.3
     */
    public EntryStream<K, V> append(K k1, V v1, K k2, V v2) {
        @SuppressWarnings("unchecked")
        SimpleImmutableEntry<K, V>[] array = new SimpleImmutableEntry[] { new SimpleImmutableEntry<>(k1, v1),
                new SimpleImmutableEntry<>(k2, v2) };
        return appendSpliterator(null, Spliterators.spliterator(array, Spliterator.ORDERED));
    }

    /**
     * Returns a new {@code EntryStream} which is a concatenation of this stream
     * and three supplied key-value pairs.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a>.
     *
     * @param k1 the key of the first {@code Entry} to append to this stream
     * @param v1 the value of the first {@code Entry} to append to this stream
     * @param k2 the key of the second {@code Entry} to append to this stream
     * @param v2 the value of the second {@code Entry} to append to this stream
     * @param k3 the key of the third {@code Entry} to append to this stream
     * @param v3 the value of the third {@code Entry} to append to this stream
     * @return the new stream
     * @since 0.2.3
     */
    public EntryStream<K, V> append(K k1, V v1, K k2, V v2, K k3, V v3) {
        @SuppressWarnings("unchecked")
        SimpleImmutableEntry<K, V>[] array = new SimpleImmutableEntry[] { new SimpleImmutableEntry<>(k1, v1),
                new SimpleImmutableEntry<>(k2, v2), new SimpleImmutableEntry<>(k3, v3) };
        return appendSpliterator(null, Spliterators.spliterator(array, Spliterator.ORDERED));
    }

    /**
     * Returns a new {@code EntryStream} which is a concatenation of the stream
     * created from the supplied map entries and this stream.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a> with <a href="package-summary.html#TSO">tail-stream
     * optimization</a>.
     *
     * <p>
     * May return this if the supplied map is empty and non-concurrent.
     *
     * @param map the map to prepend to the stream
     * @return the new stream
     * @since 0.2.1
     */
    public EntryStream<K, V> prepend(Map<K, V> map) {
        return prependSpliterator(null, map.entrySet().spliterator());
    }

    /**
     * Returns a new {@code EntryStream} which is a concatenation of the
     * supplied key-value pair and this stream.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a> with <a href="package-summary.html#TSO">tail-stream
     * optimization</a>.
     *
     * @param key the key of the new {@code Entry} to prepend to this stream
     * @param value the value of the new {@code Entry} to prepend to this stream
     * @return the new stream
     */
    public EntryStream<K, V> prepend(K key, V value) {
        return supply(new PrependSpliterator<>(spliterator(), new SimpleImmutableEntry<>(key, value)));
    }

    /**
     * Returns a new {@code EntryStream} which is a concatenation of two
     * supplied key-value pairs and this stream.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a> with <a href="package-summary.html#TSO">tail-stream
     * optimization</a>.
     *
     * @param k1 the key of the first {@code Entry} to prepend to this stream
     * @param v1 the value of the first {@code Entry} to prepend to this stream
     * @param k2 the key of the second {@code Entry} to prepend to this stream
     * @param v2 the value of the second {@code Entry} to prepend to this stream
     * @return the new stream
     * @since 0.2.3
     */
    public EntryStream<K, V> prepend(K k1, V v1, K k2, V v2) {
        @SuppressWarnings("unchecked")
        SimpleImmutableEntry<K, V>[] array = new SimpleImmutableEntry[] { new SimpleImmutableEntry<>(k1, v1),
                new SimpleImmutableEntry<>(k2, v2) };
        return prependSpliterator(null, Spliterators.spliterator(array, Spliterator.ORDERED));
    }

    /**
     * Returns a new {@code EntryStream} which is a concatenation of three
     * supplied key-value pairs and this stream.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a> with <a href="package-summary.html#TSO">tail-stream
     * optimization</a>.
     *
     * @param k1 the key of the first {@code Entry} to prepend to this stream
     * @param v1 the value of the first {@code Entry} to prepend to this stream
     * @param k2 the key of the second {@code Entry} to prepend to this stream
     * @param v2 the value of the second {@code Entry} to prepend to this stream
     * @param k3 the key of the third {@code Entry} to prepend to this stream
     * @param v3 the value of the third {@code Entry} to prepend to this stream
     * @return the new stream
     * @since 0.2.3
     */
    public EntryStream<K, V> prepend(K k1, V v1, K k2, V v2, K k3, V v3) {
        @SuppressWarnings("unchecked")
        SimpleImmutableEntry<K, V>[] array = new SimpleImmutableEntry[] { new SimpleImmutableEntry<>(k1, v1),
                new SimpleImmutableEntry<>(k2, v2), new SimpleImmutableEntry<>(k3, v3) };
        return prependSpliterator(null, Spliterators.spliterator(array, Spliterator.ORDERED));
    }

    /**
     * Returns a stream consisting of the elements of this stream which have
     * distinct keys (according to object equality).
     *
     * <p>
     * For ordered streams, the selection of distinct keys is stable (for
     * elements with duplicating keys, the element appearing first in the
     * encounter order is preserved.) For unordered streams, no stability
     * guarantees are made.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">stateful intermediate
     * operation</a>.
     *
     * @return the new stream
     * @since 0.3.8
     */
    public EntryStream<K, V> distinctKeys() {
        return distinct(Entry::getKey);
    }

    /**
     * Returns a stream consisting of the elements of this stream which have
     * distinct values (according to object equality).
     *
     * <p>
     * For ordered streams, the selection of distinct values is stable (for
     * elements with duplicating values, the element appearing first in the
     * encounter order is preserved.) For unordered streams, no stability
     * guarantees are made.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">stateful intermediate
     * operation</a>.
     *
     * @return the new stream
     * @since 0.3.8
     */
    public EntryStream<K, V> distinctValues() {
        return distinct(Entry::getValue);
    }

    /**
     * Returns an {@code EntryStream} consisting of the entries whose keys are
     * modified by applying the given function and values are left unchanged.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate</a>
     * operation.
     *
     * @param <KK> The type of the keys of the new stream
     * @param keyMapper a non-interfering, stateless function to apply to each
     *        key
     * @return the new stream
     */
    public <KK extends @Nullable Object> EntryStream<KK, V> mapKeys(Function<? super K, ? extends KK> keyMapper) {
        return new EntryStream<>(stream().map(
                e -> new SimpleImmutableEntry<>(keyMapper.apply(e.getKey()), e.getValue())), context);
    }

    /**
     * Returns an {@code EntryStream} consisting of the entries whose keys are
     * modified by applying the given partial function to keys of this {@code EntryStream}
     * and removing the keys to which the function is not applicable. The values are left
     * unchanged.
     *
     * <p>
     * If the mapping function returns {@link Optional#empty()}, the original
     * key will be removed from the resulting {@code EntryStream}. The mapping function
     * may not return null.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * <p>
     * The {@code mapKeysPartial()} operation has the effect of applying a
     * one-to-zero-or-one transformation to the keys of the {@code EntryStream},
     * and then flattening the resulting elements into a new stream.
     *
     * @param <KK> The element type of keys in the new {@code EntryStream}
     * @param keyMapper a <a
     *        href="package-summary.html#NonInterference">non-interfering </a>,
     *        <a href="package-summary.html#Statelessness">stateless</a>
     *        partial function to apply to each key which returns a present optional
     *        if it's applicable, or an empty optional otherwise
     * @return the new stream
     * @since 0.8.4
     *
     */
    public <KK> EntryStream<KK, V> mapKeysPartial(Function<? super K, ? extends Optional<? extends KK>> keyMapper) {
        return new EntryStream<>(stream().map(e -> {
                    KK mapping = keyMapper.apply(e.getKey()).orElse(null);
                    return mapping != null ? new SimpleImmutableEntry<>(mapping, e.getValue()) : null;
                }
        ).filter(Objects::nonNull), context);
    }

    /**
     * Returns an {@code EntryStream} consisting of the entries whose keys are
     * left unchanged and values are modified by applying the given function.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate</a>
     * operation.
     *
     * @param <VV> The type of the values of the new stream
     * @param valueMapper a non-interfering, stateless function to apply to each
     *        value
     * @return the new stream
     */
    public <VV extends @Nullable Object> EntryStream<K, VV> mapValues(Function<? super V, ? extends VV> valueMapper) {
        return new EntryStream<>(stream().map(
                e -> new SimpleImmutableEntry<>(e.getKey(), valueMapper.apply(e.getValue()))), context);
    }

    /**
     * Returns an {@code EntryStream} consisting of the entries whose values are
     * modified by applying the given partial function to values of this {@code EntryStream}
     * and removing the values to which the function is not applicable. The keys are left
     * unchanged.
     *
     * <p>
     * If the mapping function returns {@link Optional#empty()}, the original
     * value will be removed from the resulting {@code EntryStream}. The mapping function
     * may not return null.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * <p>
     * The {@code mapValuesPartial()} operation has the effect of applying a
     * one-to-zero-or-one transformation to the keys of the {@code EntryStream},
     * and then flattening the resulting elements into a new stream.
     *
     * @param <VV> The element type of keys in the new {@code EntryStream}
     * @param valueMapper a <a
     *        href="package-summary.html#NonInterference">non-interfering </a>,
     *        <a href="package-summary.html#Statelessness">stateless</a>
     *        partial function to apply to each value which returns a present optional
     *        if it's applicable, or an empty optional otherwise
     * @return the new stream
     * @since 0.8.4
     */
    public <VV> EntryStream<K, VV> mapValuesPartial(Function<? super V, ? extends Optional<? extends VV>> valueMapper) {
        return new EntryStream<>(stream().map(e -> {
                    VV mapping = valueMapper.apply(e.getValue()).orElse(null);
                    return mapping != null ? new SimpleImmutableEntry<>(e.getKey(), mapping) : null;
                }
        ).filter(Objects::nonNull), context);
    }

    /**
     * Returns a {@link StreamEx} consisting of the results of applying the
     * given function to the keys and values of this stream.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate</a>
     * operation.
     *
     * @param <R> The element type of the new stream
     * @param mapper a <a
     *        href="package-summary.html#NonInterference">non-interfering </a>,
     *        <a href="package-summary.html#Statelessness">stateless</a> function to apply to key and
     *        value of each {@link Entry} in this stream
     * @return the new stream
     */
    public <R extends @Nullable Object> StreamEx<R> mapKeyValue(BiFunction<? super K, ? super V, ? extends R> mapper) {
        return this.<R>map(toFunction(mapper));
    }

    /**
     * Performs a mapping of the stream keys and values to a partial function
     * removing the elements to which the function is not applicable.
     *
     * <p>
     * If the mapping function returns {@link Optional#empty()}, the original
     * entry will be removed from the resulting stream. The mapping function
     * may not return null.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * <p>
     * The {@code mapKeyValuePartial()} operation has the effect of applying a
     * one-to-zero-or-one transformation to the elements of the stream, and then
     * flattening the resulting elements into a new stream.
     *
     * @param <R> The element type of the new stream
     * @param mapper a <a
     *        href="package-summary.html#NonInterference">non-interfering </a>,
     *        <a href="package-summary.html#Statelessness">stateless</a>
     *        partial function to apply to original keys and values, which returns a present optional
     *        if it's applicable, or an empty optional otherwise
     * @return the new stream
     * @since 0.6.8
     */
    public <R extends @Nullable Object> StreamEx<R> mapKeyValuePartial(
            BiFunction<? super K, ? super V, ? extends Optional<? extends R>> mapper
    ) {
        return this.<R>mapPartial(toFunction(mapper));
    }

    /**
     * Returns an {@code EntryStream} consisting of the entries whose keys are
     * modified by applying the given function and values are left unchanged.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate</a>
     * operation.
     *
     * @param <KK> The type of the keys of the new stream
     * @param keyMapper a non-interfering, stateless function to apply to each
     *        key-value pair which returns the updated key
     * @return the new stream
     * @since 0.3.0
     */
    public <KK extends @Nullable Object> EntryStream<KK, V> mapToKey(BiFunction<? super K, ? super V, ? extends KK> keyMapper) {
        return new EntryStream<>(stream().map(
                e -> new SimpleImmutableEntry<>(keyMapper.apply(e.getKey(), e.getValue()), e.getValue())), context);
    }

    /**
     * Performs a mapping of the stream content to a partial function
     * removing the entries to which the function is not applicable.
     *
     * <p>
     * If the mapping function returns an optional containing a new key,
     * or {@link Optional#empty()} if function is not applicable to the entry.
     * For successfully mapped keys, the values are left intact. The mapping function
     * may not return null.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * <p>
     * The {@code mapToValuePartial()} operation has the effect of applying a
     * one-to-zero-or-one transformation to the elements of the stream, and then
     * flattening the resulting elements into a new stream.
     *
     * @param <KK>        The type of new keys
     * @param keyMapper a <a
     *                    href="package-summary.html#NonInterference">non-interfering </a>,
     *                    <a href="package-summary.html#Statelessness">stateless</a>
     *                    partial function to apply to original keys and values, which returns a present optional
     *                    if it's applicable, or an empty optional otherwise
     * @return the new stream
     * @since 0.6.8
     */
    public <KK> EntryStream<KK, V> mapToKeyPartial(BiFunction<? super K, ? super V, ? extends Optional<? extends KK>> keyMapper) {
        return new EntryStream<>(stream().map(
                e -> {
                    KK mapping = keyMapper.apply(e.getKey(), e.getValue()).orElse(null);
                    return mapping != null ? new SimpleImmutableEntry<>(mapping, e.getValue()) : null;
                }
        ).filter(Objects::nonNull), context);
    }

    /**
     * Returns an {@code EntryStream} consisting of the entries whose keys are
     * left unchanged and values are modified by applying the given function.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate</a>
     * operation.
     *
     * @param <VV> The type of the values of the new stream
     * @param valueMapper a non-interfering, stateless function to apply to each
     *        key-value pair which returns the updated value
     * @return the new stream
     * @since 0.3.0
     */
    public <VV extends @Nullable Object> EntryStream<K, VV> mapToValue(BiFunction<? super K, ? super V, ? extends VV> valueMapper) {
        return new EntryStream<>(stream().map(
                e -> new SimpleImmutableEntry<>(e.getKey(), valueMapper.apply(e.getKey(), e.getValue()))), context);
    }

    /**
     * Performs a mapping of the stream content to a partial function
     * removing the entries to which the function is not applicable.
     *
     * <p>
     * If the mapping function returns an optional containing a new value,
     * or {@link Optional#empty()} if function is not applicable to the entry.
     * For successfully mapped values, the keys are left intact. The mapping function
     * may not return null.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * <p>
     * The {@code mapToValuePartial()} operation has the effect of applying a
     * one-to-zero-or-one transformation to the elements of the stream, and then
     * flattening the resulting elements into a new stream.
     *
     * @param <VV>        The type of new values
     * @param valueMapper a <a
     *                    href="package-summary.html#NonInterference">non-interfering </a>,
     *                    <a href="package-summary.html#Statelessness">stateless</a>
     *                    partial function to apply to original keys and values, which returns a present optional
     *                    if it's applicable, or an empty optional otherwise
     * @return the new stream
     * @since 0.6.8
     */
    public <VV> EntryStream<K, VV> mapToValuePartial(BiFunction<? super K, ? super V, ? extends Optional<? extends VV>> valueMapper) {
        return new EntryStream<>(stream().map(
                e -> {
                    VV mapping = valueMapper.apply(e.getKey(), e.getValue()).orElse(null);
                    return mapping != null ? new SimpleImmutableEntry<>(e.getKey(), mapping) : null;
                }
        ).filter(Objects::nonNull), context);
    }

    /**
     * Returns a stream consisting of the {@link Entry} objects which keys are
     * the values of this stream elements and vice versa.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate</a>
     * operation.
     *
     * @return the new stream
     */
    public EntryStream<V, K> invert() {
