package binaryTree;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;

import org.junit.*;

import static org.junit.Assert.*;

/**
 * @author David Matuszek
 * @version Jan 29, 2008
 *
 */
public class BinaryTreeTest {
    private BinaryTree leafA, leafB, leafC, nodeAB, rootABC;
    private BinaryTree leafB2, rootAB;
    private String stringA = "A";
    private String stringB = "B";    
    private String stringB2 = new String("B");
    private String stringC = "C";
    private String stringAB = "AB";
    private String stringAB2 = stringA + stringB;
    private 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(stringA);
        leafB = new BinaryTree(stringB);
        leafC = new BinaryTree(stringC, null, null);
        leafB2 = new BinaryTree(stringB2);
        
        nodeAB = new BinaryTree(stringAB, leafA, leafB);
        
        rootABC = new BinaryTree(stringABC, nodeAB, leafC);
        rootAB = new BinaryTree(stringAB2, leafA, leafB2);
        
        assertEquals("A", leafA.value);
        assertEquals("AB", rootAB.value);
    }

    @Test
    public void testGetLeftChild() {
        BinaryTree 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 leafD = new BinaryTree("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 leafD = new BinaryTree("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 root = makeTreeWithMissingChildren();
        assertEquals("A (B (D, null), C (null, E))", root.toString());
    }

    private BinaryTree makeTreeWithMissingChildren() {
        BinaryTree root, left, right;
        left = new BinaryTree("D");
        left = new BinaryTree("B", left, null);
        right = new BinaryTree("E");
        right = new BinaryTree("C", null, right);
        root = new BinaryTree("A", left, right);
        return root;
    }
    
    @Test
    public void testHashCode() {
        BinaryTree first = makeTreeWithMissingChildren();
        BinaryTree second = makeTreeWithMissingChildren();
        assertEquals(first.hashCode(), second.hashCode());
    }
    
    @Test
    public void testPrint() {
        String sep = System.getProperty("line.separator");

        String expected = "ABC" + sep + "   AB" + sep + "      A" + sep +
                "      B" + sep + "   C" + sep + "";
        checkOutput(expected, rootABC);

        expected = "A" + sep + "   B" + sep + "      D" + sep + "      null" +
                sep + "   C" + sep + "      null" + sep + "      E" + sep;
        checkOutput(expected, makeTreeWithMissingChildren());
    }
    
    private void checkOutput(String expected, BinaryTree treeToPrint) {
        // Save and redirect standard output
        PrintStream originalOut = System.out;
        OutputStream os = new ByteArrayOutputStream();
        PrintStream ps = new PrintStream(os);
        System.setOut(ps);
        
        // Perform test
        try {
            treeToPrint.print();
            assertEquals(expected, os.toString());
            
        // Restore standard output
        } finally {
            System.setOut(originalOut);
        }        
    }
}
