package net.ranides.assira.collection.maps;

import net.ranides.assira.generic.Ref;
import net.ranides.assira.generic.Pair;

import java.io.Serializable;
import java.util.Collection;
import java.util.Map;
import java.util.Set;

/**
 * CrossMap implementation which uses internally hash table.
 *
 * Internal hash table uses composite keys to store K1,K2 pairs.
 *
 * @param <K1> K1
 * @param <K2> K2
 * @param <V> V
 */
public class OpenCrossMap<K1, K2, V> implements CrossMap<K1, K2, V>, Serializable {

    private static final long serialVersionUID = 1L;

    private final Map<Pair<K1, K2>, V> map;

    /**
     * Creates new empty map
     */
    public OpenCrossMap() {
        this.map = new OpenMap<>();
    }

    @Override
    public int size() {
        return map.size();
    }

    @Override
    public boolean isEmpty() {
        return map.isEmpty();
    }

    @Override
    public boolean containsKey(K1 key1, K2 key2) {
        return map.containsKey(Pair.of(key1, key2));
    }

    @Override
    public boolean containsValue(V value) {
        return map.containsValue(value);
    }

    @Override
    public V get(K1 key1, K2 key2) {
        return map.get(Pair.of(key1, key2));
    }

    @Override
    public Ref<V> getOptional(K1 key1, K2 key2) {
        return Ref.ofNullable(get(key1, key2));
    }

    @Override
    public V put(K1 key1, K2 key2, V value) {
        return map.put(Pair.of(key1, key2), value);
    }

    @Override
    public V remove(K1 key1, K2 key2) {
        return map.remove(Pair.of(key1, key2));
    }

    @Override
    public void putAll(CrossMap<K1, K2, ? extends V> values) {
        values.forEachPair(map::put);
    }

    @Override
    public void clear() {
        map.clear();
    }

    @Override
    public MultiMap<K2, V> reduceFirst() {
        OpenMultiMap<K2, V> result = new OpenMultiMap<>();
        map.forEach((key, value) -> result.put(key.getSecond(), value));
        return result;
    }

    @Override
    public MultiMap<K1, V> reduceSecond() {
        OpenMultiMap<K1, V> result = new OpenMultiMap<>();
        map.forEach((key, value) -> result.put(key.getFirst(), value));
        return result;
    }

    @Override
    public MultiMap<K1, K2> reduceValues() {
        OpenMultiMap<K1, K2> result = new OpenMultiMap<>();
        map.forEach((key, value) -> result.put(key.getFirst(), key.getSecond()));
        return result;
    }

    @Override
    public Set<Pair<K1, K2>> keySet() {
        return map.keySet();
    }

    @Override
    public Collection<V> values() {
        return map.values();
    }

    @Override
    public Set<Map.Entry<Pair<K1, K2>, V>> entrySet() {
        return map.entrySet();
    }

    @Override
    public String toString() {
        return entrySet().toString();
    }
}
