package vectorizedintview;


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

import java.util.random.RandomGenerator;
import java.util.stream.IntStream;

import static org.junit.jupiter.api.Assertions.*;

public final class VectorizedIntViewTest {

  @Nested
  public class Q1 {

    @Test
    public void containsOnEmptyListIsFalse() {
      var view = new VectorizedIntView();
      assertFalse(view.contains(42));
    }

    @Test
    public void containsFindsExistingElement() {
      var view = new VectorizedIntView(3, 1, 4, 1, 5, 9, 2, 6);
      assertTrue(view.contains(9));
      assertTrue(view.contains(3));
    }

    @Test
    public void containsReturnsFalseForMissingElement() {
      var view = new VectorizedIntView(3, 1, 4, 1, 5, 9, 2, 6);
      assertFalse(view.contains(42));
    }

    @Test
    public void containsWorksWhenSizeIsNotAMultipleOfVectorLength() {
      // taille volontairement "impaire" pour forcer le passage par la post-loop
      var view = new VectorizedIntView(1, 2, 3, 4, 5, 6, 7);
      assertTrue(view.contains(7));
      assertFalse(view.contains(8));
    }

    @Test
    public void containsWorksOnLargeList() {
      var array = IntStream.range(0, 10_000).toArray();
      var view = new VectorizedIntView(array);

      assertTrue(view.contains(9_999));
      assertTrue(view.contains(0));
      assertFalse(view.contains(10_000));
    }
  }


  @Nested
  public class Q2 {

    @Test
    public void indexOfOnEmptyListReturnsMinusOne() {
      var view = new VectorizedIntView();
      assertEquals(-1, view.indexOf(1));
    }

    @Test
    public void indexOfFindsCorrectPosition() {
      var view = new VectorizedIntView(3, 1, 4, 1, 5, 9, 2, 6);
      assertEquals(4, view.indexOf(5));
    }

    @Test
    public void indexOfReturnsFirstOccurrenceWithDuplicates() {
      var view = new VectorizedIntView(3, 1, 4, 1, 5, 9, 2, 6);
      assertEquals(1, view.indexOf(1));
    }

    @Test
    public void indexOfReturnsMinusOneWhenAbsent() {
      var view = new VectorizedIntView(3, 1, 4, 1, 5, 9, 2, 6);
      assertEquals(-1, view.indexOf(42));
    }

    @Test
    public void indexOfFindsElementAtBlockBoundary() {
      var array = IntStream.range(0, 100).toArray();
      var view = new VectorizedIntView(array);

      assertEquals(63, view.indexOf(63));
      assertEquals(64, view.indexOf(64));
    }

    @Test
    public void indexOfFindsElementInPostLoop() {
      var view = new VectorizedIntView(1, 2, 3);
      assertEquals(2, view.indexOf(3));
    }

    @Test
    public void indexOfFindsElementInPostLoop2() {
      var view = new VectorizedIntView(1, 2, 3, 4, 5, 6, 7);
      assertEquals(6, view.indexOf(7));
    }
  }

  @Nested
  public class Q3 {

    @Test
    public void minMaxOnFullRange() {
      var view = new VectorizedIntView(3, 1, 4, 1, 5, 9, 2, 6);
      var result = view.minMax(0, view.size());
      assertEquals(1, result.min());
      assertEquals(9, result.max());
    }

    @Test
    public void minMaxOnSubRange() {
      var view = new VectorizedIntView(3, 1, 4, 1, 5, 9, 2, 6);
      var result = view.minMax(2, 6); // {4, 1, 5, 9}
      assertEquals(1, result.min());
      assertEquals(9, result.max());
    }

    @Test
    public void minMaxOnSingleElementRange() {
      var view = new VectorizedIntView(3, 1, 4, 1, 5, 9, 2, 6);
      var result = view.minMax(5, 6); // {9}
      assertEquals(9, result.min());
      assertEquals(9, result.max());
    }

    @Test
    public void minMaxWithNegativeValues() {
      var view = new VectorizedIntView(-3, 7, -10, 2, 15, -1);
      var result = view.minMax(0, view.size());
      assertEquals(-10, result.min());
      assertEquals(15, result.max());
    }

    @Test
    public void minMaxOnRangeNotMultipleOfVectorLength() {
      var array = IntStream.range(0, 37).map(v -> 37 - v).toArray();
      var view = new VectorizedIntView(array);
      var result = view.minMax(0, view.size());

      assertEquals(1, result.min());
      assertEquals(37, result.max());
    }
  }


  @Nested
  public class Q4 {

    @Test
    public void parallelMinMaxMatchesSequentialOnSmallList() {
      var view = new VectorizedIntView(3, 1, 4, 1, 5, 9, 2, 6);
      var sequential = view.minMax(0, view.size());
      var parallel = view.parallelMinMax();
      assertEquals(sequential.min(), parallel.min());
      assertEquals(sequential.max(), parallel.max());
    }

    @Test
    public void parallelMinMaxOnKnownSequence() {
      var array = IntStream.range(0, 500_000).toArray();
      var view = new VectorizedIntView(array);
      var result = view.parallelMinMax();

      assertEquals(0, result.min());
      assertEquals(499_999, result.max());
    }

    @Test
    public void parallelMinMaxAroundSequentialThreshold() {
      var below = new VectorizedIntView(IntStream.range(0, 1024).toArray());

      assertEquals(0, below.parallelMinMax().min());
      assertEquals(1023, below.parallelMinMax().max());

      var above = new VectorizedIntView(IntStream.range(0, 1025).toArray());

      assertEquals(0, above.parallelMinMax().min());
      assertEquals(1024, above.parallelMinMax().max());
    }

    @Test
    public void parallelMinMaxMatchesSequentialOnRandomLargeList() {
      var random = RandomGenerator.getDefault();
      var array = random.ints(1_000_000, -1_000_000, 1_000_000).toArray();
      var view = new VectorizedIntView(array);

      var sequential = view.minMax(0, view.size());
      var parallel = view.parallelMinMax();

      assertEquals(sequential.min(), parallel.min());
      assertEquals(sequential.max(), parallel.max());
    }
  }
}