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

import java.io.PrintStream;
import java.io.Serializable;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import net.ranides.assira.collection.HashComparator;
import net.ranides.assira.collection.Swapper;
import net.ranides.assira.collection.arrays.NativeArrayUtils;
import net.ranides.assira.collection.arrays.ArrayAllocator;
import net.ranides.assira.collection.lists.ListUtils;
import net.ranides.assira.collection.lookups.ALookup;
import net.ranides.assira.collection.lookups.Lookup.LookupEntry;
import net.ranides.assira.collection.maps.HashMap;
import net.ranides.assira.collection.maps.AIntMap;
import net.ranides.assira.collection.maps.IntMap;
import net.ranides.assira.collection.maps.IntMap.IntEntry;
import net.ranides.assira.generic.CompareUtils;

/**
 *
 * @author Ranides Atterwim <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)(Object)a.getKey(),(K)(Object)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();
        }

    };
    
    public static final class TItem<K,V> {
        
        private final TMap<K,V> that;
        
        private final int index;
        
        private final K key;
        
        private final V value;

        protected TItem(TMap<K,V> that, int index, int n) {
            this.that = that;
            this.index = index;
            this.key = that.key(index, n);
            this.value = that.value(index, n);
        }
        
        public int index() {
            return index;
        }
        
        public K key() {
            return key;
        }
        
        public V value() {
            return value;
        }
        
        public int keyInt() {
            return (Integer)key;
        }
        
        public int valueInt() {
            return (Integer)value;
        }
        
        public Entry<K,V> entry() {
            return new AbstractMap.SimpleImmutableEntry<>(key, value);
        }
        
        public IntMap.Entry<K,V> entry(int value) {
            return new AbstractMap.SimpleImmutableEntry<>(key, that.value(value));
        }
        
        public IntEntry<V> entryInt() {
            return new AIntMap.BasicEntry<>(keyInt(), value());
        }
        
        public IntMap.IntEntry<V> entryInt(int value) {
            return new AIntMap.BasicEntry<>(keyInt(), that.value(value));
        }
        
        public LookupEntry<K> lookup(int value) {
            return new ALookup.BasicEntry<>(key, that.item(value).valueInt());
        }
        
        public LookupEntry<K> lookup() {
            return new ALookup.BasicEntry<>(key, valueInt());
        }
        
		public <M extends Map<K,V>> M into(M target) {
			target.put(key(), value());
			return target;
		}
        
    }
        
    public static final class TItems<K,V> implements Iterable<TItem<K,V>> {
        
        protected final TMap<K,V> that;
        
        protected final List<TItem<K,V>> data = new ArrayList<>();
        
        protected TItems(TMap<K,V> that) {
            this.that = that;
        }
        
        public int[] indexes() {
            int[] array = new int[data.size()];
            for(int i=0,n=data.size(); i<n; i++) {
                array[i] = data.get(i).index();
            }
            return array;
        }
        
        public K[] keys() {
            K[] array = ArrayAllocator.forComponent(that.key(0).getClass(), data.size());
            for(int i=0,n=data.size(); i<n; i++) {
                array[i] = data.get(i).key();
            }
            return array;
        }
        
        public V[] values() {
            V[] array = ArrayAllocator.forComponent(that.value(0).getClass(), data.size());
            for(int i=0,n=data.size(); i<n; i++) {
                array[i] = data.get(i).value();
            }
            return array;
        }
        
        public int[] keysInt() {
            int[] array = new int[data.size()];
            for(int i=0,n=data.size(); i<n; i++) {
                array[i] = data.get(i).keyInt();
            }
            return array;
        }
        
        public int[] valuesInt() {
            int[] array = new int[data.size()];
            for(int i=0,n=data.size(); i<n; i++) {
                array[i] = data.get(i).valueInt();
            }
            return array;
        }
        
		@SuppressWarnings("unchecked")
        public Entry<K,V>[] entries() {
            Entry<K,V> array[] = new Entry[data.size()];
            for(int i=0, n=data.size(); i<n; i++) {
                array[i] = data.get(i).entry();
            }
            return array;
        }
        
        @SuppressWarnings("unchecked")
        public IntEntry<V>[] entriesInt() {
            IntEntry<V> array[] = new IntEntry[data.size()];
            for(int i=0, n=data.size(); i<n; i++) {
                array[i] = data.get(i).entryInt();
            }
            return array;
        }
        
        @SuppressWarnings("unchecked")
        public LookupEntry<K>[] lookups() {
            LookupEntry<K> array[] = new LookupEntry[data.size()];
            for(int i=0, n=data.size(); i<n; i++) {
                array[i] = data.get(i).lookup();
            }
            return array;
        }
        
		public TItems<K,V> sort(Comparator<K> cmp) {
			TItems<K,V> items = new TItems<>(that);
			items.data.addAll(data);
			items.data.sort((a,b) -> cmp.compare(a.key(), b.key()));
			return items;
		}
        
        public TItems<K,V> shuffle(long seed) {
            NativeArrayUtils.shuffle(new Random(seed), data.size(), ListUtils.swapper(data));
            return this;
        }

        public TItems<K,V> item(int item) {
			data.add(that.item(item,0));
			return this;
		}
        
        public TItems<K,V> item(int item, int n) {
            data.add(that.item(item,n));
			return this;
		}
		
        @Override
        public Iterator<TItem<K,V>> iterator() {
            return data.iterator();
        }
        
        public TItem<K,V> at(int index) {
            return data.get(index);
        }
		
		public <M extends Map<K,V>> M into(M target) {
			for(TItem<K,V> item : data) {
				target.put(item.key(), item.value());
			}
			return target;
		}
        
		public void dump(PrintStream out) {
			out.printf("TItems(%d)%n", data.size());
			for(int i=0,n=data.size(); i<n; i++) {
				out.printf(" - %3d. %s%n", i, data.get(i).key());
			}
		}
        
    }
    
    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 TItem<K,V> item(int index) {
        return item(index, 0);
    }
    
    public TItem<K,V> item(int index, int n) {
        return new TItem<>(this, index, n);
    }
	
    public TItems<K,V> list(int... indexes) {
        TItems<K,V> ret = new TItems<>(this);
        for(int i=0; i<indexes.length; i++) {
            ret.data.add(item(indexes[i]));
        }
        return ret;
    }
    
    public TItems<K,V> range(int begin, int end) {
        TItems<K,V> ret = new TItems<>(this);
        for(int i=begin; i<end; i++) {
            ret.data.add(item(i));
        }
        return ret;
    }
	
	public TItems<K,V> range(int count) {
		return range(0,count);
	}
        
    public TItems<K,V> urange(int begin, int end) {
        TItems<K,V> ret = new TItems<>(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 TItems<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 HashMap<Integer, TItem<K,V>> emap = new HashMap<>();
        private final HashMap<K, Integer> imap = new HashMap<>();

        private TItem<K,V> iitem(int index, int n) {
            if(n != 0) {
                throw new UnsupportedOperationException("NO TEST");
            }
            if(!emap.containsKey(index)) {
                emap.put(index, TMap.this.item(index));
                imap.put(emap.get(index).key(), index);
            }
            return emap.get(index);
        }
                
        @Override
        protected K key(int index, int n) {
            return iitem(index, n).key();
        }

        @Override
        protected V value(int index, int n) {
            return iitem(index, n).value();
        }

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

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

}
