package tc.oc.commons.core.util;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;

/**
 * Map adapter that inherits entries from a parent map. This map's entries
 * have priority over the parent's. Modifications affect this map only.
 * Modification through views or iterators is not supported.
 */
public class InheritingMap<K, V> extends ForwardingMap<K, V> {

    private final Map<K, V> map, parent;

    public InheritingMap(Map<K, V> parent) {
        this(new HashMap<>(), parent);
    }

    public InheritingMap(Map<K, V> map, Map<K, V> parent) {
        this.map = map;
        this.parent = parent;
    }

    @SafeVarargs
    public static <K, V> Map<K, V> chain(Map<K, V>... maps) {
        return chain(Iterators.forArray(maps));
    }

    public static <K, V> Map<K, V> chain(Iterable<Map<K, V>> maps) {
        return chain(maps.iterator());
    }

    public static <K, V> Map<K, V> chain(Iterator<Map<K, V>> maps) {
        if(!maps.hasNext()) return Collections.emptyMap();
        final Map<K, V> head = maps.next();
        if(!maps.hasNext()) return head;
        return new InheritingMap<>(head, chain(maps));
    }

    @Override
    protected Map<K, V> delegate() {
        return map;
    }

    @Override
    public int size() {
        return Sets.union(map.keySet(), parent.keySet()).size();
    }

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

    @Override
    public boolean containsKey(Object key) {
        return map.containsKey(key) || parent.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return map.containsValue(value) || parent.containsValue(value);
    }

    @Override
    public V get(Object key) {
        if(map.containsKey(key)) {
            return map.get(key);
        } else {
            return parent.get(key);
        }

    }

    @Override
    public Set<K> keySet() {
        return Sets.union(map.keySet(), parent.keySet());
    }

    @Override
    public Collection<V> values() {
        return Collections2.transform(keySet(), new Function<K, V>() {
            @Override public V apply(K key) {
                return get(key);
            }
        });
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        // Union of child's entrySet and paren't entrySet with child's keys filtered out
        return Sets.union(
            map.entrySet(),
            Sets.filter(
                parent.entrySet(),
                new Predicate<Entry<K, V>>() {
                    @Override public boolean apply(Entry<K, V> entry) {
                        return !map.containsKey(entry.getKey());
                    }
                }
            )
        );
    }
}
