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

import java.util.*;
import java.util.Map.Entry;

import net.ranides.assira.collection.maps.OpenMultiMap;
import net.ranides.assira.collection.sets.SetUtils;
import net.ranides.assira.collection.utils.HashCollection;
import net.ranides.assira.events.EventRouter;
import net.ranides.assira.reflection.IClass;
import net.ranides.assira.reflection.util.ClassUtils;

/**
 * GenricMap implementation which uses internally OpenMultiMap
 *
 * @param <K> K
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
@SuppressWarnings("unchecked")
public class OpenGenericMap<K> implements GenericMap<K> {
    
    protected Map<GenericKey<K,?>, Object> map;

    /**
     * Creates new empty map
     */
    public OpenGenericMap() {
        this(HashCollection.INITIAL_SIZE, HashCollection.LOAD_FACTOR);
    }

    /**
     * Creates new empty map with provided capacity
     * @param capacity capacity
     */
    public OpenGenericMap(int capacity) {
        this(capacity, HashCollection.LOAD_FACTOR);
    }

    /**
     * Creates new empty map with provided capacity and load factor.
     *
     * @param capacity capacity
     * @param factor factor
     */
    public OpenGenericMap(int capacity, float factor) {
        this(new OpenMultiMap<>(capacity, factor));
    }

    protected OpenGenericMap(Map<GenericKey<K,?>, Object> map) {
        this.map = map;
    }

    /**
     * Creates new map with values copied from provided source map
     * @param m m
     */
    public OpenGenericMap(GenericMap<? extends K> m) {
        this(m.size());
        putAll(m);
    }

    protected OpenGenericMap(boolean empty) {
        assert empty : "this is constructor for empty map";
        this.map = Collections.emptyMap();
    }

    @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) && key.type().isInstance(map.get(key));
    }

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

    @Override
    public <V> V get(GenericKey<K, V> key) {
        V value = (V) map.get(key);
        return key.type().isInstance(value) ? value : null;
    }

    @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 void putAll(GenericMap<? extends K> m) {
        if(m instanceof OpenGenericMap<?>) {
            map.putAll(((OpenGenericMap)m).map);
        } else {
            GenericMap.super.putAll(m);
        }
    }

    @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 String toString() {
        return map.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        OpenGenericMap<?> that = (OpenGenericMap<?>) o;
        return map.equals(that.map);
    }

    @Override
    public int hashCode() {
        return Objects.hash(map);
    }

    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<?> getType() {
            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));
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof GenericEntry<?>)) return false;
            GenericEntry<?> cgEntry = (GenericEntry<?>) o;
            return entry.getKey().equals(cgEntry.getKey()) &&
                    Objects.equals(entry.getValue(), cgEntry.getValue());
        }

        @Override
        public int hashCode() {
            return Objects.hash(entry);
        }

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