class _Maps {

    public static <K, V> AliasMap<K, V> newAliasMap(
            final Supplier<Map<K, V>> mapFactory){
        
        _With.requires(mapFactory, "mapFactory");

        return new AliasMap<K, V>() {

            final Map<K, V> delegate = mapFactory.get();

            @Override public int size() { return delegate.size(); }
            @Override public boolean isEmpty() { return delegate.isEmpty(); }
            @Override public boolean containsValue(Object value) { return delegate.containsValue(value); }
            @Override public Set<K> keySet() { return delegate.keySet(); }
            @Override public Collection<V> values() { return delegate.values();   }
            @Override public Set<Entry<K, V>> entrySet() { return delegate.entrySet(); }
            
            @Override 
            public V put(K key, V value) { 
                return this.put(key, Can.empty(), value); 
            }
            
            @Override 
            public void putAll(Map<? extends K, ? extends V> other) { 
                if(!_NullSafe.isEmpty(other)) {
                    other.forEach((k, v)->this.put(k, v)); 
                }
            }
            
            @Override
            public V put(K key, Can<K> aliases, V value) {
                putAliasKeys(key, aliases, /*re-map*/ false);
                return delegate.put(key, value);
            }
            
            @Override
            public V remap(K key, Can<K> aliases, V value) {
                putAliasKeys(key, aliases, /*re-map*/ true);
                return delegate.put(key, value);
            }
            
            @Override
            public boolean containsKey(Object keyOrAliasKey) {
                return delegate.containsKey(keyOrAliasKey) ||
                    containsAliasKey(keyOrAliasKey);
            }
            

            @Override
            public V get(Object keyOrAliasKey) {
                val v = delegate.get(keyOrAliasKey);
                if(v!=null) {
                    return v;
                }
                return getByAliasKey(keyOrAliasKey);
            }
            
            @Override 
            public V remove(Object key) { 
                removeAliasKeysOf(key);
                return delegate.remove(key); 
            }
            
            @Override 
            public void clear() {
                delegate.clear(); 
                clearAliasKeys();
            }
            
            // -- HELPER
            
            private final Map<K, KeyPair<K>> pairByAliasKey = _Maps.newHashMap();
            
            private void putAliasKeys(K key, Can<K> aliasKeys, boolean remap) {
                if(aliasKeys.isNotEmpty()) {
                    val keyPair = KeyPair.of(key, aliasKeys);
                    for(val aliasKey : aliasKeys) {
                        
                        val existingKeyPair = pairByAliasKey.put(aliasKey, keyPair);
                        if(existingKeyPair!=null && !remap) {
                            
                            throw _Exceptions.illegalArgument(
                                    "alias key collision on alias %s: existing-key=%s, new-key=%s", 
                                    aliasKey, existingKeyPair.key, keyPair.key);
                        }
                    }
                }
            }
            
            private V getByAliasKey(Object aliasKey) {
                val keyPair = pairByAliasKey.get(aliasKey);
                if(keyPair!=null) {
                    return delegate.get(keyPair.getKey()); 
                }
                return null;
            }

            private boolean containsAliasKey(Object aliasKey) {
                return pairByAliasKey.containsKey(aliasKey);
            }
            
            private void removeAliasKeysOf(final Object key) {
                //XXX this implementation is slow for large alias maps, since we traverse the entire map
                pairByAliasKey.entrySet()
                .removeIf(entry->{
                    val keyPair = entry.getValue();
                    return keyPair.getKey().equals(key);
                });
                
            }
            
            private void clearAliasKeys() {
                pairByAliasKey.clear();
            }
            

        };
    }

    private  _Maps();

}