/*
 * @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.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import net.ranides.assira.collection.CollectionUtils;
import net.ranides.assira.collection.sets.IntSet;
import net.ranides.assira.collection.sets.SetUtils;
import net.ranides.assira.functional.ProjectionFunction;
import net.ranides.assira.generic.CompareUtils;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class IntMapUtils {
    
    private IntMapUtils() {
        /* utility class */
    }
    
    public static <TV, RV> IntMap<RV> map(IntMap<TV> map, Function<? super TV, ? extends RV> function) {
        return new MapAdapter<>(map, function);
    }
    
    public static <TV, RV> IntMap<RV> map(IntMap<TV> map, ProjectionFunction<TV, RV> function) {
        return new MapProjection<>(map, function);
    }
    
    private static class MapAdapter<TV, RV> extends AIntMap<RV> {

        private final IntMap<TV> content;
        private final Function<? super TV, ? extends RV> function;

        public MapAdapter(IntMap<TV> content, Function<? super TV, ? extends RV> function) {
            this.content = content;
            this.function = function;
        }

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

        @Override
        public boolean containsKey(int key) {
            return content.containsKey(key);
        }
        
        @Override
        public boolean containsValue(Object value) {
            return content.values().stream().map(function).anyMatch(rv -> CompareUtils.equals(rv, value));
        }

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

        @Override
        public RV remove(int key) {
            TV prev = content.remove(key);
            return prev==null ? null : function.apply(prev);
        }

        @Override
        public IntSet keySet() {
            return content.keySet();
        }

        @Override
        public Collection<RV> values() {
            return CollectionUtils.map(content.values(), function);
        }
        
        @Override
        public Set<IntEntry<RV>> fastEntrySet() {
            return SetUtils.map(content.fastEntrySet(), e -> new EntryAdapter(e));
        }

        @Override
        public RV get(int key) {
            return function.apply(content.get(key));
        }

        private final class EntryAdapter implements IntEntry<RV> {
            
            private final IntEntry<TV> delegate;

            public EntryAdapter(IntEntry<TV> delegate) {
                this.delegate = delegate;
            }

            @Override
            public int getIntKey() {
                return delegate.getIntKey();
            }

            @Override
            public RV getValue() {
                return function.apply(delegate.getValue());
            }

            @Override
            public RV setValue(RV value) {
                throw new UnsupportedOperationException();
            }
            
            @Override
            public int hashCode() {
                return delegate.hashCode();
            }

            @Override
            public boolean equals(Object obj) {
                if (this == obj) {
                    return true;
                }
                if (!(obj instanceof Map.Entry)) {
                    return false;
                }
                if (getClass() == obj.getClass()) {
                    @SuppressWarnings("unchecked")
                    EntryAdapter other = (EntryAdapter) obj;
                    return delegate.equals(other.delegate);
                } else {
                    @SuppressWarnings("unchecked")
                    Entry<Integer,RV> other = (Entry<Integer,RV>)obj;
                    return CompareUtils.equals(getKey(), other.getKey()) && CompareUtils.equals(getValue(), other.getValue());
                }
            }
            
        }

    }
    
    private static class MapProjection<K, TV, RV> extends AIntMap<RV> {

        private final IntMap<TV> content;
        private final ProjectionFunction<TV, RV> function;

        public MapProjection(IntMap<TV> content, ProjectionFunction<TV, RV> function) {
            this.content = content;
            this.function = function;
        }

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

        @Override
        public boolean containsKey(int key) {
            return content.containsKey(key);
        }
        
        @Override
        @SuppressWarnings("unchecked")
        public boolean containsValue(Object value) {
            Object v;
            try {
                v = function.invert((RV)value);
            } catch(ClassCastException _ex) {
                return false;
            }
            return content.containsValue(v);
        }

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

        @Override
        public RV remove(int key) {
            TV prev = content.remove(key);
            return prev==null ? null : function.apply(prev);
        }

        @Override
        public IntSet keySet() {
            return content.keySet();
        }

        @Override
        public Collection<RV> values() {
            return CollectionUtils.map(content.values(), function);
        }
        
        @Override
        public Set<IntEntry<RV>> fastEntrySet() {
            return SetUtils.map(content.fastEntrySet(), e -> new EntryProjection(e));
        }

        @Override
        public RV get(int key) {
            return function.apply(content.get(key));
        }

        @Override
        public RV put(int key, RV value) {
            return function.apply(content.put(key, function.invert(value)));
        }

        @Override
        @SuppressWarnings("unchecked")
        public boolean remove(Object key, Object value) {
            Object v;
            try {
                v = function.invert((RV)value);
            } catch(ClassCastException _e) {
                return false;
            }
            return content.remove(key, v);
        }

        private final class EntryProjection implements IntEntry<RV> {
            
            private final IntEntry<TV> delegate;

            public EntryProjection(IntEntry<TV> delegate) {
                this.delegate = delegate;
            }

            @Override
            public int getIntKey() {
                return delegate.getIntKey();
            }

            @Override
            public RV getValue() {
                return function.apply(delegate.getValue());
            }

            @Override
            public RV setValue(RV value) {
                return function.apply(content.put(getIntKey(), function.invert(value)));
            }
            
            @Override
            public int hashCode() {
                return delegate.hashCode();
            }

            @Override
            public boolean equals(Object obj) {
                if (this == obj) {
                    return true;
                }
                if (!(obj instanceof Map.Entry)) {
                    return false;
                }
                if (getClass() == obj.getClass()) {
                    @SuppressWarnings("unchecked")
                    MapProjection.EntryProjection other = (MapProjection.EntryProjection) obj;
                    return delegate.equals(other.delegate);
                } else {
                    @SuppressWarnings("unchecked")
                    Map.Entry<K,RV> other = (Map.Entry<K,RV>)obj;
                    return CompareUtils.equals(getKey(), other.getKey()) && CompareUtils.equals(getValue(), other.getValue());
                }
            }
            
        }

    }
    
}
