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

import net.ranides.assira.collection.HashComparator;
import net.ranides.assira.collection.lookups.Lookup.LookupEntry;
import net.ranides.assira.collection.lookups.OpenLookup;
import net.ranides.assira.collection.maps.IntMap.IntEntry;
import net.ranides.assira.generic.CompareUtils;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

/**
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public abstract class TMap<K,V> implements Serializable {
    
    private final TCollection<K> kgen = new TCollection<K>() {
        
        @Override
        public K value(int index, int n) {
            return TMap.this.item(index, n).key();
        }

        @Override
        protected int cmp(K a, K b) {
            return TMap.this.cmp(a, b);
        }

        @Override
        protected int hash(K value) {
            return TMap.this.hash(value);
        }

    };
    
    private final TCollection<Entry<K,V>> egen = new TCollection<Entry<K,V>>() {

        @Override
        public Entry<K,V> value(int index, int n) {
            return TMap.this.item(index, n).entry();
        }

        @Override
        protected int cmp(Entry<K, V> a, Entry<K, V> b) {
            return TMap.this.cmp(a.getKey(),b.getKey());
        }

        @Override
        protected int hash(Entry<K, V> value) {
            return value.hashCode();
        }

    };
    
    private final TCollection<IntEntry<V>> eigen = new TCollection<IntEntry<V>>() {

        @Override
        public IntEntry<V> value(int index, int n) {
            return TMap.this.item(index, n).entryInt();
        }

		@SuppressWarnings("unchecked")
        @Override
        protected int cmp(IntEntry<V> a, IntEntry<V> b) {
            return TMap.this.cmp((K) a.getKey(),(K) b.getKey());
        }

        @Override
        protected int hash(IntEntry<V> value) {
            return value.hashCode();
        }

    };
    
    private final TCollection<LookupEntry<K>> lgen = new TCollection<LookupEntry<K>>() {

        @Override
        public LookupEntry<K> value(int index, int n) {
            return TMap.this.item(index, n).lookup();
        }

        @Override
        protected int cmp(LookupEntry<K> a, LookupEntry<K> b) {
            return TMap.this.cmp(a.getKey(),b.getKey());
        }

        @Override
        protected int hash(LookupEntry<K> value) {
            return value.hashCode();
        }

    };
    
    private final TCollection<V> vgen = new TCollection<V>() {

        @Override
        public V value(int index, int n) {
            return TMap.this.item(index,n).value();
        }

        @Override
        protected int cmp(V a, V b) {
            return CompareUtils.cmp(a, b);
        }

        @Override
        protected int hash(V value) {
            return value.hashCode();
        }

    };

    protected abstract K key(int index, int n);
    
    protected abstract V value(int index, int n);
    
    protected abstract int cmp(K a, K b);
    
    protected abstract int hash(K key);
    
    protected K key(int index) {
        return key(index, 0);
    }
    
    protected V value(int index) {
        return value(index, 0);
    }
    
    public HashComparator<K> comparator() {
        return kgen.comparator();
    }
    
    public TMapItem<K,V> item(int index) {
        return item(index, 0);
    }
    
    public TMapItem<K,V> item(int index, int n) {
        return new TMapItem<>(this, index, n);
    }
	
    public TMapItems<K,V> list(int... indexes) {
        TMapItems<K,V> ret = new TMapItems<>(this);
        for(int i=0; i<indexes.length; i++) {
            ret.data.add(item(indexes[i]));
        }
        return ret;
    }
    
    public TMapItems<K,V> range(int begin, int end) {
        TMapItems<K,V> ret = new TMapItems<>(this);
        for(int i=begin; i<end; i++) {
            ret.data.add(item(i));
        }
        return ret;
    }
	
	public TMapItems<K,V> range(int count) {
		return range(0,count);
	}
        
    public TMapItems<K,V> urange(int begin, int end) {
        TMapItems<K,V> ret = new TMapItems<>(this);
        for(int i=begin; i<end; i++) {
            ret.data.add(item(i,0));
            ret.data.add(item(i,0));
            ret.data.add(item(i,1));
        }
        return ret;
    }
    
    public TMapItems<K,V> urange(int count) {
        return urange(0,count);
    }
    
    public TCollection<K> keys() {
        return kgen;
    }
    
    public TCollection<V> values() {
        return vgen;
    }
    
    public TCollection<Entry<K,V>> entries() {
        return egen;
    }
    
    public TCollection<IntEntry<V>> entriesInt() {
        return eigen;
    }
    
    public TCollection<LookupEntry<K>> lookups() {
        return lgen;
    }
    
    public TMap<K,V> asIdentity() {
        return new TIdentityMap();
    }

    private class TIdentityMap extends TMap<K, V> {
        
        private final Map<Integer, TMapItem<K,V>> items = new HashMap<>();
        private final Map<Integer, K> keys = new HashMap<>();
        private final OpenLookup<K> lookup = new OpenLookup<>();
        private final Map<Integer, V> vals = new HashMap<>();

        @Override
        public TMapItem<K, V> item(int index, int n) {
            if(n != 0) {
                throw new UnsupportedOperationException("NO TEST");
            }
            return items.computeIfAbsent(index, TMap.this::item);
        }

        @Override
        protected K key(int index, int n) {
            if(n != 0) {
                throw new UnsupportedOperationException("NO TEST");
            }
            return keys.computeIfAbsent(index, i -> {
                K k = TMap.this.key(i);
                lookup.put(k, i);
                return k;
            });
        }

        @Override
        protected V value(int index, int n) {
            if(n != 0) {
                throw new UnsupportedOperationException("NO TEST");
            }
            return vals.computeIfAbsent(index, TMap.this::value);
        }

        @Override
        protected int cmp(K a, K b) {
            return lookup.get(a) - lookup.get(b);
        }

        @Override
        protected int hash(K key) {
            return System.identityHashCode(key);
        }
        
    }

}
