package hashtable;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;

/**
 * This class implements a simple hash table.
 * Hash table element keys must be Integer or String objects.
 * @author tcolburn
 */
public class HashTable {

    /**
     * Creates a new hash table as an array of linked lists.
     * The lists represent the chains of table elements that hash to the same value.
     * The table's size is given by the constant CAPACITY.
     * An auxiliary array chainLengths is also maintained that stores the
     * sizes of the linked lists.
     */
    public HashTable() {

        chains = new ArrayList<LinkedList<HashTableElement>>(CAPACITY);
        chainLengths = new ArrayList<Integer>(CAPACITY);

        for (int i = 0; i < CAPACITY; i++) {
            chains.add(new LinkedList<HashTableElement>());
            chainLengths.add(0);
        }

        size = 0;
    }

    /**
     * Clears the hash table of all elements.
     */
    public void clear() {
        for (int i = 0; i < CAPACITY; i++) {
            chains.get(i).clear();
            chainLengths.set(i, 0);
        }
        size = 0;
    }

    /**
     * Inserts a hash table element into this hash table.
     * @param element the element to be inserted
     */
    public void insert(HashTableElement element) {
        index = hash(element.getKey());
        LinkedList<HashTableElement> chain = chains.get(index);
        chain.addFirst(element);
        chainLengths.set(index, chainLengths.get(index)+1);
        size++;
    }

    /**
     * Search for an element with a given key in this hash table.
     * @param key the key to be searched for.  Must be an Integer or String.
     * @return the element with this key if it exists, null otherwise.
     */
    public HashTableElement search(Object key) {
        // you must provide
        return null;
    }

    /**
     * Attempts to remove an element from this hash table. true is returned if
     * the element was removed, false if not (that is, the element was not in
     * the table).
     * @param element the element to be removed
     * @return true if the element was removed, false otherwise
     */
    public boolean remove(HashTableElement element) {
        // you must provide
        return false;
    }

    /**
     * Returns the hash value for a given key.
     * Only Integers and Strings are currently supported.
     * @param key the key to be hashed.  Must be an Integer or String.
     * @return the hash value for the key
     */
    private static int hash(Object key) {
        Class keyClass = key.getClass();
        if ( keyClass == Integer.class )
            return integerHash((Integer)key);
        else if ( keyClass == String.class )
            return stringHash((String)key);
        else
            throw new IllegalArgumentException("Hashing not supported for " + keyClass);
    }

    /**
     * Returns the hash value for a given Integer key
     * @param key the Integer key to be hashed
     * @return the hash value for the key
     */
    private static int integerHash(Integer key) {
        // you must provide
        return 0;
    }

    /**
     * Returns the hash value for a given String key
     * @param key the String key to be hashed
     * @return the hash value for the key
     */
    private static int stringHash(String key) {
        // you must provide
        return 0;
    }

    /**
     * Computes the average chain length (load factor) for this
     * hash table.
     * @return the load factor (average chain length)
     */
    public double meanChainLength() {
        double sum = 0.0;
        for (int i = 0; i < CAPACITY; i++) {
            sum += chainLengths.get(i);
        }
        return sum/CAPACITY;
    }

    /**
     * Computes the standard deviation from the mean chain length
     * for this hash table.
     * A good hash function minimizes the standard deviation.
     * @return the standard deviation from the mean chain length
     */
    public double standardDeviation() {
        double mean = meanChainLength();
        double sum_of_squares = 0.0;
        for (int i = 0; i < CAPACITY; i++) {
            sum_of_squares += Math.pow(chainLengths.get(i) - mean, 2);
        }
        return Math.sqrt(sum_of_squares/CAPACITY);
    }

    /**
     * Returns the array of chain lengths for display purposes.
     * @return the array of chain lengths
     */
    public ArrayList<Integer> getChainLengths() {
        return chainLengths;
    }

    /**
     * Returns this hash table's size, that is, the number of elements in it.
     * @return the size of the hash table
     */
    public int getSize() {
        return size;
    }

    /**
     * Gets the index of the last table slot hashed to.
     * @return the index of the last table slot hashed to.
     */
    public int getIndex() {
        return index;
    }
    
    private ArrayList<LinkedList<HashTableElement>> chains;

    private ArrayList<Integer> chainLengths;

    private int index;

    public final static int CAPACITY = 11;

    private int size;

}