/*
 * @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 java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
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, null);
    }
    
    public static <TV, RV> IntMap<RV> map(IntMap<TV> map, Function<? super TV, ? extends RV> function, BiFunction<Integer, ? super RV, ? extends RV> inserter) {
        return new MapAdapter<>(map, function, inserter);
    }
    
    public static <TV, RV> IntMap<RV> map(IntMap<TV> map, ProjectionFunction<TV, RV> function) {
        return new MapProjection<>(map, function);
    }
    
    public static <TV, RV> IntMultiMap<RV> map(IntMultiMap<TV> map, Function<? super TV, ? extends RV> function) {
        return new MMapAdapter<>(map, function, null);
    }
    
    public static <TV, RV> IntMultiMap<RV> map(IntMultiMap<TV> map, Function<? super TV, ? extends RV> function, BiFunction<Integer, ? super RV, ? extends RV> inserter) {
        return new MMapAdapter<>(map, function, inserter);
    }
    
    public static <TV, RV> IntMultiMap<RV> map(IntMultiMap<TV> map, ProjectionFunction<TV, RV> function) {
        return new MMapProjection<>(map, function);
    }
    
    public static <TV, RV> IntMap<RV> wrap(IntMap<TV> map, Function<? super TV, ? extends RV> getter, BiFunction<? super TV, ? super RV, ? extends RV> setter) {
        return new MapWrapper<>(map, getter, setter);
    }
    
    public static <K, TV, RV> IntMultiMap<RV> wrap(IntMultiMap<TV> map, Function<? super TV, ? extends RV> getter, BiFunction<? super TV, ? super RV, ? extends RV> setter) {
        return new MMapWrapper<>(map, getter, setter);
    }
    
    private static class MapAdapter<TV, RV> extends AIntMap<RV> {
        
        private static final long serialVersionUID = 1L;
        
        @SuppressWarnings("unchecked")
        private static final BiFunction NSE = (Serializable & BiFunction)(a,b) -> { 
            throw new UnsupportedOperationException(); 
        };

        protected final IntMap<TV> content;
        protected final Function<? super TV, ? extends RV> function;
        protected final BiFunction<Integer, ? super RV, ? extends RV> inserter;

        public MapAdapter(IntMap<TV> content, Function<? super TV, ? extends RV> function, BiFunction<Integer, ? super RV, ? extends RV> inserter) {
            this.content = content;
            this.function = function;
            this.inserter = null != inserter ? inserter : NSE;
        }

        @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));
        }
        
        @Override
        public RV put(int key, RV value) {
            return inserter.apply(key, value);
        }
        

        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) {
                return inserter.apply(delegate.getKey(), 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")
                    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<TV, RV> extends AIntMap<RV> {
        
        private static final long serialVersionUID = 1L;

        protected final IntMap<TV> content;
        protected 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(int 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")
                    EntryProjection other = (EntryProjection) obj;
                    return delegate.equals(other.delegate);
                } else {
                    @SuppressWarnings("unchecked")
                    Map.Entry<Integer,RV> other = (Map.Entry<Integer,RV>)obj;
                    return CompareUtils.equals(getKey(), other.getKey()) && CompareUtils.equals(getValue(), other.getValue());
                }
            }
            
        }

    }
    
    private static class MMapAdapter<TV, RV> extends MapAdapter<TV, RV> implements IntMultiMap<RV> {
        
        private static final long serialVersionUID = 1L;
        
        private final IntMultiMap<TV> mcontent;

        public MMapAdapter(IntMultiMap<TV> mcontent, Function<? super TV, ? extends RV> function, BiFunction<Integer, ? super RV, ? extends RV> setter) {
            super(mcontent, function, setter);
            this.mcontent = mcontent;
        }
        
        @Override
        public Collection<RV> getAll(int key) {
            return CollectionUtils.map(mcontent.getAll(key), function);
        }
        
    }
    
    private static class MMapProjection<TV, RV> extends MapProjection<TV,RV> implements IntMultiMap<RV> {
        
        private static final long serialVersionUID = 1L;
        
        private final IntMultiMap<TV> mcontent;
        
        public MMapProjection(IntMultiMap<TV> content, ProjectionFunction<TV, RV> function) {
            super(content, function);
            this.mcontent = content;
        }
        
        @Override
        public Collection<RV> getAll(int key) {
            return CollectionUtils.map(mcontent.getAll(key), function);
        }

    }
    
    private static class MMapWrapper<TV, RV> extends MapWrapper<TV,RV> implements IntMultiMap<RV> { 
        
        private static final long serialVersionUID = 1L;

        private final IntMultiMap<TV> mcontent;
                
        public MMapWrapper(IntMultiMap<TV> content, Function<? super TV, ? extends RV> getter, BiFunction<? super TV, ? super RV, ? extends RV> setter) {
            super(content, getter, setter);
            this.mcontent = content;
        }

        @Override
        public Collection<RV> getAll(int key) {
            return CollectionUtils.map(mcontent.getAll(key), getter);
        }
        
    }
    
    private static class MapWrapper<TV, RV> extends AIntMap<RV> implements IntMap<RV> { 

        private static final long serialVersionUID = 1L;

        protected final IntMap<TV> content;        
        protected final Function<? super TV, ? extends RV> getter;
        protected final BiFunction<? super TV, ? super RV, ? extends RV> setter;

        public MapWrapper(IntMap<TV> content, Function<? super TV, ? extends RV> getter, BiFunction<? super TV, ? super RV, ? extends RV> setter) {
            this.content = content;
            this.getter = getter;
            this.setter = setter;
        }

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

        @Override
        public boolean containsKey(int key) {
            return content.containsKey(key);
        }

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

        @Override
        public RV get(int key) {
            TV var = content.get(key);
            return null!=var ? getter.apply(var) : null;
        }

        @Override
        public RV put(int key, RV value) {
            TV var = content.get(key);
            return null!=var ? setter.apply(var, value) : null;
        }
        
        @Override
        public IntSet keySet() {
            return content.keySet();
        }

        @Override
        public Collection<RV> values() {
            return CollectionUtils.map(content.values(), getter);
        }
        
        @Override
        public Set<IntEntry<RV>> fastEntrySet() {
            return SetUtils.map(content.fastEntrySet(), e -> new EntryWrapper(e));
        }
        
        private final class EntryWrapper implements IntEntry<RV> {
            
            private final IntEntry<TV> delegate;

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

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

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

            @Override
            public RV setValue(RV value) {
                return setter.apply(delegate.getValue(), 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;
                }
                @SuppressWarnings("unchecked")
                Map.Entry<Integer,RV> other = (Map.Entry<Integer,RV>)obj;
                return CompareUtils.equals(getKey(), other.getKey()) 
                    && CompareUtils.equals(getValue(), other.getValue());
            }
            
        }
        
    }
    
}
