package edu.caltech.cs2.datastructures;

import edu.caltech.cs2.interfaces.ICollection;
import edu.caltech.cs2.interfaces.IDeque;
import edu.caltech.cs2.interfaces.IDictionary;
import edu.caltech.cs2.interfaces.IQueue;

import java.util.Iterator;
import java.util.function.Supplier;
import java.util.stream.Stream;

public class ChainingHashDictionary<K, V> implements IDictionary<K, V> {
    private Supplier<IDictionary<K, V>> chain;
    private IDictionary<K, V>[] dict;
    private int size;
    private static final int[] primeTable = { 7, 17, 37, 79, 163, 331, 673, 1361, 2729, 5471, 10949, 21911, 43853, 87719, 175447, 350899, 701819, 1403641, 2807303, 5614657, 11229331, 22458671, 44917381, 89834777, 179669557, 359339171, 718678369, 1437356741 };
//    private double lambda = (double) this.size / (double) this.dict.length;

    public ChainingHashDictionary(Supplier<IDictionary<K, V>> chain) {
        this.size = 0;
        this.chain = chain;
        this.dict = new IDictionary[4999];
    }

    /**
     * @param key
     * @return value corresponding to key
     */
    @Override
    public V get(K key) {
        int i = key.hashCode() % this.dict.length;
        if(this.dict[i] == null){
            return null;
        }
//        if(this.dict[i].containsKey(key)){
//            return this.dict[i].get(key);
//        }
//        return null;
        else {
            return this.dict[i].get(key);
        }
    }

    @Override
    public V remove(K key) {
        int i = key.hashCode() % this.dict.length;
        if(this.dict[i] == null){
            return null;
        }
        else{
            V data = this.dict[i].remove(key);;
//            this.dict[i].remove(key);
            if(data != null){
                this.size--;
            }
            return data;
        }
    }

    @Override
    public V put(K key, V value) {
        int i = key.hashCode() % this.dict.length;
//        V old = null;
        if((double) this.size / this.dict.length > 1){
            resize();
        }
        if(this.dict[i] == null){
            var newChain = this.chain.get();
            this.dict[i] = newChain;
            size++;
            return newChain.put(key, value);
        }
        else{
            var curChain = this.dict[i];
            var curChainVal = curChain.put(key, value);
            if(curChainVal == null){
                size++;
            }
            return curChainVal;
        }
//        if(this.dict[i].keys()!= null && this.dict[i].keys().contains(key)){
//            old = this.dict[i].get(key);
//        }
//        else{
//            this.size++;
//        }
//        this.dict[i].put(key, value);
//        if((double) this.size / (double) this.dict.length >= 1 ){
//            this.resize();
//        }
    }

    private void resize(){
//        boolean primeFlag = true;
//        int nextPrime = this.dict.length * 2 + 1;
//        while(!isPrime(nextPrime)){
//            nextPrime++;
//        }
        int nextPrime = 0;
        for(int i = 0; i < primeTable.length; i++){
            if(primeTable[i] > this.dict.length*2){
                nextPrime = primeTable[i];
            }
        }

        IDictionary<K, V>[] newDict = new IDictionary[nextPrime];
//        IDictionary<K, V>[] copy = this.dict;
        //this.dict = new IDictionary[nextPrime];
//        this.dict = newDict;
        for(int i = 0; i < this.dict.length; i++){
            if(this.dict[i] != null) {
                for (K key : this.dict[i].keys()) {
                    int j = key.hashCode() % newDict.length;
                    if(newDict[j] == null){
                        newDict[j] = chain.get();
                    }
                    newDict[j].put(key, dict[i].remove(key));
                }
            }
        }
        this.dict = newDict;
    }

    /**
     * Returns true if number is prime
     * @param k
     * @return
     */
    private Boolean isPrime(int k){
        Boolean primeFlag = true;
        for (int i = 2; i <= k / 2; ++i) {
            if (k % i == 0) {
                primeFlag = false;
                return primeFlag;
            }
        }
        return primeFlag;
    }

    @Override
    public boolean containsKey(K key) {
        if(key == null){
            throw new NullPointerException();
        }
        int i = key.hashCode() % this.dict.length;
//        for(K k : this.dict[i]){
//            if(k != null && key.equals(k)){
//                return true;
//            }
//        }
        if(this.dict[i] == null){
            return false;
        }
        else{
            return dict[i].containsKey(key);
        }
//        int i = key.hashCode() % this.dict.length;
//        if(this.dict[i] )
//        if(this.get(key) != null){
//            return true;
//        }
//        return false;
    }

    /**
     * @param value
     * @return true if the HashDictionary contains a key-value pair with
     * this value, and false otherwise
     */
    @Override
    public boolean containsValue(V value) {
        for(IDictionary<K, V> d : this.dict){
            if(d != null && d.containsValue(value)){
                return true;
            }
        }
        return false;
    }

    /**
     * @return number of key-value pairs in the HashDictionary
     */
    @Override
    public int size() {
        return this.size;
    }

    @Override
    public ICollection<K> keys() {
//        ICollection<K> keys = new ArrayDeque<>();
////        for(IDictionary<K,V> d : this.dict){
//        for(int i = 0; i < this.dict.length; i++){
////            if(d != null){
////                for(K k : d.keys()){
//            if(this.dict[i] != null){
//                Iterator<K> iter = this.dict[i].keys().iterator();
//                while(iter.hasNext()){
//                    keys.add(iter.next());
//                }
//            }
//        }
//        return keys;

        IDeque<K> keys = new LinkedDeque<>();
        for(K k : this){
            keys.addBack(k);
        }
//        for(var d : this.dict){
//            if(d != null){
//                keys.addAll(d.keys());
//            }
//        }
        return keys;
    }

    @Override
    public ICollection<V> values() {
        ICollection<V> values = new ArrayDeque<>();
        for(IDictionary<K, V> d : this.dict){
//            if(d != null){
                for(V v: d.values()){
                    values.add(v);
                }
//            }
        }
        return values;
    }

    /**
     * @return An iterator for all entries in the HashDictionary
     */
    @Override
    public Iterator<K> iterator() {
        return this.keys().iterator();
    }
}
