/*
 * @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.io.Serializable;
import net.ranides.assira.collection.sets.IntSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import net.ranides.assira.collection.ACollection;
import net.ranides.assira.collection.iterators.IntIterator;
import net.ranides.assira.collection.sets.AIntSet;
import net.ranides.assira.generic.HashUtils;
import net.ranides.assira.generic.CompareUtils;

/**
 * An abstract base class for maps with primitive int keys. 
 * Designed to avoid autoboxing.
 *
 * <P>Optional operations just throw an {@link
 * UnsupportedOperationException}. Generic versions of accessors delegate to the
 * corresponding type-specific counterparts following the interface rules (they
 * take care of returning <code>null</code> on a missing key).
 *
 * <P>As a further help, this class provides a {@link BasicEntry BasicEntry}
 * inner class that implements a type-specific version of
 * {@link java.util.Map.Entry}; it is particularly useful for those classes that
 * do not implement their own entries (e.g., most immutable maps).
 */
public abstract class AIntMap<V> implements IntMap<V>, Serializable {

    private static final long serialVersionUID = 2L;

    protected AIntMap() {
        // do nothing
    }


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

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

    /**
     * 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<V> implements IntEntry<V> {

        public int key;
        public V value;

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

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

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

        @Override
        public int getIntKey() {
            return key;
        }

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

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

        @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;
        }
    }

    /**
     * Returns a type-specific-set view of the keys of this map.
     * @return a set view of the keys of this map
     */
    @Override
    public IntSet keySet() {

        return new AIntSet() {
            @Override
            public boolean contains(int k) {
                return containsKey(k);
            }

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

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

            @Override
            public boolean remove(int key) {
                int prev = AIntMap.this.size();
                AIntMap.this.remove(key);
                return prev != AIntMap.this.size();
            }

            @Override
            public IntIterator iterator() {
                return new IntIterator() {
                    final Iterator<Map.Entry<Integer, V>> i = entrySet().iterator();

                    @Override
                    public int nextInt() {
                        return ((IntEntry<V>) i.next()).getIntKey();
                    }

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

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

    @Override
    public Collection<V> values() {
        return new ACollection<V>() {
            @Override
            @SuppressWarnings("element-type-mismatch")
            public boolean contains(Object value) {
                return containsValue(value);
            }

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

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

            @Override
            public Iterator<V> iterator() {
                return new Iterator<V>() {
                    final Iterator<Map.Entry<Integer, V>> i = entrySet().iterator();

                    @Override
                    public V next() {
                        return ((IntEntry<V>) i.next()).getValue();
                    }

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

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

    @SuppressWarnings({"unchecked", "rawtypes"})
    @Override
    public Set<Map.Entry<Integer, V>> entrySet() {
        return (Set) 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();
        final Iterator<? extends Map.Entry<Integer, V>> 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() {
        final StringBuilder result = new StringBuilder();
        final Iterator<? extends Map.Entry<Integer, V>> itr = entrySet().iterator();
        int n = size();
        IntEntry<V> entry;
        boolean first = true;
        result.append('{');
        while (n-- != 0) {
            if (first) {
                first = false;
            } else {
                result.append(", ");
            }
            entry = (IntEntry<V>) itr.next();
            result.append(String.valueOf(entry.getIntKey()));
            result.append("=>");
            if (this == entry.getValue()) {
                result.append("(this map)");
            } else {
                result.append(String.valueOf(entry.getValue()));
            }
        }
        result.append('}');
        return result.toString();
    }

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

    @Override
    public V remove(int key) {
        throw new UnsupportedOperationException();
    }

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

    @Override
    public final boolean containsKey(Object key) {
        return containsKey(((Integer) key).intValue());
    }

    @Override
    public final V get(Object key) {
        return get( ((Integer)key).intValue() );
    }

    @Override
    public final V put(Integer key, V value) {
        return put( key.intValue(), value);
    }

    @Override
    public final V remove(Object key) {
        if(!(key instanceof Integer)) {
            return null;
        }
        return remove( ((Integer)key).intValue() );
    }

    @Override
    public final boolean remove(Object key, Object value) {
        if(!(key instanceof Integer)) {
            return false;
        }
        return remove( ((Integer)key).intValue(), value);
    }

}
