package fr.umlv.node;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeout;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.time.Duration;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

@SuppressWarnings("static-method")
public class NodeTest {
  
  @Test @Tag("Q1")
  public void testSimpleNode() {
    var node = new Node<>(3);
    assertEquals(3, (int)node.element());
  }
  @Test @Tag("Q1")
  public void testSimpleNode2() {
    var node = new Node<>("foo");
    assertEquals("foo", node.element());
  }
  @Test @Tag("Q1")
  public void testNodeNull() {
    assertThrows(NullPointerException.class, () -> new Node<String>(null));
  }
  
  /*
  @Test @Tag("Q2")
  public void testPrepend() {
    var node = new Node<>(3).prepend(14);
    assertEquals(14, (int)node.element());
  }
  @Test @Tag("Q2")
  public void testPrepend2() {
    var node = new Node<>("foo").prepend("bar");
    assertEquals("bar", node.element());
  }
  @Test @Tag("Q2")
  public void testPrependNull() {
    assertThrows(NullPointerException.class, () -> new Node<>("foo").prepend(null));
  }
  @Test @Tag("Q2")
  public void testSize() {
    assertAll(
        () -> assertEquals(1, new Node<>(1).size()),
        () -> assertEquals(2, new Node<>(2).prepend(1).size()),
        () -> assertEquals(3, new Node<>(3).prepend(2).prepend(1).size())
        );
  }
  @Test @Tag("Q2")
  public void testSizeFastEnough() {
    var count = 100_000;
    var node = new Node<>(0);
    for(var i = 1; i < count; i++) {
      node = node.prepend(i);
    }
    var list = node;
    assertTimeout(Duration.ofMillis(10), () -> assertEquals(count, list.size()));
  }
  */
  /*
  @Test @Tag("Q3") @Tag("Q4")
  public void testToString() {
    assertAll(
        () -> assertEquals("1", new Node<>(1).toString()),
        () -> assertEquals("1, 2", new Node<>(2).prepend(1).toString()),
        () -> assertEquals("1, 2, 3", new Node<>(3).prepend(2).prepend(1).toString())
        );
  }
  @Test @Tag("Q3") @Tag("Q4")
  public void testToStringSomeNodes() {
    var count = 10;
    var node = new Node<>(0);
    for(var i = 1; i < count; i++) {
      node = node.prepend(i);
    }
    assertEquals(IntStream.range(0, count).mapToObj(i -> "" + (count - 1 - i)).collect(Collectors.joining(", ")), node.toString());
  }
  */
  /*
  @Test @Tag("Q4")
  public void testToStringNoStackOverflow() {
    var count = 100_000;
    var node = new Node<>(0);
    for(var i = 1; i < count; i++) {
      node = node.prepend(i);
    }
    assertEquals(IntStream.range(0, count).mapToObj(i -> "" + (count - 1 - i)).collect(Collectors.joining(", ")), node.toString());
  }
  */
  /*
  @Test @Tag("Q5") @Tag("Q6")
  public void testApplyFunction() {
    var node = new Node<>("foo").applyFunction(String::toUpperCase);
    assertEquals("FOO", node.element());
  }
  @Test @Tag("Q5") @Tag("Q6")
  public void testApplyFunctionNull() {
    assertThrows(NullPointerException.class, () -> new Node<>("foo").applyFunction(null)); 
  }
  @Test @Tag("Q5") @Tag("Q6")
  public void testApplyFunctionThenToString() {
    assertAll(
        () -> assertEquals("2", new Node<>(1).applyFunction(x -> x * 2).toString()),
        () -> assertEquals("2, 4", new Node<>(2).prepend(1).applyFunction(x -> x * 2).toString()),
        () -> assertEquals("2, 4, 6", new Node<>(3).prepend(2).prepend(1).applyFunction(x -> x * 2).toString())
        );
  }
  @Test @Tag("Q5") @Tag("Q6")
  public void testApplyFunctionThenPrependToString() {
    assertAll(
        () -> assertEquals("0, 2", new Node<>(1).applyFunction(x -> x * 2).prepend(0).toString()),
        () -> assertEquals("0, 2, 4", new Node<>(2).prepend(1).applyFunction(x -> x * 2).prepend(0).toString()),
        () -> assertEquals("0, 2, 4, 6", new Node<>(3).prepend(2).prepend(1).applyFunction(x -> x * 2).prepend(0).toString())
        );
  }
  @Test @Tag("Q5") @Tag("Q6")
  public void testApplyFunctionMoreThanOnce() {
    var newNode = new Node<>(32).prepend(128).applyFunction(x -> x * 2);
    var newNode2 = newNode.prepend(42).applyFunction(x -> x + 1);
    assertAll(
        () -> assertEquals("256, 64", newNode.toString()),
        () -> assertEquals("42, 256, 64", newNode.prepend(42).toString()),
        () -> assertEquals("43, 257, 65", newNode2.toString()),
        () -> assertEquals("17, 43, 257, 65", newNode2.prepend(17).toString())
        );
  }
  @Test @Tag("Q5") @Tag("Q6")
  public void testApplyFunctionThenApplyFunctionThenToString() {
    assertAll(
        () -> assertEquals("1", new Node<>(1).applyFunction(x -> x * 2).applyFunction(x -> x / 2).toString()),
        () -> assertEquals("1, 2", new Node<>(2).prepend(1).applyFunction(x -> x * 2).applyFunction(x -> x / 2).toString()),
        () -> assertEquals("1, 2, 3", new Node<>(3).prepend(2).prepend(1).applyFunction(x -> x * 2).applyFunction(x -> x / 2).toString())
        );
  }
  */
  /*
  @Test @Tag("Q7") @Tag("Q8")
  public void testForEachIndexed() {
    var node = new Node<>(10).prepend(5);
    node.forEachIndexed((index, element) -> assertTrue(element % 5 == 0));
    node.forEachIndexed((index, element) -> assertTrue(index / 2 == 0));
  }
  @Test @Tag("Q7") @Tag("Q8")
  public void testForEachIndexed2() {
    var node = new Node<>("boy");
    node.forEachIndexed((index, element) -> assertEquals("boy", element));
    node.forEachIndexed((index, element) -> assertEquals(0, (int)index));
  }
  @Test @Tag("Q7") @Tag("Q8")
  public void testForEachIndexedNull() {
    var node = new Node<>("foo");
    assertThrows(NullPointerException.class, () -> node.forEachIndexed(null));
  }
  */
  /*
  @Test @Tag("Q8")
  public void testForEachIndexedNoBoxing() {
    var node = new Node<>("boy");
    node.forEachIndexed((int index, String element) -> assertEquals("boy", element));
    node.forEachIndexed((int index, String element) -> assertEquals(0, index));
  }
  */
  /*
  @Test @Tag("Q9")
  public void testToList() {
    assertAll(
        () -> assertEquals(List.of(1), new Node<>(1).toList()),
        () -> assertEquals(List.of(1, 2), new Node<>(2).prepend(1).toList()),
        () -> assertEquals(List.of(1, 2, 3), new Node<>(3).prepend(2).prepend(1).toList())
        );
  }
  */
}
