/*
 * @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.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.function.IntFunction;
import net.ranides.assira.collection.HashComparator;
import net.ranides.assira.collection.IntCollection;
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.maps.HashMap;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 * @param <T>
 */
public abstract class TCollection<T> implements Serializable {
    
    private final HashComparator<T> kcmp = new HashComparator<T>() {
        
        @Override
        public int hashCode(T object) {
            return hash(object);
        }
        @Override
        public int compare(T a, T b) {
            return cmp(a,b);
        }
    };
    
    public static final class TItem<T> {
        
        private final int index;
        
        private final T value;
        
        protected TItem(int index, T value) {
            this.index = index;
            this.value = value;
        }
        
        public int index() {
            return index;
        }
        
        public T value() {
            return value;
        }
        
        public int valueInt() {
            return (Integer)value;
        }
        
		public <C extends Collection<T>> C into(C target) {
			target.add(value());
			return target;
		}
        
        public <C extends IntCollection> C intoInt(C target) {
			target.add(valueInt());
			return target;
        }
        
    }
        
    public static final class TItems<T> implements Iterable<TItem<T>> {
        
        protected final TCollection<T> that;
        
        protected final List<TItem<T>> data = new ArrayList<>();
        
        protected TItems(TCollection<T> 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 T[] values() {
            T[] 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[] 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;
        }
      
		public TItems<T> sort(Comparator<T> cmp) {
			TItems<T> items = new TItems<>(that);
			items.data.addAll(data);
			items.data.sort((a,b) -> cmp.compare(a.value(), b.value()));
			return items;
		}
        
        public TItems<T> shuffle(long seed) {
            NativeArrayUtils.shuffle(new Random(seed), data.size(), ListUtils.swapper(data));
            return this;
        }

        public TItems<T> item(int item) {
			data.add(that.item(item,0));
			return this;
		}
        
        public TItems<T> item(int item, int n) {
            data.add(that.item(item,n));
			return this;
		}
		
        @Override
        public Iterator<TItem<T>> iterator() {
            return data.iterator();
        }
        
        public TItem<T> at(int index) {
            return data.get(index);
        }
		
		public <C extends Collection<T>> C into(C target) {
			for(TItem<T> item : data) {
				target.add(item.value());
			}
			return target;
		}
        
        public List<T> intoList() {
            return into(new ArrayList<>());
        }
        
        public <C extends IntCollection> C intoInt(C target) {
			for(TItem<T> item : data) {
				target.add(item.valueInt());
			}
			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).value());
			}
		}
        
    }
    
    protected abstract T value(int index, int n);
    
    protected abstract int cmp(T a, T b);
    
    protected abstract int hash(T value);
    
    protected T value(int index) {
        return value(index, 0);
    }
    
    public HashComparator<T> comparator() {
        return kcmp;
    }
    
    public TItem<T> item(int index) {
        return item(index, 0);
    }
    
    public TItem<T> item(int index, int n) {
        return new TItem<>(index, value(index, n));
    }
	
    public TItems<T> list(int... indexes) {
        TItems<T> ret = new TItems<>(this);
        for(int i=0; i<indexes.length; i++) {
            ret.data.add(item(indexes[i]));
        }
        return ret;
    }
    
    public TItems<T> range(int begin, int end) {
        TItems<T> ret = new TItems<>(this);
        for(int i=begin; i<end; i++) {
            ret.data.add(item(i));
        }
        return ret;
    }
	
	public TItems<T> range(int count) {
		return range(0,count);
	}
        
    public TItems<T> urange(int begin, int end) {
        TItems<T> 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<T> urange(int count) {
        return urange(0,count);
    }
    
    public TCollection<T> asIdentity() {
        return new TIdentityCollection();
    }
    
    public IntFunction<T[]> asRangeGenerator() {
        return n -> range(n).values();
    }
    
    private class TIdentityCollection extends TCollection<T> {
        
        private final HashMap<Integer, TItem<T>> emap = new HashMap<>();
        private final HashMap<T, Integer> imap = new HashMap<>();

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

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

        @Override
        protected int hash(T value) {
            return System.identityHashCode(value);
        }
        
    }

}
