import org.junit.*;
import static org.junit.Assert.*;

/**
 * @author David Matuszek
 * @version Jan 29, 2008
 *
 */
public class BinaryTreeTest {
    BinaryTree<String> leafA, leafB, leafC, nodeAB, rootABC;
    BinaryTree<String> leafB2, rootAB;
    String stringA = "A";
    String stringB = "B";    
    String stringB2 = new String("B");
    String stringC = "C";
    String stringAB = "AB";
    String stringAB2 = stringA + stringB;
    String stringABC = "ABC";
    
    /*
     * Constructs the following trees:<pre>
     * 
     *               rootABC:stringABC
     *                 /       \
     *                /         \
     *     nodeAB:stringAB    leafC:stringC         rootAB:stringAB2
     *           /     \                              /        \
     *          /       \                            /          \
     * leafA:stringA  leafB:stringB         leafA:stringA    leafB2:stringB2
     * 
     * </pre>
     * @see TestCase#setUp()
     */
    @Before
    public void setUp() throws Exception {        
        leafA = new BinaryTree<String>(stringA);
        leafB = new BinaryTree<String>(stringB);
        leafC = new BinaryTree<String>(stringC, null, null);
        leafB2 = new BinaryTree<String>(stringB2);
        
        nodeAB = new BinaryTree<String>(stringAB, leafA, leafB);
        
        rootABC = new BinaryTree<String>(stringABC, nodeAB, leafC);
        rootAB = new BinaryTree<String>(stringAB2, leafA, leafB2);
        
        assertEquals("A", leafA.value);
        assertEquals("AB", rootAB.value);
    }

    @Test
    public void testGetLeftChild() {
        BinaryTree<String> t = rootABC.getLeftChild();
        assertEquals(nodeAB, t);
        assertEquals(rootAB, t);
        assertSame(t, nodeAB);
        assertNotSame(t, rootAB);
        assertEquals(null, leafA.getLeftChild());        
    }

    @Test
    public void testGetRightChild() {
        assertEquals(leafC, rootABC.getRightChild());
        assertEquals(leafB, rootABC.getLeftChild().getRightChild());
        assertEquals(null, rootABC.getRightChild().getRightChild());
    }

    @Test
    public void testSetLeftChild() {
        BinaryTree<String> leafD = new BinaryTree<String>("D");
        leafA.setLeftChild(leafD);
        assertEquals(leafD, leafA.getLeftChild());
        try {
            leafB2.setLeftChild(rootAB);
            fail("Failed loop test");
        }
        catch (IllegalArgumentException e) {}
        leafB2.setLeftChild(rootABC); // should not throw exception
        assertEquals(leafB2.getLeftChild(), rootABC);
    }

    @Test
    public void testSetRightChild() {
        BinaryTree<String> leafD = new BinaryTree<String>("D");
        leafC.setRightChild(leafD);
        assertEquals(leafD, leafC.getRightChild());
        try {
            leafB.setRightChild(nodeAB);
            fail("Failed loop test");
        }
        catch (IllegalArgumentException e) {}
        leafB2.setRightChild(rootABC); // should not throw exception
        assertEquals(leafB2.getRightChild(), rootABC);
    }
    
    @Test
    public void testSetAndGetValue() {
        nodeAB.setValue("Test value");
        assertEquals("Test value", nodeAB.getValue());
    }

    @Test
    public void testIsLeaf() {
        assertTrue(leafA.isLeaf());
        assertFalse(nodeAB.isLeaf());
    }

    /*
     * Test for boolean equals(Object)
     */
    @Test
    public void testEqualsObject() {
        assertTrue(nodeAB.equals(rootAB));
        assertTrue(rootAB.equals(nodeAB));
        assertNotSame(rootAB, nodeAB);
        assertNull(leafA.getRightChild());
    }
    
    @Test
    public void testToString() {
        assertEquals("ABC (AB (A, B), C)", rootABC.toString());       
        BinaryTree<String> root = makeTreeWithMissingChildren();
        assertEquals("A (B (D, null), C (null, E))", root.toString());
    }
    
    @Test
    public void testContainsTreeTree() {
        assertTrue(BinaryTree.contains(rootABC, rootABC));
        assertTrue(BinaryTree.contains(rootABC, nodeAB));
        assertTrue(BinaryTree.contains(rootABC, leafA));
        assertFalse(BinaryTree.contains(nodeAB, rootABC));
        assertFalse(BinaryTree.contains(leafA, rootABC));
    }

    private BinaryTree<String> makeTreeWithMissingChildren() {
        BinaryTree<String> root, left, right;
        left = new BinaryTree<String>("D");
        left = new BinaryTree<String>("B", left, null);
        right = new BinaryTree<String>("E");
        right = new BinaryTree<String>("C", null, right);
        root = new BinaryTree<String>("A", left, right);
        return root;
    }
    
    @Test
    public void testHashCode() {
        BinaryTree<String> first = makeTreeWithMissingChildren();
        BinaryTree<String> second = makeTreeWithMissingChildren();
        assertEquals(first.hashCode(), second.hashCode());
    }
    /**
     * In general we do not want anything printed out when we run
     * our tests. This class can be run as a JUnit test, in which
     * case this method will not be called, or as an application,
     * in which case this method <i>will</i> be called.
     * <p>
     * The only reason to run this class as an application is to
     * test the print() method in BinaryTree; the results have to
     * be evaluated by hand.
     * <p>
     * Note that we could write a print method that took an output
     * stream as a parameter; this would allow JUnit testing.
     * 
     * @param args Not used.
     */
    public static void main(String[] args) {
        BinaryTreeTest test = new BinaryTreeTest();
        try {
            test.setUp();
            test.rootABC.print();
            System.out.println("--------------");
            test.makeTreeWithMissingChildren().print();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

}
