package fr.uge.simd;

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;

import java.time.Duration;

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

public class FastScanListTest {
  @Nested
  public class Q1 {
    @Test
    public void preconditionAdd() {
      var list = new FastScanList<String>();
      assertThrows(NullPointerException.class, () -> list.add(null));
    }

    @Test
    public void emptyList() {
      var list = new FastScanList<>();
      assertEquals("[]", "" + list);
    }

    @Test
    public void listOfStrings() {
      var list = new FastScanList<String>();
      for (var i = 0; i < 20; i++) {
        list.add("" + i);
      }
      assertAll(
          () -> assertEquals(20, list.size()),
          () -> assertEquals("[0 (48), 1 (49), 2 (50), 3 (51), 4 (52), 5 (53), 6 (54), 7 (55), 8 (56), 9 (57), 10 (31), 11 (32), 12 (33), 13 (34), 14 (35), 15 (36), 16 (37), 17 (38), 18 (39), 19 (40)]", "" + list)
      );
    }

    @Test
    public void listOfIntegers() {
      var list = new FastScanList<Integer>();
      for (var i = 0; i < 20; i++) {
        list.add(i);
      }
      assertAll(
          () -> assertEquals(20, list.size()),
          () -> assertEquals("[0 (0), 1 (1), 2 (2), 3 (3), 4 (4), 5 (5), 6 (6), 7 (7), 8 (8), 9 (9), 10 (10), 11 (11), 12 (12), 13 (13), 14 (14), 15 (15), 16 (16), 17 (17), 18 (18), 19 (19)]", "" + list)
      );
    }

    @Test
    @Timeout(1_000)
    public void listALotAddFastEnough() {
      var list = new FastScanList<Integer>();
      for (var i = 0; i < 1_000_000; i++) {
        list.add(i);
      }
      assertEquals(list.size(), 1_000_000);
    }


    @Test
    public void listOfStringsContains() {
      var list = new FastScanList<String>();
      list.add("a");
      list.add("a");
      list.add("bb");
      list.add("ccc");
      list.add("d");
      assertAll(
          () -> assertEquals(5, list.size()),
          () -> assertTrue(list.contains("a")),
          () -> assertTrue(list.contains("ccc")),
          () -> assertFalse(list.contains("")),
          () -> assertFalse(list.contains("eee"))
      );
    }

    @Test
    public void listOfIntegersContains() {
      var list = new FastScanList<Integer>();
      list.add(10);
      list.add(30);
      list.add(-128);
      list.add(32);
      assertAll(
          () -> assertEquals(4, list.size()),
          () -> assertTrue(list.contains(30)),
          () -> assertTrue(list.contains(32)),
          () -> assertFalse(list.contains(266)),
          () -> assertFalse(list.contains(286)),
          () -> assertFalse(list.contains(-1))
      );
    }

    @Test
    public void signatureContains() {
      var list = new FastScanList<String>();
      list.add("foo");
      list.add("bar");
      assertFalse(list.contains(747));
    }

    @Test
    public void preconditionContains() {
      var list = new FastScanList<>();
      assertThrows(NullPointerException.class, () -> list.contains(null));
    }


    @Test
    public void collision() {
      var list = new FastScanList<Integer>();
      for (var i = 0; i < 128; i++) {
        var value = i == 66 ? 67 + 256 : i;
        list.add(value);
      }
      assertTrue(list.contains(67));
    }

    @Test
    public void contains0() {
      var list = new FastScanList<Integer>();
      for (var i = 1; i < 100_000; i++) {
        list.add(i);
      }
      assertFalse(list.contains(0));
    }
  }

  @Nested
  public class Q2 {
    @Test
    public void listOfStringssContainsALot() {
      var list = new FastScanList<String>();
      for (var i = 1; i < 100_000; i++) {
        list.add("" + i);
      }
      assertTimeoutPreemptively(Duration.ofSeconds(4), () -> {
        for (var i = 1; i < 100_000; i++) {
          assertTrue(list.contains("" + i));
        }
      });
    }
  }

  @Nested
  public class Q3 {
    @Test
    public void listOfStringssContainsALot() {
      var list = new FastScanList<String>();
      for (var i = 1; i < 100_000; i++) {
        list.add("" + i);
      }
      assertTimeoutPreemptively(Duration.ofSeconds(1), () -> {
        for (var i = 1; i < 100_000; i++) {
          assertTrue(list.contains("" + i));
        }
      });
    }

    @Test
    public void listOfIntegersContainsALot() {
      var threshold = Integer.highestOneBit(100_000) << 1;
      var list = new FastScanList<Integer>();
      for (var i = 1; i < 100_000; i++) {
        list.add(i);
      }
      assertTimeoutPreemptively(Duration.ofSeconds(3), () -> {
        for (var i = 1; i < 100_000; i++) {
          assertTrue(list.contains(i));
          assertFalse(list.contains(i + threshold));
        }
      });
    }
  }
}