package edu.caltech.cs2.datastructures;

import com.github.javaparser.ast.Node;
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 Object[] dict;


    public ChainingHashDictionary(Supplier<IDictionary<K, V>> chain) {
        this.chain = chain;
        size = 3001;
        dict = new Object[size];
    }

    private void resize(){
        int newSize = (size*2)+1;
        Object[] newDict = new Object[newSize];
        for(int i=0; i<dict.length; i++){
            newDict[i] = dict[i];
        }
        dict = newDict;
    }

    /**
     * @param key
     * @return value corresponding to key
     */
    @Override
    public V get(K key) {
        if(dict[key.hashCode()]!=null){
            IDictionary<K, V> arrDict = (IDictionary<K, V>) dict[key.hashCode()];
            return arrDict.get(key);
        }
        return null;
    }

    @Override
    public V remove(K key) {
        if(dict[key.hashCode()]!=null){
            IDictionary<K, V> arrDict = (IDictionary<K, V>) dict[key.hashCode()];
            return arrDict.remove(key);
        }
        return null;
    }

    @Override
    public V put(K key, V value) {
        while (key.hashCode()>size){
            resize();
        }
        V prevVal = null;
        if(dict[key.hashCode()]==null){
            dict[key.hashCode()] = chain.get().put(key, value);
            size++;
        }else{
            IDictionary<K, V> arrDict = (IDictionary<K, V>) dict[key.hashCode()];
            prevVal = arrDict.put(key, value);
            if(prevVal==null){
                size++;
            }
        }
        return prevVal;
    }

    @Override
    public boolean containsKey(K key) {
        return keys().contains(key);
    }

    /**
     * @param value
     * @return true if the HashDictionary contains a key-value pair with
     * this value, and false otherwise
     */
    @Override
    public boolean containsValue(V value) {
        return values().contains(value);
    }

    /**
     * @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(Object o: dict){
            if(o!=null){
                IDictionary<K, V> arrDict = (IDictionary<K, V>) o;
                for(K key: arrDict){
                    keys.add(key);
                }
            }
        }
        return keys;
    }

    @Override
    public ICollection<V> values() {
        ICollection<V> values = new LinkedDeque<>();
        for(Object o: dict){
            if(o!=null){
                IDictionary<K, V> arrDict = (IDictionary<K, V>) o;
                for(V value: arrDict.values()){
                    values.add(value);
                }
            }
        }
        return values;
    }

    /**
     * @return An iterator for all entries in the HashDictionary
     */
    @Override
    public Iterator<K> iterator() {
        return this.keys().iterator();
    }
}
