/**
 * Tester class for BSTDictionary
 * Written for Programming Assignment 3, Summer 2014
 */
import java.util.*;
public class DictionaryTester {
	private static int randArr[] = {5,2,7,6,4,1,9,8,3,0 };
	public static void main(String[] args) {
		testEmpty();
		testOne();
		testInsertLookup();
		testInsertDelete();
		testTotalPathLength();
		testIterator();
		System.out.println("Done testing BSTDictionary");
	}
	
	private static void testEmpty() {
		// test normal construction		
		BSTDictionary<String> bst = new BSTDictionary<String>();

		if (!(bst instanceof DictionaryADT))
			error("BSTDictionary doesn't implement DictionaryADT interface");
		
		if (!bst.isEmpty()) {
			error("isEmpty incorrect on empty BSTDictionary");
		}
		if (bst.size() != 0) {
			error("size incorrect on empty BSTDictionary");
		}

		try {
			boolean answer = bst.delete("temp");
			if (answer) 
				error("delete from empty BSTDictionary returned true");
		} catch (Exception e) {
			error("delete from empty BSTDictionary threw exception");
		}
		
		try {
			String result = bst.lookup("temp");
			if (result != null)
				error("lookup on empty BSTDictionary did not return null");
		} catch (Exception e) {
			error("lookup on empty BSTDictionary threw exception");
		}
		
		try {
			Iterator<String> iter = bst.iterator();
			if (iter.hasNext()) 
				error("hasNext() on empty BSTDictionary is incorrect");
			try {
				iter.next();
				error("next() on empty BSTDictionary did not throw exception");
			} catch (NoSuchElementException e) {
				// expected
			} catch (Exception e) {
				error("next() on empty BSTDictionary threw wrong exception");
			}
		} catch (Exception e) {
			error("iterator() on empty BSTDictionary threw exception");
		}
	}
	
	private static void testOne() {
		BSTDictionary<String> bst = new BSTDictionary<String>();
		String key = new String("item1");
		String key2 = new String("item1");
		// test normal insert/lookup
		int beforeSize = bst.size();
		try {
			bst.insert(key);
			String result = bst.lookup(key2);
			if (result == null)
				error("lookup of item failed immediately after insert of item");
			else {
				if (!result.equals(key))
					error("lookup returned item with wrong key");
				if (result == key2)
					error("lookup returned parameter instead of value in bst");
			}			
			if (bst.size() != beforeSize + 1)
				error("size not updated correctly after insert");
			if (bst.isEmpty())
				error("isEmpty incorrect after inserting one item");
		} catch (DuplicateException e) {
			error("insert threw DuplicateException when it should not have");
		} catch (Exception e) {
			error("insert threw unexpected exception");
		}
		
		if (bst.delete(key)) {
			if (bst.lookup(key) != null)
				error("lookup on deleted item did not return null");
			if (bst.size() != beforeSize)
				error("size after delete only item is incorrect");
			if (!bst.isEmpty())
				error("isEmpty after delete only item is incorrect");
		} else 
			error("delete of only item didn't return true");
		
		bst = new BSTDictionary<String>();
		try {
			bst.insert(key);
			try {
				bst.insert(key);
				error("insert of duplicate key did not throw exception");
			} catch (DuplicateException e) {
				// expected
			} catch (Exception e) {
				error("insert of duplicate key threw wrong exception");
			}
		} catch (DuplicateException e) {
			error("insert of non-duplicate key threw DuplicateException");
		} catch (Exception e) {
			error("insert of non-duplicate key threw unexpected exception");
		}
	}
	
	private static void testIterator() {
		BSTDictionary<Integer> bst = new BSTDictionary<Integer>();
		try {
			for (int i = 0; i < 10; i++)
				bst.insert(randArr[i]);
			
			Iterator<Integer> iter = bst.iterator();
			List<Integer> list = new ArrayList<Integer>();
			try {
				while (iter.hasNext()) {
					list.add(iter.next());
				}
			} catch (NoSuchElementException e) {
				error("NoSuchElementException thrown while testing iterator");
			}
			
			if (list.size() != 10) 
				error("iterator did not iterate over all items");
			
			for (int i = 0; i < 10; i++)
				if (list.get(i) != i)
					error("iterator did not return items in correct order");
			
			iter = bst.iterator();
			try {
				for (int i = 0; i < 10; i++)
					iter.next();
				if (iter.hasNext())
					error("hasNext() returned true when it should not have");
				else {
					try {
						iter.next();
						error("next() should have thrown NoSuchElementException");
					} catch (NoSuchElementException e) {
						//expected
					}
				}
			} catch (NoSuchElementException e) {
				error("NoSuchElementException thrown while testing iterator");
			}
			
		} catch (DuplicateException e) {
			error("DuplicateException thrown while testing iterator");
		} catch (Exception e) {
			error("unexpected exception thrown while testing iterator");
		}
	}
	
