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

import java.util.Collection;
import java.util.Map.Entry;
import java.util.Set;
import net.ranides.assira.collection.sets.SetUtils;
import net.ranides.assira.collection.utils.HashCollection;
import net.ranides.assira.reflection.IClass;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
@SuppressWarnings("unchecked")
public class GenericHashMap<K> implements GenericMap<K> {
    
    private final MultiMap<GenericKey<K,?>, Object> map;
    
    public GenericHashMap() {
        this(HashCollection.INITIAL_SIZE, HashCollection.LOAD_FACTOR);
    }
    
    public GenericHashMap(int capacity) {
        this(capacity, HashCollection.LOAD_FACTOR);
    }
    
    public GenericHashMap(int capacity, float factor) {
        this.map = new HashMultiMap<>(capacity, factor);
    }
    
    public GenericHashMap(GenericMap<? extends K> m) {
        this(m.size());
        putAll(m);
    }

    private GenericHashMap(HashMultiMap<GenericKey<K, ?>, Object> map) {
        this.map = map;
    }
    
    @Override
    public int size() {
        return map.size();
    }

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

    @Override
    public boolean containsKey(GenericKey<K, ?> key) {
        return map.containsKey(key);
    }

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

    @Override
    public <V> V get(GenericKey<K, V> key) {
        return (V)map.get(key);
    }

    @Override
    public <V> V put(GenericKey<K, V> key, V value) {
        return (V)map.put(key, value);
    }

    @Override
    public <V> V remove(GenericKey<K, V> key) {
        return (V)map.remove(key);
    }

    @Override
    public final void putAll(GenericMap<? extends K> m) {
        if(m instanceof GenericHashMap<?>) {
            map.putAll(((GenericHashMap)m).map);
        } else { 
            iputAll(m);
        }
    }

    private void iputAll(GenericMap<? extends K> m) {
        for(GenericEntry<? extends K> e : m.entrySet()) {
            GenericKey key1 = e.getKey();
            map.put(key1, e.getValue());
        }
    }

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

    @Override
    public Set<GenericKey<K, ?>> keySet() {
        return map.keySet();
    }

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

    @Override
    public Set<GenericEntry<K>> entrySet() {
        return SetUtils.map(map.entrySet(), CGEntry::new);
    }

    @Override
    public <V> Collection<V> getAll(GenericKey<K, V> key) {
        return (Collection<V>)map.getAll(key);
    }
    
    private static final class CGEntry<K> implements GenericEntry<K> {
        
        private final Entry<GenericKey<K,?>, Object> entry;

        public CGEntry(Entry<GenericKey<K,?>, Object> entry) {
            this.entry = entry;
        }

        @Override
        public IClass<?> type() {
            return entry.getKey().type();
        }

        @Override
        public GenericKey<K, ?> getKey() {
            return entry.getKey();
        }

        @Override
        public Object getValue() {
            return entry.getValue();
        }

        @Override
        public void setValue(Object value) {
            entry.setValue(entry.getKey().type().cast(value));
        }
        
    }
    
}
