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 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;
        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;
        }
        if(this.dict[i].containsKey(key)){
            V data = this.dict[i].get(key);
            this.dict[i].remove(key);
            this.size--;
            return data;
        }
        return null;
    }

    @Override
    public V put(K key, V value) {
        int i = key.hashCode() % this.dict.length;
        V old = null;
        if(this.dict[i] == null){
            this.dict[i] = this.chain.get();
        }
        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();
        }
        return old;
    }

    private void resize(){
        boolean primeFlag = true;
        int nextPrime = this.dict.length * 2 + 1;
        while(!isPrime(nextPrime)){
            nextPrime++;
        }

        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) {
//        for(IDictionary<K, V> d : this.dict){
//            if(d != null && d.keys().contains(key)){
//                return true;
//            }
//        }
        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.values().contains(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 < 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;
    }

    @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();
    }
}
