import java.util.Scanner;
import java.util.List;
import java.util.LinkedList;
import java.util.ArrayList;

/**
 * A program to demonstrate a simple hash table.
 *
 * @author Mark Young (A00000000)
 */
public class ChainedHashTable {

    public static int arraySize = 23;

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        printIntroduction();
        pause();

        String[] mine = getWordList();
        printWordList(mine);
        pause();

        // create and fill the hash table
        List<List<String>> hashTable = makeHashTable(arraySize);
        for (String me : mine) {
            addHashEntry(me, hashTable);
        }
        System.out.println();
        pause();

        // print the hash table
        printHashTable(hashTable);
        pause();

        // ask user to add words
        for (int i = mine.length; i < hashTable.size(); ++i) {
            System.out.print("Enter another word/phrase: ");
            String word = KBD.nextLine();
            System.out.println();
            addHashEntry(word, hashTable);
            System.out.println();
            printHashTable(hashTable);
            pause();
        }
    }

    /**
     * Print out a hash table, one line per entry, with empty cells included.
     *
     * @param hashTable the table to print.
     */
    private static void printHashTable(List<List<String>> hashTable) {
        System.out.printf("%4s %-15s%n", "i", "hashTable[i]");
        System.out.printf("%4s %-15s%n", "-", "------------");
        for (int i = 0; i < arraySize; ++i) {
            System.out.printf("%4d %s%n",
                    i, hashTable.get(i).toString());
        }
    }

    /**
     * Add the given word to the given hash table. Uses chains to handle
     * collioions. Does not check for a loading factor. 
     *
     * @param me the word to add.
     * @param hashTable the table to add the word to.
     */
    private static void addHashEntry(String me, List<List<String>> hashTable) {
        System.out.println("Adding '" + me + "'");
        int location = Math.abs(me.hashCode() % arraySize);
        List<String> contents = hashTable.get(location);
        if (!contents.contains(me)) {
            hashTable.get(location).add(me);
            System.out.println("...'" + me + "' added at location " + location);
        } else {
            System.out.println("...duplicate entry...");
        }
    }

    /**
     * Print a list of words together with their hash codes.
     *
     * @param mine the list to print.
     */
    private static void printWordList(String[] mine) {
        System.out.printf("%15s %15s%n", "String", "Hash Code");
        System.out.printf("%15s %15s%n", "------", "---------");
        for (String me : mine) {
            System.out.printf("%15s %,15d%n", me, me.hashCode());
        }
    }

    /**
     * Create a list of words.
     *
     * @return a list of perfectly cromulent words and phrases.
     */
    private static String[] getWordList() {
        // make the list of words to add to the hash table
        String[] mine = new String[]{
            "me", "myself", "I", "mine", "Mark", "Young", "ego", "swollen head"
        };
        return mine;
    }

    /**
     * Introduce this program.
     */
    private static void printIntroduction() {
        System.out.println("This program creates a hash table. "
                + "It uses chained list entries.");
    }

    /**
     * Create an array-based List of the given size. Each element of the 
     * array-based List is a LinkedList.
     *
     * @param size the number of elements in the array-based List
     * @return a List of Lists of String suitable for a hash table
     */
    private static List<List<String>> makeHashTable(int size) {
        List<List<String>> result = new ArrayList<List<String>>(arraySize);
        for (int i = 0; i < size; ++i) {
            result.add(new LinkedList<>());
        }
        return result;
    }

    /**
     * The one and only Scanner on System.in.
     */
    private static final Scanner KBD = new Scanner(System.in);

    /**
     * Wait for the user to press the enter key. Includes a prompt and before-
     * after blank lines.
     */
    private static void pause() {
        System.out.println();
        System.out.print("Press enter...");
        KBD.nextLine();
        System.out.println();
    }

}
