/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */

package net.ranides.assira.collection.maps;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.TreeMap;
import java.util.WeakHashMap;
import java.util.function.Function;
import net.ranides.assira.generic.Wrapper;
import net.ranides.assira.reflection.IAttribute;
import net.ranides.assira.reflection.IClass;
import net.ranides.assira.reflection.IField;
import net.ranides.assira.reflection.IMethod;

/**
 * 
 * @author Ranides Atterwim <ranides@gmail.com>
 * @param <K>
 * @param <V> 
 */
public class Key<K,V> implements Function<Map<K,? extends V>,V> {

    private final IClass<V> itype;
    private final K mkey;
    
    public Key(Class<V> type, K key) {
        this.itype = IClass.typeinfo(type);
        this.mkey = key;
    }

    public V get(Map<K,? extends V> map) {
        return map.get(mkey);
    }
    
    public Collection<? super V> getAll(MultiMap<K, ? super V> map) {
        return map.getAll(mkey);
    }

    public void put(Map<K, ? super V> map, V value) {
        map.put(mkey, value);
    }
    
    public void putAll(MultiMap<K, ? super V> map, Collection<? extends V> value) {
        map.putAll(mkey, value);
    }
    
    public void put(Map<K, ? super V> map, Optional<V> value) {
        if(value.isPresent()) {
            map.put(mkey, value.get());
        }
    }
    
    public boolean contains(Map<K, ? super V> map) {
        return map.containsKey(mkey);
    }

    @Override
    public V apply(Map<K, ? extends V> source) {
        return get(source);
    }

    public Entry<K,V> entry(Map<K, V> map) {
        if(map instanceof TreeMap) {
            return EntryAccess.TREE.$apply(map, mkey);
        }
        if(EntryAccess.CS_TREE_SUBMAP.isInstance(map)) {
            Object treemap = EntryAccess.TREE_FIELD.get(map);
            return EntryAccess.TREE.$apply(treemap, mkey);
        }
        if(map instanceof HashMap) {
            return EntryAccess.HASH.$apply(map, mkey);
        }
        if(map instanceof WeakHashMap) {
            return EntryAccess.WEAK.$apply(map, mkey);
        }
        return new DVEntry<>(itype, mkey, map);
    }
    
    public Wrapper<V> wrapper(Map<K, V> map) {
        if(map instanceof TreeMap) {
            return new DVWrapper<>(itype, EntryAccess.TREE.$apply(map, mkey));
        }
        if(EntryAccess.CS_TREE_SUBMAP.isInstance(map)) {
            Object treemap = EntryAccess.TREE_FIELD.get(map);
            return new DVWrapper<>(itype, EntryAccess.TREE.$apply(treemap, mkey));
        }
        if(map instanceof HashMap) {
            return new DVWrapper<>(itype, EntryAccess.HASH.$apply(map, mkey));
        }
        if(map instanceof WeakHashMap) {
            return new DVWrapper<>(itype, EntryAccess.WEAK.$apply(map, mkey));
        }
        return new DVEntry<>(itype, mkey, map);
    }

    static final class EntryAccess { // NOPMD - lazy init idiom

        static final IMethod TREE;
        static final IMethod HASH;
        static final IMethod WEAK;

        static final IClass<?> CS_TREE_SUBMAP;
        static final IField TREE_FIELD;

        static {
            TREE = method(TreeMap.class);
            CS_TREE_SUBMAP = IClass.typeinfo("java.util.TreeMap$NavigableSubMap");
            TREE_FIELD = CS_TREE_SUBMAP.field("m").get();
            
            HASH = method(HashMap.class);
            WEAK = method(WeakHashMap.class);
        }

        private static IMethod method(Class<?> type) {
            return IClass.typeinfo(type).methods().require(IAttribute.DECLARED).require("getEntry").first().get();
        }

    }
    
    private static final class DVWrapper<K,V> implements Wrapper<V> {
        
        private final IClass<V> itype;
        private final Entry<K,V> entry;

        public DVWrapper(IClass<V> itype, Entry<K,V> entry) {
            this.itype = itype;
            this.entry = (Entry<K, V>)entry;
        }
        
        @Override
        public V get() {
            return entry.getValue();
        }

        @Override
        public void set(V value) {
            entry.setValue(value);
        }

        @Override
        public IClass<V> type() {
            return itype;
        }
        
    }

    private static final class DVEntry<K,V> implements Map.Entry<K, V>, Wrapper<V> {

        private final IClass<V> itype;
        private final K key;
        private final Map<K,V> map;

        public DVEntry(IClass<V> itype, K key, Map<K, V> map) {
            this.itype = itype;
            this.key = key;
            this.map = map;
        }

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

        @Override
        public V getValue() {
            return map.get(key);
        }

        @Override
        public V setValue(V value) {
            return map.put(key, value);
        }

        @Override
        public V get() {
            return getValue();
        }

        @Override
        public void set(V value) {
            setValue(value);
        }

        @Override
        public IClass<V> type() {
            return itype;
        }
        
    }

}
