/*
 * This is file TreeTest.java in project Trees2009, created on Jan 20, 2009
 */
package tree;

import static org.junit.Assert.*;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;

import org.junit.Before;
import org.junit.Test;

public class DavesTreeTest {
    Tree tree, tree1, tree2;
    Tree a1, b1, c1, d1, e1, f1, g1, a2, b2, c2, d2, e2, f2, g2;
    static boolean firstTime = true;
    
    @Before
    public void setUp() throws Exception {

        /*
         *              a1              a2
         *             / \             / \
         *            /   \           /   \
         *          b1     c1        b2    c2
         *          /\     /\       /\     /\
         *        d1  e1 f1  g1   e2  d2 f2  g2
         */
        a1 = new Tree("a");
        b1 = new Tree("b");
        c1 = new Tree("c");
        d1 = new Tree("d");
        e1 = new Tree("e");
        f1 = new Tree("f");
        g1 = new Tree("g");
        a1.addChild(b1);
        a1.addChild(c1);
        b1.addChild(d1);
        b1.addChild(e1);
        c1.addChild(f1);
        c1.addChild(g1);
        tree1 = a1;
//        if (firstTime) System.out.println("tree1 = " + tree1);
        a2 = new Tree("a");
        b2 = new Tree("b");
        c2 = new Tree("c");
        d2 = new Tree("d");
        e2 = new Tree("e");
        f2 = new Tree("f");
        g2 = new Tree("g");
        a2.addChild(b2);
        a2.addChild(c2);
        b2.addChild(e2);
        b2.addChild(d2);
        c2.addChild(f2);
        c2.addChild(g2);
        tree2 = a2;
//        if (firstTime) System.out.println("tree2 = " + tree2);
        firstTime = false;
    }

    @Test
    public void testTree() {
        Tree t = new Tree("x");
        assertTrue(t.children().size() == 0);
        assertEquals("x", t.getValue());
    }

    @Test
    public void testGetValue() {
        assertEquals("a", a1.getValue());
        assertEquals("b", b1.getValue());
        assertEquals("a", a2.getValue());
    }

    @Test
    public void testSetValue() {
        a1.setValue("A");
        assertEquals("A", a1.getValue());
    }

    @Test
    public void testEquals_Object() {
        assertTrue(f1.equals(f2));
        assertFalse(f1.equals(g1));
        assertTrue(a1.equals(a1));
        assertFalse(a1.equals(a2));
        assertTrue(c1.equals(c2));
        assertFalse(b1.equals(b2));
        Tree x = new Tree("x");
        Tree y = new Tree(null);
        assertFalse(f1.equals(null));
        assertFalse(x.equals(y));
        assertFalse(y.equals(x));
    }

    @Test
    public void testChildren() {
        ArrayList odd = new ArrayList();
        odd.add(b1);
        odd.add(c1);
        assertEquals(odd, a1.children());
    }

    @Test
    public void testAddChild_TreeOfV() {
        Tree x = new Tree("x");
        Tree y = new Tree("y");
        Tree z = new Tree("z");
        a1.addChild(x);
        a1.addChild(y);
        a1.addChild(z);
        ArrayList expected =
            new ArrayList(Arrays.asList(new Tree[]
                 { b1, c1, x, y, z }
            ));
        assertEquals(expected, a1.children());
        assertEquals(new ArrayList(), g1.children());
        try {
            f1.addChild(c1);
            fail();
        } catch (Exception e) {}
        try {
            f1.addChild(a1);
            fail();
        } catch (Exception e) {}
        try {
            b1.addChild(b1);
            fail();
        } catch (Exception e) {}
    }
    
    @Test
    public void testAddChild_result() {
        Tree result = a1.addChild(a2);
        assertEquals(a1, result);
        result = a1.addChild("hello");
        assertEquals(a1, result);
    }

    @Test
    public void testAddChild_V() {
        Tree x = new Tree<String>("x");
        Tree y = new Tree<String>("y");
        Tree z = new Tree<String>("z");
        a1.addChild("x");
        a1.addChild("y");
        a1.addChild("z");
        ArrayList expected =
            new ArrayList(Arrays.asList(new Tree[]
                 { b1, c1, x, y, z }
            ));
        assertEquals(expected, a1.children());
        assertEquals(new ArrayList(), g1.children());
    }
    
    @Test
    public final void testParse() {
        Tree expected = new Tree("abc");
        Tree actual = Tree.parse("abc");
        assertEquals(expected, actual);
        
        expected.addChild("x");
        actual = Tree.parse("abc(x)");
        assertEquals(expected, actual);
        
        expected.addChild("y");
        actual = Tree.parse("abc(x y)");
        assertEquals(expected, actual);
        actual = Tree.parse("  abc  ( x   y ) ");
        assertEquals(expected, actual);
        
        expected.addChild(actual);
        actual = Tree.parse("abc(x y abc(x y))");
        assertEquals(expected, actual);
        
        assertEquals(a1, Tree.parse("a(b (d e) c(f g))"));
    }

    @Test
    public final void testToString() {
        Tree<String> tree = Tree.parse("one(two three(four five ( six seven eight)))");
        assertEquals("one(two three(four five(six seven eight)))",
                     squeezeOutUnnecessaryBlanks(tree.toString()));
        
        tree = Tree.parse("abc(x y abc(x y))");
        assertEquals("abc(x y abc(x y))", tree.toString());
    }
    
    @Test(expected=IllegalArgumentException.class)
    public final void testToStringUnexpectedOpenParenthesis() {
        Tree<String> tree = Tree.parse("abc((x y))");
    }
    
    @Test(expected=IllegalArgumentException.class)
    public final void testToStringLeftoverTokens() {
        Tree<String> tree = Tree.parse("abc(x y abc(x y))z");
    }

    
    /**
     * Removes blanks before and after parentheses and at the beginning
     * and end of the input string, and reduces other sequences of blanks
     * to a single blank.
     * 
     * @param s The string to be squeezed.
     * @return A string with no unnecessary blanks.
     */
    private String squeezeOutUnnecessaryBlanks(String s) {
        s = s.replaceAll(" +", " ");
        s = s.replaceAll(" *\\( *", "(");
        s = s.replaceAll(" *\\) *", ")");
        return s.trim();
    }
    
    @Test
    public final void testPrint() {
        PrintStream originalOut = System.out; 
        try {
            // Prepare to capture output       
            OutputStream os = new ByteArrayOutputStream();
            PrintStream ps = new PrintStream(os);
            System.setOut(ps);

            // Perform tests
            Tree expected = new Tree("abc");
            Tree actual = Tree.parse("abc");
            assertEquals(expected, actual);
            
            actual = Tree.parse("a(b(d e) c(f g))");
            assertEquals(a1, actual);
        }
        finally {
            // Restore normal operation
            System.setOut(originalOut);
        }
    }

    @Test
    public void testContains() {
        assertTrue(a1.contains(a1));
        assertTrue(a1.contains(b1));
        assertTrue(a1.contains(c1));
        assertTrue(a1.contains(d1));
        assertTrue(b1.contains(d1));
        assertFalse(b1.contains(a1));
        assertFalse(d1.contains(a1));
        assertFalse(d1.contains(b1));
        assertFalse(c1.contains(b1));
    }

}