	private static void testInsertLookup() {
		BSTDictionary<String> bst = new BSTDictionary<String>();
		List<String> list = new ArrayList<String>();
		try {
			for (int i = 0; i < 10; i++) {
				String str = new String("a" + randArr[i]);
				bst.insert(str);
				list.add(str);
			}
			
			if (bst.isEmpty())
				error("isEmpty() incorrect on bst with 10 keys");
			if (bst.size() != 10)
				error("size() incorrect on bst with 10 keys");
			
			for (int i = 0; i < 10; i++) {
				String result = bst.lookup("a" + i);
				if (result == null)
					error("lookup() didn't find previously inserted key");
				else if (result != list.get(list.indexOf("a" + i)))
					error("lookup() found key but didn't return key from Dictionary");
			}
			
			for (int i = 10; i < 20; i++) {
				if (bst.lookup("a" + i) != null)
					error("lookup() found keys not in Dictionary");
			}
			
			for (int i = 0; i < 10; i++) {
				try {
					bst.insert(new String("a" + i));
					error("insert() of duplicate key didn't throw DuplicateException");
				} catch (DuplicateException e) {
					// expected
				} catch (Exception e) {
					error("insert() of duplicate key threw wrong exception");
				}
			}
			
		} catch (DuplicateException e) {
			error("DuplicateException thrown while testing insert");
		} catch (Exception e) {
			error("unexpected exception thrown while testing insert");
		}
	}
	
	private static void testInsertDelete() {
		BSTDictionary<String> bst = new BSTDictionary<String>();
		try {
			for (int i = 0; i < 10; i++) {
				String str = new String("a" + randArr[i]);
				bst.insert(str);
			}

			int currSize = bst.size();
			if (bst.delete("b"))
				error("delete() returned true when deleting item not in Dictionary");
			int newSize = bst.size();
			if (currSize != newSize) 
				error("size() changed when deleting item not in Dictionary");

			for (int i = 0; i < 10; i++) {
				currSize = bst.size();
				if (!bst.delete("a" + i)) 
					error("delete() returned false when deleting item in Dictionary");
				newSize = bst.size();
				if (currSize == newSize)
					error("size() not changed when deleting item in Dictionary");
				if (bst.lookup("a" + i) != null)
					error("lookup() found item deleted from Dictionary");
				try {
					bst.insert("a" + i);
				} catch (DuplicateException e) {
					error("inserting just deleted item threw DuplicateException");
				} catch (Exception e) {
					error("inserting just deleted item threw unexpected exception");
				}
			}
			
		} catch (DuplicateException e) {
			error("DuplicateException thrown while testing insert");
		} catch (Exception e) {
			error("unexpected exception thrown while testing insert");
		}
	}
	
	private static void testTotalPathLength() {
		BSTDictionary<String> bst = new BSTDictionary<String>();
		try { 
			// create a linear tree
			for (int i = 0; i < 10; i++) {
				String str = new String("a" + i);
				bst.insert(str);
			}
			
			int path = bst.totalPathLength();
			if (path != 55) // N*(N+1)/2 = 10*11/2 = 55
				error("totalPathLength incorrect, returned " + path + " (instead of 55)");
			
		} catch (DuplicateException e) {
			error("DuplicateException thrown while testing insert in testTotalPathLength 1");
		} catch (Exception e) {
			error("unexpected exception thrown while testing insert in testTotalPathLength 1");
		}
		
		bst = new BSTDictionary<String>();
		try { 
			// create a full tree with 7 values
			int[] arr = {3, 1, 5, 0, 2, 4, 6};
			for (int i = 0; i < arr.length; i++) {
				String str = new String("a" + arr[i]);
				bst.insert(str);
			}
			
			int path = bst.totalPathLength();
			if (path != 17) // see assignment write-up
				error("totalPathLength incorrect, returned " + path + " (instead of 17)");
			
		} catch (DuplicateException e) {
			error("DuplicateException thrown while testing insert in testTotalPathLength 2");
		} catch (Exception e) {
			error("unexpected exception thrown while testing insert in testTotalPathLength 2");
		}
		
		bst = new BSTDictionary<String>();
		try { 
			// create a full tree with 15 values
			int[] arr = {17, 13, 21, 11, 15, 19, 23, 10, 12, 14, 16, 18, 20, 22, 24};
			for (int i = 0; i < arr.length; i++) {
				String str = new String("a" + arr[i]);
				bst.insert(str);
			}
			
			int path = bst.totalPathLength();
			if (path != 49) // see assignment write-up
				error("totalPathLength incorrect, returned " + path + " (instead of 49)");
			
		} catch (DuplicateException e) {
			error("DuplicateException thrown while testing insert in totalPathLength 3");
		} catch (Exception e) {
			error("unexpected exception thrown while testing insert in totalPathLength 3");
		}			
	}
	
	private static void error(String msg) {
		System.out.println(msg);
	}
}
