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 int size;
    private int capacity;
    private static final int[] primes = new int[]{17, 43, 97, 283, 661, 1523, 4073, 7919, 16649, 19319, 21269, 21851, 22279, 23197, 23899, 24439, 25253, 26099, 27103, 27277, 55073, 111653, 264091, 510569};
    private int primeIdx = 0;
    private IDictionary<K, V>[] bucketList;
    // load factor add up size of dictionaries and divide by size of array




    public ChainingHashDictionary(Supplier<IDictionary<K, V>> chain) {
        // student: TODO fill me in!
        this.chain = chain;
        size = 0;
        capacity = primes[primeIdx];
        this.bucketList = new IDictionary[capacity];
    }

    private void ensureCapacity(){
        if ((float)size/capacity >= 1){
            primeIdx ++;
            int newCap = primes[primeIdx];
            while ((float)size/newCap >= 1){
                primeIdx ++;
                newCap = primes[primeIdx];
            }

            IDictionary<K, V>[] newBucketList = new IDictionary[newCap];
            for (int i = 0; i < newCap; i++){
                newBucketList[i] = chain.get();
            }

            for (IDictionary<K, V> bucket : bucketList){
                if (bucket != null){
                    for (K key : bucket){
                        int hashCode = key.hashCode();
                        int hashIdx = hashCode % newCap;
                        if (hashIdx < 0){
                            hashIdx += newCap;
                        }
//                        hashIdx = hashIdx < 0 ? hashIdx + newCap : hashIdx;
                        newBucketList[hashIdx].put(key, this.get(key));
                    }
                }
            }
            this.bucketList = newBucketList;
            this.capacity = newCap;
        }
    }



    /**
     * @param key
     * @return value corresponding to key
     */
    @Override
    public V get(K key) {
        int modKey = getIdx(key);
        if (bucketList[modKey] != null) {
            return bucketList[modKey].get(key);
        }
        return null;
    }

    @Override
    public V remove(K key) {
        int modKey = getIdx(key);
        if (bucketList[modKey] != null){
            V returnVal = bucketList[modKey].remove(key);
            if (returnVal != null){
                size --;
            }
            return returnVal;
        }
        return null;
    }

    private int getIdx(K key){
        int hashCode = key.hashCode();
        int hashIdx = hashCode % capacity;
//        hashIdx = hashIdx < 0 ? hashIdx + capacity : hashIdx;
        if (hashIdx < 0){
            hashIdx += capacity;
        }
        return hashIdx;
    }

    @Override
    public V put(K key, V value) {
        this.ensureCapacity();
        int modKey = getIdx(key);
        if (bucketList[modKey] == null){
            bucketList[modKey] = chain.get();
        }

//        V returnVal =  bucketList[modKey].put(key, value);
        V returnVal = remove(key);
        bucketList[modKey].put(key, value);
        size++;
//        if (returnVal == null){
//
//            size ++;
//        }
        return returnVal;
//        if (bucketList[modKey] != null){
//            V oldVal = bucketList[modKey].put(key, value);
//            if (oldVal == null){
//                size ++;
//            }
//            return oldVal;
//        }
//        bucketList[modKey] = chain.get();
//        bucketList[modKey].put(key, value);
//        size ++;
//        return null;
    }

    @Override
    public boolean containsKey(K key) {
        int modKey = getIdx(key);
        if (bucketList[modKey] != null){
            return bucketList[modKey].containsKey(key);
        }
        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> dic : bucketList){
            if (dic != null){
                if (dic.containsValue(value)){
                    return true;
                }
            }

        }
        return false;
    }

    /**
     * @return number of key-value pairs in the HashDictionary
     */
    @Override
    public int size() {
        return size;
    }

    @Override
    public ICollection<K> keys() {
        ICollection<K> keys = new LinkedDeque<>();
        for (IDictionary dic : bucketList){
            if (dic != null){
                ICollection<K> theseKeys = dic.keys();
                for (K key : theseKeys){
                    keys.add(key);
                }
            }

        }
        return keys;
    }

    @Override
    public ICollection<V> values() {
        ICollection<V> values = new LinkedDeque<>();
        for (IDictionary dic : bucketList){
            if (dic != null){
                ICollection<V> theseVals = dic.values();
                for (V value : theseVals){
                    values.add(value);
                }
            }

        }
        return values;
    }

    /**
     * @return An iterator for all entries in the HashDictionary
     */
    @Override
    public Iterator<K> iterator() {
        return this.keys().iterator();
    }
}
