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

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import net.ranides.assira.collection.sets.ASet;
import net.ranides.assira.collection.AIntCollection;
import net.ranides.assira.collection.IntCollection;
import net.ranides.assira.collection.iterators.IntIterator;
import net.ranides.assira.generic.HashUtils;
import net.ranides.assira.generic.CompareUtils;

/**
 * An abstract class for maps with primitive int values.
 */
public abstract class ALookup<K> implements Lookup<K> {

    private static final long serialVersionUID = 2L;
    
    protected int defRetValue;

    protected ALookup() {
        // do nothing
    }
	
    @Override
    public boolean containsValue(Object value) {
        return containsValue(((Integer) value).intValue());
    }


    @SuppressWarnings("unchecked")
    @Override
    public void putAll(Map<? extends K, ? extends Integer> values) {
        int n = values.size();
        Iterator<? extends Map.Entry<? extends K, ? extends Integer>> itr = values.entrySet().iterator();
        if (values instanceof Lookup) {
            LookupEntry<? extends K> entry;
            while (n-- != 0) {
                entry = (LookupEntry<? extends K>) itr.next();
                put(entry.getKey(), entry.getIntValue());
            }
        } else {
            Map.Entry<? extends K, ? extends Integer> entry;
            while (n-- != 0) {
                entry = itr.next();
                put(entry.getKey(), entry.getValue());
            }
        }
    }

    @Override
    public boolean isEmpty() {
        return size() == 0;
    }

    @Override
    public Set<K> keySet() {
        return new KeySet();
    }

