package fr.umlv.tpnote;
 
import static fr.umlv.tpnote.ReadOnlyTree.leaf;
import static fr.umlv.tpnote.ReadOnlyTree.tree;
import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import junit.framework.Assert;

import org.junit.Test;

public class ReadOnlyTreeTest {
  @Test
  public void leafTest() {
    leaf(3);
  }
  
  @Test(expected=NullPointerException.class)
  public void leafNull() {
    leaf(null);
  }
  
  @Test
  public void treeTest() {
    tree(2, leaf(1), leaf(3));
    tree("baz", leaf("bar"), leaf("foo"));
    tree(2.0, tree(2.0, null, null), null);
    tree("l", tree("f", leaf("a"), leaf("g")), tree("o", leaf("n"), leaf("u")));
  }
  
  @Test(expected=NullPointerException.class)
  public void treeNull() {
    tree(null, null, null);
  }
  
  @Test(expected=IllegalArgumentException.class)
  public void balance1() {
    tree(2l, leaf(3l), leaf(1l));
  }
  
  public void balance2() {
    tree("baz", null, leaf("xoxo"));
  }
  
  public void balance3() {
    tree("baz", leaf("aaa"), null);
  }
  
  @Test(expected=IllegalArgumentException.class)
  public void balance4() {
    tree("baz", null, leaf("baz"));
  }
  
  @Test(expected=IllegalArgumentException.class)
  public void balance5() {
    tree("g", tree("f", leaf("a"), leaf("h")), tree("o", leaf("n"), leaf("u")));
  }
  
  @Test(expected=IllegalArgumentException.class)
  public void balance6() {
    tree("l", tree("f", leaf("a"), leaf("h")), tree("o", leaf("g"), leaf("u")));
  }
  
  @Test
  public void getElement() {
    Assert.assertEquals(42, (int)leaf(42).getElement());
    Assert.assertEquals(4357, (int)tree(4357, null, null).getElement());
    Assert.assertEquals(747, (int)tree(747, leaf(101), null).getElement());
    Assert.assertEquals(-13, (int)tree(-13, null, leaf(-3)).getElement());
  }
  
  @Test
  public void getMin() {
    Assert.assertEquals(1, (int)leaf(1).getMin());
    Assert.assertEquals(3, (int)tree(3, null, null).getMin());
    Assert.assertEquals(1, (int)tree(2, leaf(1), leaf(3)).getMin());
    Assert.assertEquals("bar", tree("baz", leaf("bar"), leaf("foo")).getMin());
    Assert.assertEquals("a", tree("l", tree("f", leaf("a"), leaf("g")), tree("o", leaf("n"), leaf("u"))).getMin());
  }
  
  @Test
  public void getMax() {
    Assert.assertEquals(1, (int)leaf(1).getMax());
    Assert.assertEquals(3, (int)tree(3, null, null).getMax());
    Assert.assertEquals(3, (int)tree(2, leaf(1), leaf(3)).getMax());
    Assert.assertEquals("foo", tree("baz", leaf("bar"), leaf("foo")).getMax());
    Assert.assertEquals("u", tree("l", tree("f", leaf("a"), leaf("g")), tree("o", leaf("n"), leaf("u"))).getMax());
  }
  
  @Test
  public void size() {
    Assert.assertEquals(1, leaf(1).size());
    Assert.assertEquals(1, tree(1, null, null).size());
    Assert.assertEquals(2, tree(1, leaf(0), null).size());
    Assert.assertEquals(3, tree(1, leaf(-1), leaf(2)).size());
  }
  
  static class TestBlock implements Block<Number> {
    int sum;
    
    TestBlock() {
      // do nothing
    }
    @Override
    public void apply(Number element) {
       sum += element.intValue();
    }
    
    static int sum(ReadOnlyTree<Integer> tree) {
      TestBlock block = new TestBlock();
      tree.traverse(block);
      return block.sum;
    }
  }
  
  @Test
  public void traverse() {
    Assert.assertEquals(7, TestBlock.sum(leaf(7)));
    Assert.assertEquals(9, TestBlock.sum(tree(9, null, null)));
    Assert.assertEquals(110, TestBlock.sum(tree(64, leaf(46), null)));
    Assert.assertEquals(31, TestBlock.sum(tree(17, leaf(-13), leaf(27))));
  }
  
