/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.wildfly.clustering.server.util;

import java.io.Serializable;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

/**
 * An immutable map entry.  This implementation differs from {@link Map#entry(Object, Object)} in the following ways:
 * <ul>
 * <li>Supports null key or value</li>
 * <li>Supports mapping operation</li>
 * </ul>
 * @author Paul Ferraro
 * @param <K> key type
 * @param <V> value type
 */
public interface MapEntry<K, V> extends Map.Entry<K, V>, Serializable {

	/**
	 * Returns a new map entry whose key/value are generated by applying the specified mapping functions.
	 * @param <KK> the mapped key type
	 * @param <VV> the mapped value type
	 * @param keyMapper a key mapper
	 * @param valueMapper a value mapper
	 * @return a new map entry
	 */
	default <KK, VV> MapEntry<KK, VV> map(Function<K, KK> keyMapper, Function<V, VV> valueMapper) {
		return of(keyMapper.apply(this.getKey()), valueMapper.apply(this.getValue()));
	}

	@Override
	default V setValue(V value) {
		throw new UnsupportedOperationException();
	}

	/**
	 * Returns a new immutable map entry from the specified key and value.
	 * @param <K> the key type
	 * @param <V> the value type
	 * @param key the map entry key
	 * @param value the map entry value
	 * @return a new map entry
	 */
	static <K, V> MapEntry<K, V> of(K key, V value) {
		return new SimpleMapEntry<>(key, value);
	}

	class SimpleMapEntry<K, V> implements MapEntry<K, V> {
		private static final long serialVersionUID = -2330498313768028514L;

		private final K key;
		private final V value;

		SimpleMapEntry(K key, V value) {
			this.key = key;
			this.value = value;
		}

		@Override
		public K getKey() {
			return this.key;
		}

		@Override
		public V getValue() {
			return this.value;
		}

		@Override
		public int hashCode() {
			return (this.key != null) ? this.key.hashCode() : 0;
		}

		@Override
		public boolean equals(Object object) {
			if (!(object instanceof Map.Entry)) return false;
			@SuppressWarnings("unchecked")
			Map.Entry<Object, Object> entry = (Map.Entry<Object, Object>) object;
			return Objects.equals(this.key, entry.getKey()) && Objects.equals(this.value, entry.getValue());
		}

		@Override
		public String toString() {
			return String.format("%s=%s", this.key, this.value);
		}
	}
}