    @Override
    public IntCollection values() {
        return new ValuesCollection();
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    @Override
    public Set<Map.Entry<K, Integer>> entrySet() {
        return new EntrySetAdapter<>(fastEntrySet());
    }
    
    /**
     * Returns a hash code for this map.
     *
     * The hash code of a map is computed by summing the hash codes of its
     * entries.
     *
     * @return a hash code for this map.
     */
    @Override
    public int hashCode() {
        int h = 0, n = size();
        Iterator<? extends Map.Entry<K, Integer>> i = entrySet().iterator();
        while (n-- != 0) {
            h += i.next().hashCode();
        }
        return h;
    }

    @Override
    public boolean equals(Object other) {
        if (other == this) {
            return true;
        }
        if (!(other instanceof Map)) {
            return false;
        }
        Map<?, ?> map = (Map<?, ?>) other;
        if (map.size() != size()) {
            return false;
        }
        return entrySet().containsAll(map.entrySet());
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder();
        Iterator<? extends Map.Entry<K, Integer>> i = entrySet().iterator();
        int n = size();
        LookupEntry<K> entry;
        boolean first = true;
        result.append('{');
        while (n-- != 0) {
            if (first) {
                first = false;
            } else {
                result.append(", ");
            }
            entry = (LookupEntry<K>) i.next();
            if (this == entry.getKey()) {
                result.append("(this map)");
            } else {
                result.append(String.valueOf(entry.getKey()));
            }
            result.append("=>");
            result.append(String.valueOf(entry.getIntValue()));
        }
        result.append('}');
        return result.toString();
    }

    @Override
    public void defaultReturnValue(int rv) {
        defRetValue = rv;
    }

    @Override
    public int defaultReturnValue() {
        return defRetValue;
    }

    @Override
    public int put(K key, int value) {
        throw new UnsupportedOperationException();
    }

    @Override
    public int removeInt(Object key) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void clear() {
        throw new UnsupportedOperationException();
    }

    /**
     * Delegates to the corresponding type-specific method, taking care of
     * returning
     * <code>null</code> on a missing key.
     *
     * <P>This method must check whether the provided key is in the map using
     * <code>containsKey()</code>. Thus, it probes the map <em>twice</em>.
     * Implementors of subclasses should override it with a more efficient
     * method.
     */
    @Override
    @SuppressWarnings("element-type-mismatch")
    public Integer get(Object key) {
        return containsKey(key) ? getInt(key) : null;
    }

    /**
     * Delegates to the corresponding type-specific method, taking care of
     * returning
     * <code>null</code> on a missing key.
     *
     * <P>This method must check whether the provided key is in the map using
     * <code>containsKey()</code>. Thus, it probes the map <em>twice</em>.
     * Implementors of subclasses should override it with a more efficient
     * method.
     */
    @Override
    public Integer put(K key, Integer value) {
        boolean containsKey = containsKey(key);
        int prev = put(key, value.intValue());
        return containsKey ? prev : null;
    }

    /**
     * Delegates to the corresponding type-specific method, taking care of
     * returning
     * <code>null</code> on a missing key.
     *
     * <P>This method must check whether the provided key is in the map using
     * <code>containsKey()</code>. Thus, it probes the map <em>twice</em>.
     * Implementors of subclasses should override it with a more efficient
     * method.
     */
    @Override
    @SuppressWarnings("element-type-mismatch")
    public Integer remove(Object key) {
        return containsKey(key) ? removeInt(key) : null;
    }
    
    /**
     * This class provides a basic but complete type-specific entry class for
     * all those maps implementations that do not have entries on their own
     * (e.g., most immutable maps).
     *
     * <P>This class does not implement
     * {@link java.util.Map.Entry#setValue(Object) setValue()}, as the
     * modification would not be reflected in the base map.
     * 
     */
    public static class BasicEntry<K> implements LookupEntry<K> {

        private final K key;
        private final int value;

        public BasicEntry(K key, Integer value) {
            this.key = key;
            this.value = value;
        }

        public BasicEntry(K key, int value) {
            this.key = key;
            this.value = value;
        }

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

        @Override
        public Integer getValue() {
            return value;
        }

        @Override
        public int getIntValue() {
            return value;
        }

        @Override
        public int setValue(int value) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Integer setValue(Integer value) {
            return setValue(value.intValue());
        }

        @Override
        public boolean equals(Object other) {
            if (!(other instanceof Map.Entry)) {
                return false;
            }
            Map.Entry<?, ?> entry = (Map.Entry<?, ?>) other;
            return CompareUtils.equals(key, entry.getKey()) && CompareUtils.equals(value, entry.getValue());
        }

        @Override
        public int hashCode() {
            return HashUtils.hashPair(key, value);
        }

        @Override
        public String toString() {
            return key + "->" + value;
        }
    }
    
    public static class EntryWrapper<K> implements LookupEntry<K> {

        private final Entry<K, Integer> that;

        public EntryWrapper(Entry<K, Integer> entry) {
            this.that = entry;
        }

        @Override
        public K getKey() {
            return that.getKey();
        }

        @Override
        public Integer getValue() {
            return that.getValue();
        }

        @Override
        public int getIntValue() {
            return that.getValue();
        }

        @Override
        public int setValue(int value) {
            return that.setValue(value);
        }

        @Override
        public Integer setValue(Integer value) {
            return that.setValue(value);
        }

        @Override
        public boolean equals(Object other) {
            if (other instanceof EntryWrapper) {
                return that.equals(((EntryWrapper<?>)other).that);
            }
            if (!(other instanceof Map.Entry)) {
                return false;
            }
            Map.Entry<?, ?> entry = (Map.Entry<?, ?>) other;
            return CompareUtils.equals(entry.getKey(), that.getKey()) && CompareUtils.equals(entry.getValue(), that.getValue());
        }

        @Override
        public int hashCode() {
            return HashUtils.hashPair(that.getKey(), that.getValue());
        }

        @Override
        public String toString() {
            return that.getKey() + "->" + that.getValue();
        }
    }
    
    private static final class EntrySetAdapter<K> extends ASet<Map.Entry<K, Integer>> {
        
        private final Set<LookupEntry<K>> that;

        public EntrySetAdapter(Set<LookupEntry<K>> that) {
            this.that = that;
        }

        @SuppressWarnings("unchecked")
        @Override
        public Iterator<Entry<K, Integer>> iterator() {
            return (Iterator)that.iterator();
        }

        @Override
        public int size() {
            return that.size();
        }

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

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

        @Override
        public boolean remove(Object value) {
            return that.remove(value);
        }

        @Override
        public boolean contains(Object value) {
            return that.contains(value);
        }
    
    }
    
    protected class KeySet extends ASet<K> {
        @Override
        @SuppressWarnings("element-type-mismatch")
        public boolean contains(Object key) {
            return containsKey(key);
        }

        @Override
        public int size() {
            return ALookup.this.size();
        }

        @Override
        public boolean remove(Object value) {
            int prev = ALookup.this.size();
            ALookup.this.remove(value);
            return prev != ALookup.this.size();
        }
        
        @Override
        public void clear() {
            ALookup.this.clear();
        }

        @Override
        public Iterator<K> iterator() {
            return new KeyIterator();
        }
    }
    
    protected class KeyIterator implements Iterator<K> {
        
        private final Iterator<Map.Entry<K, Integer>> delegate = entrySet().iterator();

        @Override
        public K next() {
            return ((LookupEntry<K>) delegate.next()).getKey();
        }

        @Override
        public boolean hasNext() {
            return delegate.hasNext();
        }

        @Override
        public void remove() {
            delegate.remove();
        }
    }

    protected class ValuesCollection extends AIntCollection {
        @Override
        public boolean contains(int k) {
            return containsValue(k);
        }

        @Override
        public int size() {
            return ALookup.this.size();
        }

        @Override
        public void clear() {
            ALookup.this.clear();
        }

        @Override
        public IntIterator iterator() {
            return new ValuesIterator();
        }
    }
    
    protected class ValuesIterator implements IntIterator {
        
        private final Iterator<Map.Entry<K, Integer>> delegate = entrySet().iterator();

        @Override
        public int nextInt() {
            return ((LookupEntry<K>) delegate.next()).getIntValue();
        }

        @Override
        public boolean hasNext() {
            return delegate.hasNext();
        }

        @Override
        public void remove() {
            delegate.remove();
        }

    }

}