  @Test
  public void toStringTest() {
    Assert.assertEquals("[24]", leaf(24).toString());
    Assert.assertEquals("[132]", tree(132, null, null).toString());
    Assert.assertEquals("[11, 27]", tree(27, leaf(11), null).toString());
    Assert.assertEquals("[a, f, g, l, n, o, u]", tree("l", tree("f", leaf("a"), leaf("g")), tree("o", leaf("n"), leaf("u"))).toString());
  }
  
  @Test
  public void intoList() {
    Assert.assertEquals(Arrays.asList(79), leaf(79).into(new ArrayList<Integer>(), Integer.class));
    Assert.assertEquals(Arrays.asList("zoosh"), tree("zoosh", null, null).into(new ArrayList<>(), Object.class));
    Assert.assertEquals("[a, f, g, l, n, o, u]", tree("l", tree("f", leaf("a"), leaf("g")), tree("o", leaf("n"), leaf("u"))).into(new ArrayList<String>(), String.class).toString());
  }
  
  @Test
  public void into() {
    List<CharSequence> list = tree("foo", null, null).into(new ArrayList<CharSequence>(), CharSequence.class);
    Assert.assertEquals(1, list.size());
    HashSet<String> set = tree("foobar", leaf("foobar"), null).into(new HashSet<String>(), String.class);
    Assert.assertEquals(1, list.size());
  }
  
  @Test
  public void containsTrue() {
    Assert.assertTrue(leaf(67).contains(67));
    Assert.assertTrue(tree(234, null, null).contains(234));
    Assert.assertTrue(tree(37, leaf(21), leaf(55)).contains(37));
    Assert.assertTrue(tree(37, leaf(21), leaf(55)).contains(21));
    Assert.assertTrue(tree(37, leaf(21), leaf(55)).contains(55));
  }
  
  @Test
  public void containsFalse() {
    Assert.assertFalse(leaf(67).contains(65));
    Assert.assertFalse(tree(234, null, null).contains(235));
    Assert.assertFalse(tree(37, leaf(21), leaf(55)).contains(22));
    Assert.assertFalse(tree(37, leaf(21), leaf(55)).contains(38));
    Assert.assertFalse(tree(37, leaf(21), leaf(55)).contains(36));
  }
  
  static class AscendingOrderCheckBlock implements Block<Integer> {
    int value = Integer.MIN_VALUE;
    
    AscendingOrderCheckBlock() {
      // do nothing
    }
    @Override
    public void apply(Integer element) {
       if (element<value)
         fail();
       value = element;
    }
  }
  
  @Test
  public void append() {
    Random random = new Random(-1);
    ReadOnlyTree<Integer> tree = leaf(0);
    for(int i=0; i<29; i++) {
      tree = tree.append(random.nextInt(10));
    }
    Assert.assertEquals(30, tree.size());
    tree.traverse(new AscendingOrderCheckBlock());
  }
  
  @Test
  public void append2() {
    Random random = new Random(0);
    ReadOnlyTree<Integer> tree = leaf(0);
    for(int i=0; i<30; i++) {
      tree = tree.append(random.nextInt(10));
    }
    Assert.assertEquals(Arrays.asList(new Integer[]{0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 7, 7, 7, 7, 8, 8, 9, 9}),
        tree.into(new LinkedList<Integer>(), Integer.class));
  }
  
  @Test
  public void findElementAt() {
    Random random = new Random(0);
    ReadOnlyTree<Integer> tree = leaf(0);
    for(int i=0; i<10; i++) {
      tree = tree.append(random.nextInt(10));
    }
    
    Assert.assertEquals(0, (int)tree.findElementAt(0));
    Assert.assertEquals(0, (int)tree.findElementAt(1));
    Assert.assertEquals(1, (int)tree.findElementAt(2));
    Assert.assertEquals(1, (int)tree.findElementAt(3));
    Assert.assertEquals(3, (int)tree.findElementAt(4));
    Assert.assertEquals(4, (int)tree.findElementAt(5));
    Assert.assertEquals(5, (int)tree.findElementAt(6));
    Assert.assertEquals(7, (int)tree.findElementAt(7));
    Assert.assertEquals(8, (int)tree.findElementAt(8));
    Assert.assertEquals(9, (int)tree.findElementAt(9));
    Assert.assertEquals(9, (int)tree.findElementAt(10));
  }
  
  @Test(expected=IndexOutOfBoundsException.class)
  public void findElementAtKaboom() {
    leaf(3).findElementAt(-1);
  }
  
  @Test(expected=IndexOutOfBoundsException.class)
  public void findElementAtKaboom2() {
    leaf(3).findElementAt(1);
  }
}
