package fr.uge.numeric;

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

import java.io.IOException;
import java.lang.classfile.ClassFile;
import java.lang.classfile.Instruction;
import java.lang.classfile.Opcode;
import java.lang.classfile.instruction.InvokeInstruction;
import java.lang.reflect.AccessFlag;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.RandomAccess;
import java.util.Set;
import java.util.Spliterator;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toSet;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

public final class NumericVecTest {

  @Nested
  public class Q1 {
    @Test
    public void testLongsWithNoArguments() {
      var vec = NumericVec.longs();
      assertNotNull(vec);
      assertEquals(0, vec.size());
    }

    @Test
    public void testLongsWithSingleValue() {
      var vec = NumericVec.longs(42L);
      assertEquals(1, vec.size());
      assertEquals(42L, vec.get(0));
    }

    @Test
    public void testLongsWithMultipleValues() {
      var vec = NumericVec.longs(1L, 2L, 3L, 4L, 5L);
      assertEquals(5, vec.size());
      assertEquals(1L, vec.get(0));
      assertEquals(2L, vec.get(1));
      assertEquals(3L, vec.get(2));
      assertEquals(4L, vec.get(3));
      assertEquals(5L, vec.get(4));
    }

    @Test
    public void testLongsWithNegativeValues() {
      var vec = NumericVec.longs(-1L, -100L, -999L);
      assertEquals(3, vec.size());
      assertEquals(-1L, vec.get(0));
      assertEquals(-100L, vec.get(1));
      assertEquals(-999L, vec.get(2));
    }

    @Test
    public void testLongsWithZero() {
      var vec = NumericVec.longs(0L);
      assertEquals(1, vec.size());
      assertEquals(0L, vec.get(0));
    }

    @Test
    public void testLongsWithMixedValues() {
      var vec = NumericVec.longs(-5L, 0L, 5L);
      assertEquals(3, vec.size());
      assertEquals(-5L, vec.get(0));
      assertEquals(0L, vec.get(1));
      assertEquals(5L, vec.get(2));
    }

    @Test
    public void testLongsWithMaxAndMinValues() {
      var vec = NumericVec.longs(Long.MAX_VALUE, Long.MIN_VALUE);
      assertEquals(2, vec.size());
      assertEquals(Long.MAX_VALUE, vec.get(0));
      assertEquals(Long.MIN_VALUE, vec.get(1));
    }

    @Test
    public void testLongsNoEncapsulationViolation() {
      var original = new long[] { 1L, 2L, 3L };
      var vec = NumericVec.longs(original);
      original[0] = 999L;
      assertEquals(1L, vec.get(0));
    }

    @Test
    public void testLongsWithDuplicateValues() {
      var vec = NumericVec.longs(7L, 7L, 7L);
      assertEquals(3, vec.size());
      assertEquals(7L, vec.get(0));
      assertEquals(7L, vec.get(1));
      assertEquals(7L, vec.get(2));
    }

    @Test
    public void testGetThrowsIndexOutOfBoundsForNegativeIndex() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertThrows(IndexOutOfBoundsException.class, () -> vec.get(-1));
    }

    @Test
    public void testGetThrowsIndexOutOfBoundsForIndexEqualToSize() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertThrows(IndexOutOfBoundsException.class, () -> vec.get(3));
    }

    @Test
    public void testGetThrowsIndexOutOfBoundsForIndexGreaterThanSize() {
      var vec = NumericVec.longs(1L, 2L);
      assertThrows(IndexOutOfBoundsException.class, () -> vec.get(10));
    }

    @Test
    public void testGetOnEmptyVec() {
      var vec = NumericVec.longs();
      assertThrows(IndexOutOfBoundsException.class, () -> vec.get(0));
    }

    @Test
    public void testLongsFactoryNullThrowsNullPointerException() {
      assertThrows(NullPointerException.class, () -> NumericVec.longs(null));
    }

    @Test
    public void testLongFactoryReturnsANumericVec() {
      var vec = NumericVec.longs(42L);
      assertSame(NumericVec.class, vec.getClass());
    }

    @Test
    public void testLongsArrayCapacityIsNotBiggerThanSize() throws IllegalAccessException {
      var arrayField = Arrays.stream(NumericVec.class.getDeclaredFields())
          .filter(f -> f.getType().isArray())
          .findFirst().orElseThrow();
      arrayField.setAccessible(true);

      var vec = NumericVec.longs(1L, 2L, 3L);
      assertEquals(3, Array.getLength(arrayField.get(vec)));
    }

    @Test
    public void testClassIsPublicFinal() {
      assertTrue(NumericVec.class.accessFlags().contains(AccessFlag.PUBLIC));
      assertTrue(NumericVec.class.accessFlags().contains(AccessFlag.FINAL));
    }

    @Test
    public void testNoPublicConstructor() {
      assertEquals(0, NumericVec.class.getConstructors().length);
    }

    @Test
    public void testAllFieldsArePrivate() {
      var fields =  NumericVec.class.getDeclaredFields();
      for(var field : fields) {
        assertTrue(field.accessFlags().contains(AccessFlag.PRIVATE));
      }
    }

    @Test
    public void testConstructorsCallSuperAsLastInstruction() throws IOException {
      var className = "/" + NumericVec.class.getName().replace('.', '/') + ".class";
      byte[] data;
      try (var input = NumericVec.class.getResourceAsStream(className)) {
        data = input.readAllBytes();
      }
      var classModel = ClassFile.of().parse(data);
      var constructors =
          classModel.methods().stream().filter(m -> m.methodName().equalsString("<init>")).toList();
      for (var constructor : constructors) {
        var code =
            constructor.code().orElseThrow(() -> new AssertionError("Constructor has no code"));
        var instructions =
            code.elementStream()
                .flatMap(e -> e instanceof Instruction instruction ? Stream.of(instruction) : null)
                .toList();
        var lastInstruction =
            instructions.get(instructions.size() - 2); // -2 because last is RETURN
        if (!(lastInstruction instanceof InvokeInstruction invokeInstruction)) {
          throw new AssertionError(
              "lastInstruction is neither super() nor this() " + lastInstruction);
        }
        assertAll(
            () -> assertEquals(Opcode.INVOKESPECIAL, invokeInstruction.opcode()),
            () -> assertEquals("<init>", invokeInstruction.name().stringValue())
        );
      }
    }
  }


  @Nested
  public class Q2 {

    @Test
    public void testAddToEmptyVec() {
      var vec = NumericVec.longs();
      vec.add(42L);

      assertEquals(1, vec.size());
      assertEquals(42L, vec.get(0));
    }

    @Test
    public void testAddSingleElement() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      vec.add(4L);

      assertEquals(4, vec.size());
      assertEquals(4L, vec.get(3));
    }

    @Test
    public void testAddMultipleElements() {
      var vec = NumericVec.longs(1L);
      vec.add(2L);
      vec.add(3L);
      vec.add(4L);

      assertEquals(4, vec.size());
      assertEquals(1L, vec.get(0));
      assertEquals(2L, vec.get(1));
      assertEquals(3L, vec.get(2));
      assertEquals(4L, vec.get(3));
    }

    @Test
    public void testAddNegativeValue() {
      var vec = NumericVec.longs(10L);
      vec.add(-5L);

      assertEquals(2, vec.size());
      assertEquals(-5L, vec.get(1));
    }

    @Test
    public void testAddZero() {
      var vec = NumericVec.longs(1L);
      vec.add(0L);

      assertEquals(2, vec.size());
      assertEquals(0L, vec.get(1));
    }

    @Test
    public void testAddMaxValue() {
      var vec = NumericVec.longs();
      vec.add(Long.MAX_VALUE);

      assertEquals(1, vec.size());
      assertEquals(Long.MAX_VALUE, vec.get(0));
    }

    @Test
    public void testAddMinValue() {
      var vec = NumericVec.longs();
      vec.add(Long.MIN_VALUE);

      assertEquals(1, vec.size());
      assertEquals(Long.MIN_VALUE, vec.get(0));
    }

    @Test
    public void testAddTriggersArrayGrowth() {
      var vec = NumericVec.longs(1L);
      vec.add(2L);
      vec.add(3L);

      assertEquals(3, vec.size());
      assertEquals(1L, vec.get(0));
      assertEquals(2L, vec.get(1));
      assertEquals(3L, vec.get(2));
    }

    @Test
    public void testAddMultipleTimesToTriggerMultipleGrowths() {
      var vec = NumericVec.longs();
      assertTimeoutPreemptively(Duration.ofSeconds(1), () -> {
        for (var i = 0; i < 1_000_000; i++) {
          vec.add((long) i);
        }
      });

      assertEquals(1_000_000, vec.size());
      for (var i = 0; i < 1_000_000; i++) {
        assertEquals(i, (long) vec.get(i));
      }
    }

    @Test
    public void testAddPreservesExistingElements() {
      var vec = NumericVec.longs(10L, 20L, 30L);
      vec.add(40L);

      assertEquals(10L, vec.get(0));
      assertEquals(20L, vec.get(1));
      assertEquals(30L, vec.get(2));
      assertEquals(40L, vec.get(3));
    }

    @Test
    public void testAddDuplicateValues() {
      var vec = NumericVec.longs(5L);
      vec.add(5L);
      vec.add(5L);

      assertEquals(3, vec.size());
      assertEquals(5L, vec.get(0));
      assertEquals(5L, vec.get(1));
      assertEquals(5L, vec.get(2));
    }

    @Test
    public void testSizeIncreasesAfterEachAdd() {
      var vec = NumericVec.longs();
      assertEquals(0, vec.size());
      vec.add(1L);
      assertEquals(1, vec.size());
      vec.add(2L);
      assertEquals(2, vec.size());
      vec.add(3L);
      assertEquals(3, vec.size());
    }

    @Test
    public void testAddToFullCapacityArray() {
      var vec = NumericVec.longs(1L, 2L, 3L, 4L);
      assertEquals(4, vec.size());

      vec.add(5L);

      assertEquals(5, vec.size());
      assertEquals(5L, vec.get(4));
    }
  }


  @Nested
  public class Q3 {

    @Test
    public void testIntsWithNoArguments() {
      var vec = NumericVec.ints();

      assertNotNull(vec);
      assertEquals(0, vec.size());
    }

    @Test
    public void testIntsWithSingleValue() {
      var vec = NumericVec.ints(42);

      assertEquals(1, vec.size());
      assertEquals(42, vec.get(0));
    }

    @Test
    public void testIntsWithMultipleValues() {
      var vec = NumericVec.ints(1, 2, 3, 4, 5);

      assertEquals(5, vec.size());
      assertEquals(1, vec.get(0));
      assertEquals(2, vec.get(1));
      assertEquals(3, vec.get(2));
      assertEquals(4, vec.get(3));
      assertEquals(5, vec.get(4));
    }

    @Test
    public void testIntsWithNegativeValues() {
      var vec = NumericVec.ints(-1, -100, -999);

      assertEquals(3, vec.size());
      assertEquals(-1, vec.get(0));
      assertEquals(-100, vec.get(1));
      assertEquals(-999, vec.get(2));
    }

    @Test
    public void testIntsWithZero() {
      var vec = NumericVec.ints(0);

      assertEquals(1, vec.size());
      assertEquals(0, vec.get(0));
    }

    @Test
    public void testIntsWithMaxAndMinValues() {
      var vec = NumericVec.ints(Integer.MAX_VALUE, Integer.MIN_VALUE);

      assertEquals(2, vec.size());
      assertEquals(Integer.MAX_VALUE, vec.get(0));
      assertEquals(Integer.MIN_VALUE, vec.get(1));
    }

    @Test
    public void testIntsWithDuplicateValues() {
      var vec = NumericVec.ints(7, 7, 7);

      assertEquals(3, vec.size());
      assertEquals(7, vec.get(0));
      assertEquals(7, vec.get(1));
      assertEquals(7, vec.get(2));
    }

    @Test
    public void testIntsGetThrowsIndexOutOfBoundsForNegativeIndex() {
      var vec = NumericVec.ints(1, 2, 3);
      assertThrows(IndexOutOfBoundsException.class, () -> vec.get(-1));
    }

    @Test
    public void testIntsGetThrowsIndexOutOfBoundsForIndexEqualToSize() {
      var vec = NumericVec.ints(1, 2, 3);
      assertThrows(IndexOutOfBoundsException.class, () -> vec.get(3));
    }

    @Test
    public void testIntsFactoryNullThrowsNullPointerException() {
      assertThrows(NullPointerException.class, () -> NumericVec.ints(null));
    }

    @Test
    public void testIntsAddElement() {
      var vec = NumericVec.ints(1, 2, 3);
      vec.add(4);

      assertEquals(4, vec.size());
      assertEquals(4, vec.get(3));
    }

    @Test
    public void testIntsAddMultipleElements() {
      var vec = NumericVec.ints(1);
      vec.add(2);
      vec.add(3);

      assertEquals(3, vec.size());
      assertEquals(1, vec.get(0));
      assertEquals(2, vec.get(1));
      assertEquals(3, vec.get(2));
    }

    @Test
    public void testIntsReturnType() {
      var vec = NumericVec.ints(1, 2);
      assertSame(NumericVec.class, vec.getClass());
    }

    @Test
    public void testIntsSideMutation() {
      var array = new int[] { 12, 80, 128 };
      var vec = NumericVec.ints(array);
      array[1] = 64;

      assertEquals(80, vec.get(1));
    }


    @Test
    public void testDoublesWithNoArguments() {
      var vec = NumericVec.doubles();

      assertNotNull(vec);
      assertEquals(0, vec.size());
    }

    @Test
    public void testDoublesWithSingleValue() {
      var vec = NumericVec.doubles(42.5);

      assertEquals(1, vec.size());
      assertEquals(42.5, vec.get(0), 0.0);
    }

    @Test
    public void testDoublesWithMultipleValues() {
      var vec = NumericVec.doubles(1.1, 2.2, 3.3, 4.4, 5.5);

      assertEquals(5, vec.size());
      assertEquals(1.1, vec.get(0), 0.0);
      assertEquals(2.2, vec.get(1), 0.0);
      assertEquals(3.3, vec.get(2), 0.0);
      assertEquals(4.4, vec.get(3), 0.0);
      assertEquals(5.5, vec.get(4), 0.0);
    }

    @Test
    public void testDoublesWithNegativeValues() {
      var vec = NumericVec.doubles(-1.5, -100.25, -999.99);

      assertEquals(3, vec.size());
      assertEquals(-1.5, vec.get(0), 0.0);
      assertEquals(-100.25, vec.get(1), 0.0);
      assertEquals(-999.99, vec.get(2), 0.0);
    }

    @Test
    public void testDoublesWithZero() {
      var vec = NumericVec.doubles(0.0);

      assertEquals(1, vec.size());
      assertEquals(0.0, vec.get(0), 0.0);
    }

    @Test
    public void testDoublesWithMaxAndMinValues() {
      var vec = NumericVec.doubles(Double.MAX_VALUE, Double.MIN_VALUE);

      assertEquals(2, vec.size());
      assertEquals(Double.MAX_VALUE, vec.get(0), 0.0);
      assertEquals(Double.MIN_VALUE, vec.get(1), 0.0);
    }

    @Test
    public void testDoublesWithSpecialValues() {
      var vec = NumericVec.doubles(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN);

      assertEquals(3, vec.size());
      assertEquals(Double.POSITIVE_INFINITY, vec.get(0), 0.0);
      assertEquals(Double.NEGATIVE_INFINITY, vec.get(1), 0.0);
      assertTrue(Double.isNaN(vec.get(2)));
    }

    @Test
    public void testDoublesWithNegativeZero() {
      var vec = NumericVec.doubles(-0.0, 0.0);

      assertEquals(2, vec.size());
      assertEquals(-0.0, vec.get(0), 0.0);
      assertEquals(0.0, vec.get(1), 0.0);
      assertEquals(Double.doubleToRawLongBits(-0.0), Double.doubleToRawLongBits(vec.get(0)));
    }

    @Test
    public void testDoublesWithDuplicateValues() {
      var vec = NumericVec.doubles(7.5, 7.5, 7.5);

      assertEquals(3, vec.size());
      assertEquals(7.5, vec.get(0), 0.0);
      assertEquals(7.5, vec.get(1), 0.0);
      assertEquals(7.5, vec.get(2), 0.0);
    }

    @Test
    public void testDoublesGetThrowsIndexOutOfBoundsForNegativeIndex() {
      var vec = NumericVec.doubles(1.0, 2.0, 3.0);
      assertThrows(IndexOutOfBoundsException.class, () -> vec.get(-1));
    }

    @Test
    public void testDoublesGetThrowsIndexOutOfBoundsForIndexEqualToSize() {
      var vec = NumericVec.doubles(1.0, 2.0, 3.0);
      assertThrows(IndexOutOfBoundsException.class, () -> vec.get(3));
    }

    @Test
    public void testDoublesFactoryNullThrowsNullPointerException() {
      assertThrows(NullPointerException.class, () -> NumericVec.doubles(null));
    }

    @Test
    public void testDoublesAddElement() {
      var vec = NumericVec.doubles(1.0, 2.0, 3.0);
      vec.add(4.0);

      assertEquals(4, vec.size());
      assertEquals(4.0, vec.get(3), 0.0);
    }

    @Test
    public void testDoublesAddMultipleElements() {
      var vec = NumericVec.doubles(1.0);
      vec.add(2.5);
      vec.add(3.75);

      assertEquals(3, vec.size());
      assertEquals(1.0, vec.get(0), 0.0);
      assertEquals(2.5, vec.get(1), 0.0);
      assertEquals(3.75, vec.get(2), 0.0);
    }

    @Test
    public void testDoublesReturnType() {
      var vec = NumericVec.doubles(1.0, 2.0);
      assertSame(NumericVec.class, vec.getClass());
    }

    @Test
    public void testDoublesWithVerySmallValues() {
      var vec = NumericVec.doubles(1e-100, 1e-200, 1e-300);

      assertEquals(3, vec.size());
      assertEquals(1e-100, vec.get(0), 0.0);
      assertEquals(1e-200, vec.get(1), 0.0);
      assertEquals(1e-300, vec.get(2), 0.0);
    }

    @Test
    public void testDoublesWithVeryLargeValues() {
      var vec = NumericVec.doubles(1e100, 1e200, 1e300);

      assertEquals(3, vec.size());
      assertEquals(1e100, vec.get(0), 0.0);
      assertEquals(1e200, vec.get(1), 0.0);
      assertEquals(1e300, vec.get(2), 0.0);
    }

    @Test
    public void testDoublesSideMutation() {
      var array = new double[] { 12, 80, 128 };
      var vec = NumericVec.doubles(array);
      array[1] = 64.;

      assertEquals(80., vec.get(1));
    }

    @Test
    public void testNumericClassOnlyHasOneArrayOfLongs() {
      var fields = NumericVec.class.getDeclaredFields();
      assertTrue(Arrays.stream(fields)
          .noneMatch(field -> field.getType().isArray() && field.getType() != long[].class));
    }

    @Test
    public void testIntsArrayCapacityIsNotBiggerThanSize() throws IllegalAccessException {
      var arrayField = Arrays.stream(NumericVec.class.getDeclaredFields())
          .filter(f -> f.getType().isArray())
          .findFirst().orElseThrow();
      arrayField.setAccessible(true);

      var vec = NumericVec.ints(1, 2, 3);
      assertEquals(3, Array.getLength(arrayField.get(vec)));
    }

    @Test
    public void testDoublesArrayCapacityIsNotBiggerThanSize() throws IllegalAccessException {
      var arrayField = Arrays.stream(NumericVec.class.getDeclaredFields())
          .filter(f -> f.getType().isArray())
          .findFirst().orElseThrow();
      arrayField.setAccessible(true);

      var vec = NumericVec.doubles(1., 2., 3.);
      assertEquals(3, Array.getLength(arrayField.get(vec)));
    }

    @Test
    public void testIntsWithValuesNullThrowsNullPointerException() {
      assertThrows(NullPointerException.class, () -> NumericVec.ints(null));
    }

    @Test
    public void testDoublesWithValuesNullThrowsNullPointerException() {
      assertThrows(NullPointerException.class, () -> NumericVec.doubles(null));
    }
  }


  @Nested
  public class Q4 {

    @Test
    public void testStreamWithEmptyVec() {
      var vec = NumericVec.longs();
      var stream = vec.stream();

      assertNotNull(stream);
      assertEquals(0, stream.count());
    }

    @Test
    public void testStreamWithSingleElement() {
      var vec = NumericVec.longs(42L);
      var list = vec.stream().toList();

      assertEquals(1, list.size());
      assertEquals(42L, list.get(0));
    }

    @Test
    public void testStreamPreservesOrder() {
      var vec = NumericVec.longs(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L);
      var list = vec.stream().toList();

      assertEquals(8, list.size());
      assertEquals(1L, list.get(0));
      assertEquals(2L, list.get(1));
      assertEquals(3L, list.get(2));
      assertEquals(4L, list.get(3));
      assertEquals(5L, list.get(4));
      assertEquals(6L, list.get(5));
      assertEquals(7L, list.get(6));
      assertEquals(8L, list.get(7));
    }

    @Test
    public void testStreamCount() {
      var vec = NumericVec.longs(1L, 2L, 3L, 4L, 5L);
      assertEquals(5, vec.stream().map(_ -> fail()).count());
    }

    @Test
    public void testStreamForEach() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var box = new Object() { int sum; };
      vec.stream().forEach(value -> box.sum += value);

      assertEquals(6L, box.sum);
    }

    @Test
    public void testStreamFilter() {
      var vec = NumericVec.longs(1L, 2L, 3L, 4L, 5L, 6L);
      var list = vec.stream().filter(x -> x % 2 == 0).toList();

      assertEquals(3, list.size());
      assertEquals(2L, list.get(0));
      assertEquals(4L, list.get(1));
      assertEquals(6L, list.get(2));
    }

    @Test
    public void testStreamMap() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var list = vec.stream().map(x -> x * 2).toList();

      assertEquals(3, list.size());
      assertEquals(2L, list.get(0));
      assertEquals(4L, list.get(1));
      assertEquals(6L, list.get(2));
    }

    @Test
    public void testStreamReduce() {
      var vec = NumericVec.longs(1L, 2L, 3L, 4L, 5L);
      var result = vec.stream().reduce(0L, Long::sum);
      assertEquals(15L, result);
    }

    @Test
    public void testStreamWithInts() {
      var vec = NumericVec.ints(1, 2, 3, 4, 5);
      var list = vec.stream().toList();

      assertEquals(5, list.size());
      assertEquals(1, list.get(0));
      assertEquals(2, list.get(1));
      assertEquals(3, list.get(2));
      assertEquals(4, list.get(3));
      assertEquals(5, list.get(4));
    }

    @Test
    public void testStreamWithDoubles() {
      var vec = NumericVec.doubles(1.5, 2.5, 3.5);
      var list = vec.stream().toList();

      assertEquals(3, list.size());
      assertEquals(1.5, list.get(0), 0.0);
      assertEquals(2.5, list.get(1), 0.0);
      assertEquals(3.5, list.get(2), 0.0);
    }

    @Test
    public void testStreamMultipleCalls() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var list1 = vec.stream().toList();
      var list2 = vec.stream().toList();

      assertEquals(list1, list2);
    }

    @Test
    public void testStreamDoesNotModifyOriginal() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      vec.stream().forEach(_ -> {});

      assertEquals(3, vec.size());
      assertEquals(1L, vec.get(0));
      assertEquals(2L, vec.get(1));
      assertEquals(3L, vec.get(2));
    }

    @Test
    public void testStreamFindFirst() {
      var vec = NumericVec.longs(10L, 20L, 30L);
      var result = vec.stream().findFirst();

      assertTrue(result.isPresent());
      assertEquals(10L, result.get());
    }

    @Test
    public void testStreamFindFirstOnEmpty() {
      var vec = NumericVec.longs();
      var result = vec.stream().findFirst();

      assertFalse(result.isPresent());
    }

    @Test
    public void testStreamAnyMatch() {
      var vec = NumericVec.longs(1L, 2L, 3L, 4L, 5L);

      assertTrue(vec.stream().anyMatch(x -> x == 3L));
      assertFalse(vec.stream().anyMatch(x -> x == 10L));
    }

    @Test
    public void testStreamAllMatch() {
      var vec = NumericVec.longs(2L, 4L, 6L, 8L);

      assertTrue(vec.stream().allMatch(x -> x % 2 == 0));
      assertFalse(vec.stream().allMatch(x -> x > 5));
    }

    @Test
    public void testStreamNoneMatch() {
      var vec = NumericVec.longs(1L, 3L, 5L, 7L);

      assertTrue(vec.stream().noneMatch(x -> x % 2 == 0));
      assertFalse(vec.stream().noneMatch(x -> x > 0));
    }

    @Test
    public void testStreamCollect() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var set = vec.stream().collect(Collectors.toSet());

      assertEquals(3, set.size());
      assertTrue(set.contains(1L));
      assertTrue(set.contains(2L));
      assertTrue(set.contains(3L));
    }

    @Test
    public void testStreamSkip() {
      var vec = NumericVec.longs(1L, 2L, 3L, 4L, 5L);
      var list = vec.stream().skip(2).toList();

      assertEquals(3, list.size());
      assertEquals(3L, list.get(0));
      assertEquals(4L, list.get(1));
      assertEquals(5L, list.get(2));
    }

    @Test
    public void testStreamLimit() {
      var vec = NumericVec.longs(1L, 2L, 3L, 4L, 5L);
      var list = vec.stream().limit(3).toList();

      assertEquals(3, list.size());
      assertEquals(1L, list.get(0));
      assertEquals(2L, list.get(1));
      assertEquals(3L, list.get(2));
    }

    @Test
    public void testStreamDistinct() {
      var vec = NumericVec.longs(1L, 2L, 2L, 3L, 3L, 3L);
      var list = vec.stream().distinct().toList();

      assertEquals(3, list.size());
      assertTrue(list.contains(1L));
      assertTrue(list.contains(2L));
      assertTrue(list.contains(3L));
    }

    @Test
    public void testStreamSorted() {
      var vec = NumericVec.longs(5L, 1L, 4L, 2L, 3L);
      var list = vec.stream().sorted().toList();

      assertEquals(5, list.size());
      assertEquals(1L, list.get(0));
      assertEquals(2L, list.get(1));
      assertEquals(3L, list.get(2));
      assertEquals(4L, list.get(3));
      assertEquals(5L, list.get(4));
    }

    @Test
    public void testStreamMax() {
      var vec = NumericVec.longs(3L, 7L, 2L, 9L, 1L);
      var result = vec.stream().max(Long::compare);

      assertTrue(result.isPresent());
      assertEquals(9L, result.get());
    }

    @Test
    public void testStreamMin() {
      var vec = NumericVec.longs(3L, 7L, 2L, 9L, 1L);
      var result = vec.stream().min(Long::compare);

      assertTrue(result.isPresent());
      assertEquals(1L, result.get());
    }

    @Test
    public void testSpliteratorTryAdvance() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var spliterator = vec.stream().spliterator();
      var values = new ArrayList<Long>();
      while (spliterator.tryAdvance(values::add)) {
        // empty
      }

      assertEquals(3, values.size());
      assertEquals(1L, values.get(0));
      assertEquals(2L, values.get(1));
      assertEquals(3L, values.get(2));
    }

    @Test
    public void testSpliteratorEstimateSize() {
      var vec = NumericVec.longs(1L, 2L, 3L, 4L, 5L);
      var spliterator = vec.stream().spliterator();

      assertEquals(5, spliterator.estimateSize());
    }

    @Test
    public void testSpliteratorEstimateSizeAfterAdvance() {
      var vec = NumericVec.longs(1L, 2L, 3L, 4L, 5L);
      var spliterator = vec.stream().spliterator();
      spliterator.tryAdvance(x -> assertEquals(1L, x));

      assertEquals(4, spliterator.estimateSize());
    }

    @Test
    public void testSpliteratorCharacteristics() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var spliterator = vec.stream().spliterator();

      assertTrue(spliterator.hasCharacteristics(Spliterator.NONNULL));
    }

    @Test
    public void testSpliteratorTrySplitOnSmallVec() {
      var vec = NumericVec.longs();
      LongStream.range(0, 512).forEach(vec::add);

      assertNull(vec.stream().spliterator().trySplit());
    }

    @Test
    public void testSpliteratorTrySplitOnLargeVec() {
      var vec = NumericVec.longs();
      LongStream.range(0, 2_048).forEach(vec::add);

      assertNotNull(vec.stream().spliterator().trySplit());
    }

    @Test
    public void testSpliteratorTrySplitPreservesElements() {
      var vec = NumericVec.longs(LongStream.range(0, 2000).toArray());
      var spliterator = vec.stream().spliterator();
      var split = spliterator.trySplit();

      var values1 = new ArrayList<Long>();
      var values2 = new ArrayList<Long>();
      split.forEachRemaining(values1::add);
      spliterator.forEachRemaining(values2::add);
      assertEquals(2000, values1.size() + values2.size());
    }

    @Test
    public void testSpliteratorTrySplitDividesMidpoint() {
      var vec = NumericVec.longs(LongStream.range(0, 2000).toArray());
      var spliterator = vec.stream().spliterator();
      var originalSize = spliterator.estimateSize();
      var split = spliterator.trySplit();

      var splitSize = split.estimateSize();
      var remainingSize = spliterator.estimateSize();
      assertEquals(originalSize, splitSize + remainingSize);
    }

    @Test
    public void testSpliteratorTryAdvanceReturnsFalseWhenEmpty() {
      var vec = NumericVec.longs();
      var spliterator = vec.stream().spliterator();
      var advanced = spliterator.tryAdvance(_ -> fail());

      assertFalse(advanced);
    }

    @Test
    public void testSpliteratorTryAdvanceReturnsFalseAfterExhaustion() {
      var vec = NumericVec.longs(1L);
      var spliterator = vec.stream().spliterator();

      assertTrue(spliterator.tryAdvance(x -> assertEquals(1L, x)));
      var advanced = spliterator.tryAdvance(_ -> fail());
      assertFalse(advanced);
    }

    @Test
    public void streamNotParallelByDefault() {
      var stream = NumericVec.longs(200L).stream();
      assertFalse(stream.isParallel());
    }

    @Test
    public void testStreamParallelReduce() {
      var vec = NumericVec.longs(LongStream.range(0, 10_000).toArray());
      var sum = vec.stream().parallel().reduce(0L, Long::sum);

      var expectedSum = 10_000L * (10_000L - 1) / 2;
      assertEquals(expectedSum, sum);
    }

    @Test
    public void testStreamParallelUsesSeveralThreads() {
      var threads = new CopyOnWriteArraySet<Thread>();
      var vec = NumericVec.longs(LongStream.range(0, 1_000_000).toArray());
      var sum = vec.stream().parallel()
          .peek(_ -> threads.add(Thread.currentThread()))
          .mapToLong(x -> x)
          .sum();

      var expectedSum = 1_000_000L * (1_000_000L - 1) / 2;
      assertEquals(expectedSum, sum);
      assertTrue(threads.size() > 1);
    }

    @Test
    public void streamCharacteristics() {
      var spliterator = NumericVec.longs().stream().spliterator();
      assertAll(
          () -> assertTrue(spliterator.hasCharacteristics(Spliterator.NONNULL)),
          () -> assertTrue(spliterator.hasCharacteristics(Spliterator.ORDERED)),
          () -> assertTrue(spliterator.hasCharacteristics(Spliterator.IMMUTABLE))
      );
    }
  }


  @Nested
  public class Q5 {

    @Test
    public void testImplementsListInterface() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertTrue(vec instanceof List);
    }

    @Test
    public void testImplementsRandomAccess() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertTrue(vec instanceof RandomAccess);
    }

    @Test
    public void testSizeEmpty() {
      var vec = NumericVec.longs();
      assertEquals(0, vec.size());
    }

    @Test
    public void testSizeNonEmpty() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertEquals(3, vec.size());
    }

    @Test
    public void testIsEmptyTrue() {
      var vec = NumericVec.longs();
      assertTrue(vec.isEmpty());
    }

    @Test
    public void testIsEmptyFalse() {
      var vec = NumericVec.longs(1L);
      assertFalse(vec.isEmpty());
    }

    @Test
    public void testGetValidIndex() {
      var vec = NumericVec.longs(10L, 20L, 30L);

      assertEquals(10L, vec.get(0));
      assertEquals(20L, vec.get(1));
      assertEquals(30L, vec.get(2));
    }

    @Test
    public void testGetNegativeIndex() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertThrows(IndexOutOfBoundsException.class, () -> vec.get(-1));
    }

    @Test
    public void testGetIndexEqualToSize() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertThrows(IndexOutOfBoundsException.class, () -> vec.get(3));
    }

    @Test
    public void testGetIndexGreaterThanSize() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertThrows(IndexOutOfBoundsException.class, () -> vec.get(10));
    }

    @Test
    public void testGetFirst() {
      var vec = NumericVec.longs(10L, 20L, 30L);

      assertEquals(10L, vec.getFirst());
    }

    @Test
    public void testGetLast() {
      var vec = NumericVec.longs(10L, 20L, 30L);

      assertEquals(30L, vec.getLast());
    }

    @Test
    public void testAddElement() {
      var vec = NumericVec.longs(1L, 2L);
      assertTrue(vec.add(3L));

      assertEquals(3, vec.size());
      assertEquals(3L, vec.get(2));
    }

    @Test
    public void testAddNull() {
      var vec = NumericVec.longs(1L, 2L);
      assertThrows(NullPointerException.class, () -> vec.add(null));
    }

    @Test
    public void testAddMultipleElements() {
      var vec = NumericVec.longs();
      vec.add(1L);
      vec.add(2L);
      vec.add(3L);

      assertEquals(3, vec.size());
      assertEquals(1L, vec.get(0));
      assertEquals(2L, vec.get(1));
      assertEquals(3L, vec.get(2));
    }

    @Test
    public void testAddTriggersResize() {
      var vec = NumericVec.longs();
      for (var i = 0L; i < 1_000_000L; i++) {
        vec.add(i);
      }
      assertEquals(1_000_000, vec.size());
    }

    @Test
    public void testContainsElementPresent() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertTrue(vec.contains(2L));
    }

    @Test
    public void testContainsElementAbsent() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertFalse(vec.contains(5L));
    }

    @Test
    public void testContainsElementAbsentWrongType() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertFalse(vec.contains("hello"));
    }

    @Test
    public void testIndexOfElementPresent() {
      var vec = NumericVec.longs(1L, 2L, 3L, 2L);
      assertEquals(1, vec.indexOf(2L));
    }

    @Test
    public void testIndexOfElementAbsent() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertEquals(-1, vec.indexOf(5L));
    }

    @Test
    public void testIndexOfNull() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertEquals(-1, vec.indexOf(null));
    }

    @Test
    public void testLastIndexOfElementPresent() {
      var vec = NumericVec.longs(1L, 2L, 3L, 2L);
      assertEquals(3, vec.lastIndexOf(2L));
    }

    @Test
    public void testLastIndexOfElementAbsent() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertEquals(-1, vec.lastIndexOf(5L));
    }

    @Test
    public void testToArrayObject() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var array = vec.toArray();

      assertEquals(3, array.length);
      assertArrayEquals(new Object[]{ 1L, 2L, 3L }, array);
    }

    @Test
    public void testToArrayWithParameterZero() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var array = vec.toArray(new Long[0]);

      assertEquals(3, array.length);
      assertArrayEquals(new Long[]{ 1L, 2L, 3L }, array);
    }

    @Test
    public void testToArrayWithLargerParameter() {
      var vec = NumericVec.longs(1L, 2L);
      var array = vec.toArray(new Long[5]);

      assertEquals(5, array.length);
      assertEquals(1L, array[0]);
      assertEquals(2L, array[1]);
      assertNull(array[2]);
    }

    @Test
    public void testToArrayFunction() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var array = vec.toArray(Long[]::new);

      assertEquals(3, array.length);
      assertArrayEquals(new Long[]{ 1L, 2L, 3L }, array);
    }

    @Test
    public void testIteratorHasNext() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var iterator = vec.iterator();
      assertTrue(iterator.hasNext());
      assertTrue(iterator.hasNext());
      iterator.next();
      assertTrue(iterator.hasNext());
      assertTrue(iterator.hasNext());
      iterator.next();
      assertTrue(iterator.hasNext());
      assertTrue(iterator.hasNext());
      iterator.next();
      assertFalse(iterator.hasNext());
      assertFalse(iterator.hasNext());
    }

    @Test
    public void testIteratorNext() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var iterator = vec.iterator();

      assertEquals(1L, iterator.next());
      assertEquals(2L, iterator.next());
      assertEquals(3L, iterator.next());
    }

    @Test
    public void testIteratorNextThrowsException() {
      var vec = NumericVec.longs(1L);
      var iterator = vec.iterator();

      iterator.next();
      assertThrows(NoSuchElementException.class, iterator::next);
    }

    @Test
    public void testIteratorOnEmptyList() {
      var vec = NumericVec.longs();
      var iterator = vec.iterator();

      assertFalse(iterator.hasNext());
      assertFalse(iterator.hasNext());
    }

    @Test
    public void testForEachLoop() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var sum = 0L;
      for (var value : vec) {
        sum += value;
      }
      assertEquals(6L, sum);
    }

    @Test
    public void testSpliteratorCharacteristics() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var spliterator = vec.spliterator();

      assertTrue(spliterator.hasCharacteristics(Spliterator.NONNULL));
    }

    @Test
    public void testSpliteratorEstimateSize() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var spliterator = vec.spliterator();

      assertEquals(3, spliterator.estimateSize());
    }

    @Test
    public void testSpliteratorTryAdvance() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var spliterator = vec.spliterator();

      var results = new ArrayList<Long>();
      while (spliterator.tryAdvance(results::add)) {
        // consume all elements
      }
      assertEquals(List.of(1L, 2L, 3L), results);
    }

    @Test
    public void testSpliteratorTrySplitSmallSize() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var spliterator = vec.spliterator();

      assertNull(spliterator.trySplit());
    }

    @Test
    public void testSpliteratorTrySplitLargeSize() {
      var longs = LongStream.range(0, 2000).toArray();
      var vec = NumericVec.longs(longs);

      var spliterator = vec.spliterator();
      var split = spliterator.trySplit();
      assertNotNull(split);
    }

    @Test
    public void testStreamOperations() {
      var vec = NumericVec.longs(1L, 2L, 3L, 4L, 5L);
      var result = vec.stream()
          .filter(x -> x % 2 == 0)
          .toList();

      assertEquals(List.of(2L, 4L), result);
    }

    @Test
    public void testParallelStream() {
      var longs = LongStream.range(0, 10_000).toArray();
      var vec = NumericVec.longs(longs);
      var sum = vec.parallelStream()
          .mapToLong(x -> x)
          .sum();

      assertEquals(49_995_000L, sum);
    }

    @Test
    public void testEqualsWithSameList() {
      var vec1 = NumericVec.longs(1L, 2L, 3L);
      var vec2 = NumericVec.longs(1L, 2L, 3L);
      assertEquals(vec1, vec2);
    }

    @Test
    public void testEqualsWithDifferentList() {
      var vec1 = NumericVec.longs(1L, 2L, 3L);
      var vec2 = NumericVec.longs(1L, 2L, 4L);
      assertNotEquals(vec1, vec2);
    }

    @Test
    public void testEqualsWithList() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var list = List.of(1L, 2L, 3L);
      assertEquals(vec, list);
      assertEquals(list, vec);
    }

    @Test
    public void testEqualsWithArrayList() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var list = new ArrayList<>(List.of(1L, 2L, 3L));
      assertEquals(vec, list);
      assertEquals(list, vec);
    }

    @Test
    public void testHashCode() {
      var vec1 = NumericVec.longs(1L, 2L, 3L);
      var vec2 = NumericVec.longs(1L, 2L, 3L);
      assertEquals(vec1.hashCode(), vec2.hashCode());
    }

    @Test
    public void testHashCodeConsistentWithList() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var list = List.of(1L, 2L, 3L);
      assertEquals(vec.hashCode(), list.hashCode());
    }

    @Test
    public void testHashCodeConsistentWithArrayList() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var list = new ArrayList<>(List.of(1L, 2L, 3L));
      assertEquals(vec.hashCode(), list.hashCode());
    }

    @Test
    public void testToString() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertEquals("[1, 2, 3]", vec.toString());
    }

    @Test
    public void testToStringEmpty() {
      var vec = NumericVec.longs();
      assertEquals("[]", vec.toString());
    }

    @Test
    public void testSubList() {
      var vec = NumericVec.longs(0L, 1L, 2L, 3L, 4L);
      var subList = vec.subList(1, 4);

      assertEquals(3, subList.size());
      assertEquals(1L, subList.get(0));
      assertEquals(2L, subList.get(1));
      assertEquals(3L, subList.get(2));
    }

    @Test
    public void testSubListEmpty() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var subList = vec.subList(1, 1);

      assertTrue(subList.isEmpty());
    }

    @Test
    public void testAddAllCollection() {
      var vec = NumericVec.longs(1L, 2L);
      var result = vec.addAll(List.of(3L, 4L, 5L));

      assertTrue(result);
      assertEquals(5, vec.size());
      assertEquals(List.of(1L, 2L, 3L, 4L, 5L), vec);
    }

    @Test
    public void testAddAllEmptyCollection() {
      var vec = NumericVec.longs(1L, 2L);
      var result = vec.addAll(List.of());

      assertFalse(result);
      assertEquals(2, vec.size());
    }

    @Test
    public void testClear() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertThrows(UnsupportedOperationException.class, vec::clear);
    }

    @Test
    public void testRemove() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertThrows(UnsupportedOperationException.class, () -> vec.remove(0));
    }

    @Test
    public void testRemoveAll() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertThrows(UnsupportedOperationException.class, () -> vec.removeAll(List.of(1L, 3L)));
    }

    @Test
    public void testListIterator() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var listIterator = vec.listIterator();

      assertTrue(listIterator.hasNext());
      assertEquals(1L, listIterator.next());
      assertEquals(2L, listIterator.next());
    }

    @Test
    public void testListIteratorWithIndex() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var listIterator = vec.listIterator(1);

      assertEquals(2L, listIterator.next());
    }

    @Test
    public void testWithIntegers() {
      var vec = NumericVec.ints(1, 2, 3);

      assertTrue(vec instanceof List);
      assertEquals(3, vec.size());
      assertEquals(2, vec.get(1));
    }

    @Test
    public void testWithDoubles() {
      var vec = NumericVec.doubles(1.5, 2.5, 3.5);

      assertTrue(vec instanceof List);
      assertEquals(3, vec.size());
      assertEquals(2.5, vec.get(1));
    }

    @Test
    public void testLongsVecEquals() {
      var vec = NumericVec.longs(1L, 2L, 3L, 4L);
      assertEquals(NumericVec.longs(1L, 2L, 3L, 4L), vec);
    }

    @Test
    public void testIntsVecEquals() {
      var vec = NumericVec.ints(1, 2, 3, 4);
      assertEquals(NumericVec.ints(1, 2, 3, 4), vec);
    }

    @Test
    public void testDoublesVecEquals() {
      var vec = NumericVec.doubles(1., 2., 3., 4.);
      assertEquals(NumericVec.doubles(1., 2., 3., 4.), vec);
    }
  }


  @Nested
  public class Q6 {

    @Test
    public void testAddAllNumericVecSameType() {
      var vec1 = NumericVec.longs(1L, 2L);
      var vec2 = NumericVec.longs(3L, 4L, 5L);
      var result = vec1.addAll(vec2);

      assertTrue(result);
      assertEquals(5, vec1.size());
      assertEquals(List.of(1L, 2L, 3L, 4L, 5L), vec1);
    }

    @Test
    public void testAddAllNumericVecEmpty() {
      var vec1 = NumericVec.longs(1L, 2L);
      var vec2 = NumericVec.longs();
      var result = vec1.addAll(vec2);

      assertFalse(result);
      assertEquals(2, vec1.size());
      assertEquals(List.of(1L, 2L), vec1);
    }

    @Test
    public void testAddAllNumericVecToEmpty() {
      var vec1 = NumericVec.longs();
      var vec2 = NumericVec.longs(1L, 2L, 3L);
      var result = vec1.addAll(vec2);

      assertTrue(result);
      assertEquals(3, vec1.size());
      assertEquals(List.of(1L, 2L, 3L), vec1);
    }

    @Test
    public void testAddAllNumericVecTriggersResize() {
      var vec1 = NumericVec.longs(1L);
      var vec2 = NumericVec.longs(LongStream.range(0, 100).toArray());
      var result = vec1.addAll(vec2);

      assertTrue(result);
      assertEquals(101, vec1.size());
      assertEquals(1L, vec1.get(0));
      assertEquals(0L, vec1.get(1));
      assertEquals(99L, vec1.get(100));
    }

    @Test
    public void testAddAllNumericVecIntegers() {
      var vec1 = NumericVec.ints(1, 2);
      var vec2 = NumericVec.ints(3, 4, 5);
      var result = vec1.addAll(vec2);

      assertTrue(result);
      assertEquals(5, vec1.size());
      assertEquals(List.of(1, 2, 3, 4, 5), vec1);
    }

    @Test
    public void testAddAllNumericVecDoubles() {
      var vec1 = NumericVec.doubles(1.0, 2.0);
      var vec2 = NumericVec.doubles(3.0, 4.0, 5.0);
      var result = vec1.addAll(vec2);

      assertTrue(result);
      assertEquals(5, vec1.size());
      assertEquals(List.of(1.0, 2.0, 3.0, 4.0, 5.0), vec1);
    }

    @Test
    public void testAddAllRegularCollection() {
      var vec = NumericVec.longs(1L, 2L);
      var list = List.of(3L, 4L, 5L);
      var result = vec.addAll(list);

      assertTrue(result);
      assertEquals(5, vec.size());
      assertEquals(List.of(1L, 2L, 3L, 4L, 5L), vec);
    }

    @Test
    public void testAddAllEmptyRegularCollection() {
      var vec = NumericVec.longs(1L, 2L);
      var list = List.<Long>of();
      var result = vec.addAll(list);

      assertFalse(result);
      assertEquals(2, vec.size());
    }

    @Test
    public void testAddAllArrayList() {
      var vec = NumericVec.longs(1L, 2L);
      var arrayList = new ArrayList<>(List.of(3L, 4L, 5L));
      var result = vec.addAll(arrayList);

      assertTrue(result);
      assertEquals(5, vec.size());
      assertEquals(List.of(1L, 2L, 3L, 4L, 5L), vec);
    }

    @Test
    public void testLongsAddAllNullCollection() {
      var vec = NumericVec.longs(1L, 2L);
      assertThrows(NullPointerException.class, () -> vec.addAll(null));
    }

    @Test
    public void testIntsAddAllNullCollection() {
      var vec = NumericVec.ints(1, 2);
      assertThrows(NullPointerException.class, () -> vec.addAll(null));
    }

    @Test
    public void testDoublesAddAllNullCollection() {
      var vec = NumericVec.doubles(1., 2.);
      assertThrows(NullPointerException.class, () -> vec.addAll(null));
    }

    @Test
    public void testAddAllPreservesOriginalCollection() {
      var vec1 = NumericVec.longs(1L, 2L);
      var vec2 = NumericVec.longs(3L, 4L, 5L);
      vec1.addAll(vec2);

      assertEquals(List.of(3L, 4L, 5L), vec2);
      assertEquals(3, vec2.size());
      assertEquals(3L, vec2.get(0));
      assertEquals(4L, vec2.get(1));
      assertEquals(5L, vec2.get(2));
    }

    @Test
    public void testAddAllLargeNumericVec() {
      var vec1 = NumericVec.longs(1L, 2L);
      var vec2 = NumericVec.longs(LongStream.range(0, 10_000).toArray());
      var result = vec1.addAll(vec2);

      assertTrue(result);
      assertEquals(10002, vec1.size());
      assertEquals(1L, vec1.get(0));
      assertEquals(2L, vec1.get(1));
      assertEquals(0L, vec1.get(2));
      assertEquals(9999L, vec1.get(10001));
    }

    @Test
    public void testAddAllWithArrayListContainingNull() {
      var vec = NumericVec.longs(1L, 2L);
      var list = new ArrayList<Long>();
      list.add(3L);
      list.add(null);
      list.add(4L);
      assertThrows(NullPointerException.class, () -> vec.addAll(list));
    }

    @Test
    public void testAddAllWithAsListContainingNull() {
      var vec = NumericVec.longs(1L, 2L);
      var list = Arrays.asList(3L, null, 5L);
      assertThrows(NullPointerException.class, () -> vec.addAll(list));
    }

    @Test
    public void testAddAllIntsList() {
      var vec = NumericVec.ints(1, 2);
      var result = vec.addAll(List.of(3, 4));
      assertTrue(result);
      assertEquals(List.of(1, 2, 3, 4), vec);
    }

    @Test
    public void testAddAllDoubleList() {
      var vec = NumericVec.doubles(1., 2.);
      var result = vec.addAll(List.of(3., 4.));
      assertTrue(result);
      assertEquals(List.of(1., 2., 3., 4.), vec);
    }

    @Test
    public void testAddAllLongsSet() {
      var vec = NumericVec.longs(1L, 2L);
      var result = vec.addAll(new LinkedHashSet<>(List.of(3L, 4L)));
      assertTrue(result);
      assertEquals(List.of(1L, 2L, 3L, 4L), vec);
    }

    @Test
    public void testAddAllIntsSet() {
      var vec = NumericVec.ints(1, 2);
      var result = vec.addAll(new LinkedHashSet<>(List.of(3, 4)));
      assertTrue(result);
      assertEquals(List.of(1, 2, 3, 4), vec);
    }

    @Test
    public void testAddAllDoubleSet() {
      var vec = NumericVec.doubles(1., 2.);
      var result = vec.addAll(new LinkedHashSet<>(List.of(3., 4.)));
      assertTrue(result);
      assertEquals(List.of(1., 2., 3., 4.), vec);
    }

    @Test
    public void testAddAllVecInts() {
      var vec = NumericVec.ints(44, 666);
      var vec2 = NumericVec.ints(77, 888);
      var result = vec.addAll(vec2);
      assertTrue(result);
      assertEquals(NumericVec.ints(44, 666, 77, 888), vec);
    }

    @Test
    public void testAddAllVecDoubles() {
      var vec = NumericVec.doubles(44, 666);
      var vec2 = NumericVec.doubles(77, 888);
      var result = vec.addAll(vec2);
      assertTrue(result);
      assertEquals(NumericVec.doubles(44, 666, 77, 888), vec);
    }

    @Test
    @SuppressWarnings({"rawtypes", "unchecked"})
    public void testAddAllShouldThrowAnExceptionInCaseOfTypePollution() {
      NumericVec<Integer> vec = NumericVec.ints(1, 2);
      NumericVec vec2 = NumericVec.longs(3);
      assertThrows(ClassCastException.class, () -> vec.addAll(vec2));
    }

    @Test
    public void testAddAllIntsLargeDataset() {
      var vec = NumericVec.ints();
      IntStream.range(0, 1_000_000).forEach(vec::add);
      var vec2 = NumericVec.ints();
      IntStream.range(1_000_000, 2_000_000).forEach(vec2::add);
      var result = vec.addAll(vec2);
      assertTrue(result);
      assertAll(
          () -> assertEquals(2_000_000, vec.size()),
          () -> IntStream.range(0, 2_000_000).forEach(i -> assertEquals(i, vec.get(i)))
      );
    }

    @Test
    public void testAddAllLongsLargeDataset() {
      var vec = NumericVec.longs();
      LongStream.range(0, 1_000_000).forEach(vec::add);
      var vec2 = NumericVec.longs();
      LongStream.range(1_000_000, 2_000_000).forEach(vec2::add);
      var result = vec.addAll(vec2);
      assertTrue(result);
      assertAll(
          () -> assertEquals(2_000_000, vec.size()),
          () -> IntStream.range(0, 2_000_000).forEach(i -> assertEquals((long) i, vec.get(i)))
      );
    }

    @Test
    public void addAllDoublesLargeDataset() {
      var vec = NumericVec.doubles();
      IntStream.range(0, 1_000_000).mapToDouble(i -> i).forEach(vec::add);
      var vec2 = NumericVec.doubles();
      IntStream.range(1_000_000, 2_000_000).mapToDouble(i -> i).forEach(vec2::add);
      var result = vec.addAll(vec2);
      assertTrue(result);
      assertAll(
          () -> assertEquals(2_000_000, vec.size()),
          () -> IntStream.range(0, 2_000_000).forEach(i -> assertEquals((double) i, vec.get(i)))
      );
    }
  }


  @Nested
  public class Q7 {

    @Test
    public void testCollectLongsFromStream() {
      var vec = Stream.of(1L, 2L, 3L, 4L, 5L)
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertEquals(5, vec.size());
      assertEquals(List.of(1L, 2L, 3L, 4L, 5L), vec);
    }

    @Test
    public void testCollectIntsFromStream() {
      var vec = Stream.of(1, 2, 3, 4, 5)
          .collect(NumericVec.toNumericVec(NumericVec::ints));

      assertEquals(5, vec.size());
      assertEquals(List.of(1, 2, 3, 4, 5), vec);
    }

    @Test
    public void testCollectDoublesFromStream() {
      var vec = Stream.of(1.0, 2.0, 3.0, 4.0, 5.0)
          .collect(NumericVec.toNumericVec(NumericVec::doubles));

      assertEquals(5, vec.size());
      assertEquals(List.of(1.0, 2.0, 3.0, 4.0, 5.0), vec);
    }

    @Test
    public void testCollectEmptyStream() {
      var vec = Stream.<Long>empty()
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertTrue(vec.isEmpty());
      assertEquals(0, vec.size());
    }

    @Test
    public void testCollectSingleElement() {
      var vec = Stream.of(42L)
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertEquals(1, vec.size());
      assertEquals(42L, vec.get(0));
    }

    @Test
    public void testCollectWithFilter() {
      var vec = Stream.of(1L, 2L, 3L, 4L, 5L, 6L)
          .filter(x -> x % 2 == 0)
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertEquals(3, vec.size());
      assertEquals(List.of(2L, 4L, 6L), vec);
    }

    @Test
    public void testCollectWithMap() {
      var vec = Stream.of(1L, 2L, 3L)
          .map(x -> x * 2)
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertEquals(3, vec.size());
      assertEquals(List.of(2L, 4L, 6L), vec);
    }

    @Test
    public void testCollectLargeStream() {
      var vec = LongStream.range(0, 10_000)
          .boxed()
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertEquals(10_000, vec.size());
      assertEquals(0L, vec.get(0));
      assertEquals(9_999L, vec.get(9_999));
    }

    @Test
    public void testCollectParallelStream() {
      var vec = LongStream.range(0, 1000)
          .parallel()
          .boxed()
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertEquals(1000, vec.size());
      var list = vec.stream().toList();
      assertEquals(LongStream.range(0, 1000).boxed().toList(), list);
    }

    @Test
    public void testCollectParallelStreamIntegers() {
      var vec = IntStream.range(0, 1000)
          .parallel()
          .boxed()
          .collect(NumericVec.toNumericVec(NumericVec::ints));

      assertEquals(1000, vec.size());
      var list = vec.stream().toList();
      assertEquals(IntStream.range(0, 1000).boxed().toList(), list);
    }

    @Test
    public void testCollectParallelStreamDoubles() {
      var vec = DoubleStream.iterate(1.0, d -> d + 1.0)
          .limit(1000)
          .parallel()
          .boxed()
          .collect(NumericVec.toNumericVec(NumericVec::doubles));

      assertEquals(1000, vec.size());
      var list = vec.stream().toList();
      assertEquals(DoubleStream.iterate(1.0, d -> d + 1.0).limit(1000).boxed().sorted().toList(), list);
    }

    @Test
    public void testCollectorNullSupplier() {
      assertThrows(NullPointerException.class, () -> NumericVec.toNumericVec(null));
    }

    @Test
    public void testCollectWithFlatMap() {
      var vec = Stream.of(List.of(1L, 2L), List.of(3L, 4L), List.of(5L))
          .flatMap(List::stream)
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertEquals(5, vec.size());
      assertEquals(List.of(1L, 2L, 3L, 4L, 5L), vec);
    }

    @Test
    public void testCollectWithDistinct() {
      var vec = Stream.of(1L, 2L, 2L, 3L, 3L, 3L)
          .distinct()
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertEquals(3, vec.size());
      assertEquals(List.of(1L, 2L, 3L), vec);
    }

    @Test
    public void testCollectWithSorted() {
      var vec = Stream.of(5L, 2L, 8L, 1L, 9L)
          .sorted()
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertEquals(5, vec.size());
      assertEquals(List.of(1L, 2L, 5L, 8L, 9L), vec);
    }

    @Test
    public void testCollectWithLimit() {
      var vec = LongStream.range(0, 100)
          .boxed()
          .limit(10)
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertEquals(10, vec.size());
      assertEquals(List.of(0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L), vec);
    }

    @Test
    public void testCollectWithSkip() {
      var vec = LongStream.range(0, 10)
          .boxed()
          .skip(5)
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertEquals(5, vec.size());
      assertEquals(List.of(5L, 6L, 7L, 8L, 9L), vec);
    }

    @Test
    public void testCollectorReusability() {
      var collector = NumericVec.toNumericVec(NumericVec::longs);
      var vec1 = Stream.of(1L, 2L, 3L).collect(collector);
      var vec2 = Stream.of(4L, 5L, 6L).collect(collector);

      assertEquals(List.of(1L, 2L, 3L), vec1);
      assertEquals(List.of(4L, 5L, 6L), vec2);
    }

    @Test
    public void testCollectFromListStream() {
      var list = List.of(1L, 2L, 3L, 4L, 5L);
      var vec = list.stream()
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertEquals(list.size(), vec.size());
      assertEquals(list, vec);
    }

    @Test
    public void testCollectPreservesOrder() {
      var vec = Stream.of(5L, 4L, 3L, 2L, 1L)
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertEquals(List.of(5L, 4L, 3L, 2L, 1L), vec);
    }

    @Test
    public void testCollectWithConvertedAndBoxedIntStream() {
      var vec = IntStream.of(1, 2, 3, 4, 5)
          .boxed()
          .map(v -> (long) v)
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertEquals(5, vec.size());
      assertEquals(List.of(1L, 2L, 3L, 4L, 5L), vec);
    }

    @Test
    public void testCollectResultIsNumericVec() {
      var result = Stream.of(1L, 2L, 3L)
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertTrue(result instanceof NumericVec);
    }

    @Test
    public void testCollectFromInfiniteStream() {
      var vec = Stream.iterate(1L, x -> x + 1)
          .limit(100)
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertEquals(100, vec.size());
      assertEquals(1L, vec.get(0));
      assertEquals(100L, vec.get(99));
    }


    @Test
    public void testToNumericVecStreamParallel() {
      var vec = IntStream.range(0, 1_000_000)
          .boxed()
          .collect(NumericVec.toNumericVec(NumericVec::ints));

      var thread = Thread.currentThread();
      var otherThreadCount = vec.stream().parallel().mapToInt(__ -> thread != Thread.currentThread()? 1: 0).sum();
      assertNotEquals(0, otherThreadCount);
    }

    @Test
    public void testToNumericVecMutable() {
      var vec = Stream.of(12L, 45L)
          .collect(NumericVec.toNumericVec(NumericVec::longs));
      vec.add(99L);

      assertAll(
          () -> assertEquals(3, vec.size()),
          () -> assertEquals(12L, vec.get(0)),
          () -> assertEquals(45L, vec.get(1)),
          () -> assertEquals(99L, vec.get(2))
      );
    }

    @Test
    public void testToNumericVecParallelInts() {
      var vec = IntStream.range(0, 1_000_000).parallel()
          .boxed()
          .collect(NumericVec.toNumericVec(NumericVec::ints));

      assertEquals(1_000_000, vec.size());
      IntStream.range(0, 1_000_000)
          .forEach(i -> assertEquals(i, vec.get(i)));
    }

    @Test
    public void testToNumericVecParallelLongs() {
      var vec = LongStream.range(0, 1_000_000).parallel()
          .boxed()
          .collect(NumericVec.toNumericVec(NumericVec::longs));

      assertEquals(1_000_000, vec.size());
      IntStream.range(0, 1_000_000)
          .forEach(i -> assertEquals((long) i, vec.get(i)));
    }

    @Test
    public void testTNumericVecParallelDoubles() {
      var vec = IntStream.range(0, 1_000_000).parallel()
          .mapToObj(i -> (double) i)
          .collect(NumericVec.toNumericVec(NumericVec::doubles));

      assertEquals(1_000_000, vec.size());
      IntStream.range(0, 1_000_000)
          .forEach(i -> assertEquals((double) i, vec.get(i)));
    }

    @Test
    public void testToNumericLongsCanNotAddNull() {
      assertThrows(NullPointerException.class,
          () -> Stream.of(12L, null)
              .collect(NumericVec.toNumericVec(NumericVec::longs)));
    }

    @Test
    public void testToNumericIntsCanNotAddNull() {
      assertThrows(NullPointerException.class,
          () -> Stream.of(12, null)
              .collect(NumericVec.toNumericVec(NumericVec::ints)));
    }

    @Test
    public void testToNumericDoublesCanNotAddNull() {
      assertThrows(NullPointerException.class,
          () -> Stream.of(12., null)
              .collect(NumericVec.toNumericVec(NumericVec::doubles)));
    }
  }


  @Nested
  public class Q8 {

    @Test
    public void testIsFrozenInitiallyFalse() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      assertFalse(vec.isFrozen());
    }

    @Test
    public void testFreezeReturnsNumericVec() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var frozen = vec.freeze();

      assertEquals(NumericVec.class, frozen.getClass());
    }

    @Test
    public void testFrozenVecIsFrozen() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var frozen = vec.freeze();

      assertTrue(frozen.isFrozen());
    }

    @Test
    public void testOriginalVecNotFrozenAfterFreeze() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var frozen = vec.freeze();

      assertFalse(vec.isFrozen());
    }

    @Test
    public void testFrozenVecPreservesSize() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var frozen = vec.freeze();

      assertEquals(3, frozen.size());
    }

    @Test
    public void testFrozenVecPreservesElements() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var frozen = vec.freeze();

      assertEquals(1L, frozen.get(0));
      assertEquals(2L, frozen.get(1));
      assertEquals(3L, frozen.get(2));
    }

    @Test
    public void testFrozenVecPreservesAllElements() {
      var vec = NumericVec.longs(1L, 2L, 3L, 4L, 5L);
      var frozen = vec.freeze();

      assertEquals(List.of(1L, 2L, 3L, 4L, 5L), frozen);
    }

    @Test
    public void testFrozenVecCannotAdd() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var frozen = vec.freeze();

      assertThrows(UnsupportedOperationException.class, () -> frozen.add(4L));
    }

    @Test
    public void testFrozenVecCannotAddAll() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var frozen = vec.freeze();

      assertThrows(UnsupportedOperationException.class, () -> frozen.addAll(List.of(4L, 5L)));
    }

    @Test
    public void testFrozenVecCannotAddAllNumericVec() {
      var vec1 = NumericVec.longs(1L, 2L, 3L);
      var vec2 = NumericVec.longs(4L, 5L);
      var frozen = vec1.freeze();

      assertThrows(UnsupportedOperationException.class, () -> frozen.addAll(vec2));
    }

    @Test
    public void testOriginalVecCanAddAfterFreeze() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var frozen = vec.freeze();
      vec.add(4L);

      assertEquals(4, vec.size());
      assertEquals(3, frozen.size());
    }

    @Test
    public void testFreezeEmptyVec() {
      var vec = NumericVec.longs();
      var frozen = vec.freeze();
      assertTrue(frozen.isFrozen());
      assertTrue(frozen.isEmpty());
    }

    @Test
    public void testFrozenVecCanGet() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var frozen = vec.freeze();
      assertEquals(2L, frozen.get(1));
    }

    @Test
    public void testFrozenVecCanIterate() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var frozen = vec.freeze();

      var sum = 0L;
      for (var value : frozen) {
        sum += value;
      }
      assertEquals(6L, sum);
    }

    @Test
    public void testFrozenVecCanStream() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var frozen = vec.freeze();

      var sum = frozen.stream().mapToLong(x -> x).sum();
      assertEquals(6L, sum);
    }

    @Test
    public void testFrozenVecSharesUnderlyingArray() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var frozen = vec.freeze();
      vec.add(4L);

      assertEquals(3, frozen.size());
    }

    @Test
    public void testFreezeAlreadyFrozenVec() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var frozen = vec.freeze();
      var doubleFrozen = frozen.freeze();

      assertTrue(doubleFrozen.isFrozen());
      assertSame(frozen, doubleFrozen);
    }

    @Test
    public void testFrozenVecIntegers() {
      var vec = NumericVec.ints(1, 2, 3);
      var frozen = vec.freeze();
      assertTrue(frozen.isFrozen());
      assertEquals(List.of(1, 2, 3), frozen);

      assertThrows(UnsupportedOperationException.class, () -> frozen.add(4));
    }

    @Test
    public void testFrozenVecDoubles() {
      var vec = NumericVec.doubles(1.0, 2.0, 3.0);
      var frozen = vec.freeze();
      assertTrue(frozen.isFrozen());
      assertEquals(List.of(1.0, 2.0, 3.0), frozen);

      assertThrows(UnsupportedOperationException.class, () -> frozen.add(4.0));
    }

    @Test
    public void testFrozenVecSize() {
      var vec = NumericVec.longs(1L, 2L, 3L, 4L, 5L);
      var frozen = vec.freeze();
      assertEquals(vec.size(), frozen.size());
    }

    @Test
    public void testFrozenVecContains() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var frozen = vec.freeze();
      assertTrue(frozen.contains(2L));
      assertFalse(frozen.contains(5L));
    }

    @Test
    public void testFrozenVecIndexOf() {
      var vec = NumericVec.longs(1L, 2L, 3L, 2L);
      var frozen = vec.freeze();
      assertEquals(1, frozen.indexOf(2L));
    }

    @Test
    public void testFrozenVecToArray() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var frozen = vec.freeze();
      var array = frozen.toArray();
      assertArrayEquals(new Object[]{1L, 2L, 3L}, array);
    }

    @Test
    public void testFrozenVecEquals() {
      var vec1 = NumericVec.longs(1L, 2L, 3L);
      var vec2 = NumericVec.longs(1L, 2L, 3L);
      var frozen1 = vec1.freeze();
      var frozen2 = vec2.freeze();
      assertEquals(frozen1, frozen2);
    }

    @Test
    public void testFrozenVecHashCode() {
      var vec1 = NumericVec.longs(1L, 2L, 3L);
      var vec2 = NumericVec.longs(1L, 2L, 3L);
      var frozen1 = vec1.freeze();
      var frozen2 = vec2.freeze();
      assertEquals(frozen1.hashCode(), frozen2.hashCode());
    }

    @Test
    public void testFrozenVecSubList() {
      var vec = NumericVec.longs(1L, 2L, 3L, 4L, 5L);
      var frozen = vec.freeze();
      var subList = frozen.subList(1, 4);
      assertEquals(List.of(2L, 3L, 4L), subList);
    }

    @Test
    public void testFrozenVecIterator() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var frozen = vec.freeze();
      var iterator = frozen.iterator();
      assertTrue(iterator.hasNext());
      assertEquals(1L, iterator.next());
      assertEquals(2L, iterator.next());
      assertEquals(3L, iterator.next());
      assertFalse(iterator.hasNext());
    }

    @Test
    public void testFrozenVecSpliterator() {
      var vec = NumericVec.longs(1L, 2L, 3L);
      var frozen = vec.freeze();
      var spliterator = frozen.spliterator();
      assertEquals(3, spliterator.estimateSize());
    }

    @Test
    public void testCannotAddToFrozenVecFromCollector() {
      var vec = List.of(1L, 2L, 3L).stream()
          .collect(NumericVec.toNumericVec(NumericVec::longs));
      var frozen = vec.freeze();
      assertThrows(UnsupportedOperationException.class, () -> frozen.add(4L));
    }

    @Test
    public void testFrozenVecLargeSize() {
      var longs = LongStream.range(0, 1_000_000).toArray();
      var vec = NumericVec.longs(longs);
      var frozen = vec.freeze();

      assertTrue(frozen.isFrozen());
      assertEquals(1_000_000, frozen.size());
      assertEquals(0L, frozen.get(0));
      assertEquals(999_999L, frozen.get(999_999));
    }

    @Test
    public void testAddAllFrozenLongs() {
      var vec = NumericVec.longs(17, 21, 42);
      var frozen = NumericVec.longs(1, 2).freeze();
      vec.addAll(frozen);

      assertEquals(NumericVec.longs(17, 21, 42, 1, 2), vec);
    }

    @Test
    public void testAddAllFrozenInts() {
      var vec = NumericVec.ints(17, 21, 42);
      var frozen = NumericVec.ints(1, 2).freeze();

      vec.addAll(frozen);
      assertEquals(NumericVec.ints(17, 21, 42, 1, 2), vec);
    }

    @Test
    public void testAddAllFrozenDoubles() {
      var vec = NumericVec.doubles(17, 21, 42);
      var frozen = NumericVec.doubles(1, 2).freeze();
      vec.addAll(frozen);

      assertEquals(NumericVec.doubles(17, 21, 42, 1, 2), vec);
    }

    @Test
    public void testImplementationNoSupplementaryFields() {
      var fieldTypes = Arrays.stream(NumericVec.class.getDeclaredFields())
          .map(Field::getType)
          .filter(type -> !type.getPackageName().equals("java.util.function"))
          .collect(toSet());

      assertEquals(Set.of(long[].class, int.class), fieldTypes);
    }

    @Test
    public void testImplementationNoArrayDuplication() throws IllegalAccessException {
      var arrayField = Arrays.stream(NumericVec.class.getDeclaredFields())
          .filter(f -> f.getType().isArray())
          .findFirst()
          .orElseThrow();
      arrayField.setAccessible(true);

      var vec = NumericVec.ints(1, 2, 3);
      var frozen = vec.freeze();
      assertSame(arrayField.get(vec), arrayField.get(frozen));
    }
  }


  @Nested
  public class Q9 {

    @Test
    public void testCollectToUnmodifiableIsFrozen() {
      var vec = Stream.of(1L, 2L, 3L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertTrue(vec.isFrozen());
    }

    @Test
    public void testCollectToUnmodifiablePreservesElements() {
      var vec = Stream.of(1L, 2L, 3L, 4L, 5L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertEquals(5, vec.size());
      assertEquals(List.of(1L, 2L, 3L, 4L, 5L), vec);
    }

    @Test
    public void testCollectToUnmodifiableCannotAdd() {
      var vec = Stream.of(1L, 2L, 3L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertThrows(UnsupportedOperationException.class, () -> vec.add(4L));
    }

    @Test
    public void testCollectToUnmodifiableCannotAddAll() {
      var vec = Stream.of(1L, 2L, 3L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertThrows(UnsupportedOperationException.class, () -> vec.addAll(List.of(4L, 5L)));
    }

    @Test
    public void testCollectToUnmodifiableEmptyStream() {
      var vec = Stream.<Long>empty()
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertTrue(vec.isFrozen());
      assertTrue(vec.isEmpty());
    }

    @Test
    public void testCollectToUnmodifiableSingleElement() {
      var vec = Stream.of(42L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertTrue(vec.isFrozen());
      assertEquals(1, vec.size());
      assertEquals(42L, vec.get(0));
    }

    @Test
    public void testCollectToUnmodifiableWithIntegers() {
      var vec = Stream.of(1, 2, 3, 4, 5)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::ints));

      assertTrue(vec.isFrozen());
      assertEquals(5, vec.size());
      assertEquals(List.of(1, 2, 3, 4, 5), vec);
      assertThrows(UnsupportedOperationException.class, () -> vec.add(6));
    }

    @Test
    public void testCollectToUnmodifiableWithDoubles() {
      var vec = Stream.of(1.0, 2.0, 3.0, 4.0, 5.0)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::doubles));

      assertTrue(vec.isFrozen());
      assertEquals(5, vec.size());
      assertEquals(List.of(1.0, 2.0, 3.0, 4.0, 5.0), vec);
      assertThrows(UnsupportedOperationException.class, () -> vec.add(6.0));
    }

    @Test
    public void testCollectToUnmodifiableWithFilter() {
      var vec = Stream.of(1L, 2L, 3L, 4L, 5L, 6L)
          .filter(x -> x % 2 == 0)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertTrue(vec.isFrozen());
      assertEquals(3, vec.size());
      assertEquals(List.of(2L, 4L, 6L), vec);
    }

    @Test
    public void testCollectToUnmodifiableWithMap() {
      var vec = Stream.of(1L, 2L, 3L)
          .map(x -> x * 2)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertTrue(vec.isFrozen());
      assertEquals(List.of(2L, 4L, 6L), vec);
    }

    @Test
    public void testCollectToUnmodifiableLargeStream() {
      var vec = LongStream.range(0, 10000)
          .boxed()
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertTrue(vec.isFrozen());
      assertEquals(10000, vec.size());
      assertEquals(0L, vec.get(0));
      assertEquals(9999L, vec.get(9999));
    }

    @Test
    public void testCollectToUnmodifiableParallelStream() {
      var vec = LongStream.range(0, 1_000_000)
          .parallel()
          .boxed()
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertTrue(vec.isFrozen());
      assertEquals(1_000_000, vec.size());
      var list = vec.stream().toList();
      assertEquals(LongStream.range(0, 1_000_000).boxed().toList(), list);
    }

    @Test
    public void testCollectToUnmodifiableParallelStreamIntegers() {
      var vec = IntStream.range(0, 1_000_000)
          .parallel()
          .boxed()
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::ints));

      assertTrue(vec.isFrozen());
      assertEquals(1_000_000, vec.size());
      var list = vec.stream().toList();
      assertEquals(IntStream.range(0, 1_000_000).boxed().toList(), list);
    }

    @Test
    public void testCollectToUnmodifiableParallelStreamDoubles() {
      var vec = IntStream.range(0, 1_000_000)
          .parallel()
          .mapToDouble(x -> x)
          .boxed()
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::doubles));

      assertTrue(vec.isFrozen());
      assertEquals(1_000_000, vec.size());
      var list = vec.stream().toList();
      assertEquals(IntStream.range(0, 1_000_000).mapToDouble(x -> x).boxed().toList(), list);
    }

    @Test
    public void testCollectorToUnmodifiableNullSupplier() {
      assertThrows(NullPointerException.class, () -> NumericVec.toUnmodifiableNumericVec(null));
    }

    @Test
    public void testCollectToUnmodifiableCanRead() {
      var vec = Stream.of(1L, 2L, 3L, 4L, 5L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertTrue(vec.isFrozen());
      assertEquals(3L, vec.get(2));
      assertTrue(vec.contains(4L));
      assertEquals(1, vec.indexOf(2L));
    }

    @Test
    public void testCollectToUnmodifiableCanIterate() {
      var vec = Stream.of(1L, 2L, 3L, 4L, 5L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertTrue(vec.isFrozen());
      var sum = 0L;
      for (var value : vec) {
        sum += value;
      }
      assertEquals(15L, sum);
    }

    @Test
    public void testCollectToUnmodifiableCanStream() {
      var vec = Stream.of(1L, 2L, 3L, 4L, 5L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertTrue(vec.isFrozen());
      var filtered = vec.stream()
          .filter(x -> x > 2)
          .toList();
      assertEquals(List.of(3L, 4L, 5L), filtered);
    }

    @Test
    public void testCollectToUnmodifiableWithFlatMap() {
      var vec = Stream.of(List.of(1L, 2L), List.of(3L, 4L), List.of(5L))
          .flatMap(List::stream)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertTrue(vec.isFrozen());
      assertEquals(5, vec.size());
      assertEquals(List.of(1L, 2L, 3L, 4L, 5L), vec);
    }

    @Test
    public void testCollectToUnmodifiableWithDistinct() {
      var vec = Stream.of(1L, 2L, 2L, 3L, 3L, 3L)
          .distinct()
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertTrue(vec.isFrozen());
      assertEquals(3, vec.size());
      assertEquals(List.of(1L, 2L, 3L), vec);
    }

    @Test
    public void testCollectToUnmodifiableWithSorted() {
      var vec = Stream.of(5L, 2L, 8L, 1L, 9L)
          .sorted()
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertTrue(vec.isFrozen());
      assertEquals(List.of(1L, 2L, 5L, 8L, 9L), vec);
    }

    @Test
    public void testCollectToUnmodifiableReusability() {
      var collector = NumericVec.toUnmodifiableNumericVec(NumericVec::longs);
      var vec1 = Stream.of(1L, 2L, 3L).collect(collector);
      var vec2 = Stream.of(4L, 5L, 6L).collect(collector);

      assertTrue(vec1.isFrozen());
      assertTrue(vec2.isFrozen());
      assertEquals(List.of(1L, 2L, 3L), vec1);
      assertEquals(List.of(4L, 5L, 6L), vec2);
    }

    @Test
    public void testCollectToUnmodifiableEquals() {
      var vec1 = Stream.of(1L, 2L, 3L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));
      var vec2 = Stream.of(1L, 2L, 3L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertEquals(vec1, vec2);
    }

    @Test
    public void testCollectToUnmodifiableHashCode() {
      var vec1 = Stream.of(1L, 2L, 3L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));
      var vec2 = Stream.of(1L, 2L, 3L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertEquals(vec1.hashCode(), vec2.hashCode());
    }

    @Test
    public void testCollectToUnmodifiableToArray() {
      var vec = Stream.of(1L, 2L, 3L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      var array = vec.toArray();
      assertArrayEquals(new Object[]{1L, 2L, 3L}, array);
    }

    @Test
    public void testCollectToUnmodifiableSubList() {
      var vec = Stream.of(1L, 2L, 3L, 4L, 5L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      var subList = vec.subList(1, 4);
      assertEquals(List.of(2L, 3L, 4L), subList);
    }

    @Test
    public void testCollectToUnmodifiableIsNumericVec() {
      var result = Stream.of(1L, 2L, 3L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));
      assertEquals(NumericVec.class, result.getClass());
    }

    @Test
    public void testCollectToUnmodifiableFromList() {
      var list = List.of(1L, 2L, 3L, 4L, 5L);
      var vec = list.stream()
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertTrue(vec.isFrozen());
      assertEquals(list, vec);
    }

    @Test
    public void testCollectToUnmodifiablePreservesOrder() {
      var vec = Stream.of(5L, 4L, 3L, 2L, 1L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertEquals(List.of(5L, 4L, 3L, 2L, 1L), vec);
    }

    @Test
    public void testCollectToUnmodifiableWithLimit() {
      var vec = LongStream.range(0, 100)
          .boxed()
          .limit(10)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertTrue(vec.isFrozen());
      assertEquals(10, vec.size());
    }

    @Test
    public void testCollectToUnmodifiableFromInfiniteStream() {
      var vec = Stream.iterate(1L, x -> x + 1)
          .limit(100)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertTrue(vec.isFrozen());
      assertEquals(100, vec.size());
      assertEquals(1L, vec.get(0));
      assertEquals(100L, vec.get(99));
    }

    @Test
    public void testCollectToUnmodifiableDifferentFromModifiable() {
      var modifiable = Stream.of(1L, 2L, 3L)
          .collect(NumericVec.toNumericVec(NumericVec::longs));
      var unmodifiable = Stream.of(1L, 2L, 3L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      assertFalse(modifiable.isFrozen());
      assertTrue(unmodifiable.isFrozen());

      modifiable.add(4L);  // Ok
      assertThrows(UnsupportedOperationException.class, () -> unmodifiable.add(4L));
    }

    @Test
    public void testCollectToUnmodifiableCannotAddAllNumericVec() {
      var vec1 = Stream.of(1L, 2L, 3L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));
      var vec2 = NumericVec.longs(4L, 5L);

      assertThrows(UnsupportedOperationException.class, () -> vec1.addAll(vec2));
    }

    @Test
    public void testCollectToUnmodifiableSpliterator() {
      var vec = Stream.of(1L, 2L, 3L)
          .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs));

      var spliterator = vec.spliterator();
      assertEquals(3, spliterator.estimateSize());
      assertTrue(spliterator.hasCharacteristics(Spliterator.NONNULL));
    }


    @Test
    public void testToUnmodifiableNumericVecNotModifiableNoSideEffect() {
      var vec = NumericVec.longs();
      var frozen = Stream.of(1L, 2L, 3L)
          .collect(NumericVec.toUnmodifiableNumericVec(() -> vec));
      vec.add(4L);

      assertEquals(NumericVec.longs(1L, 2L, 3L), frozen);
    }

    @Test
    public void testToUnmodifiableNumericLongsCanNotAddNull() {
      assertThrows(NullPointerException.class,
          () -> Stream.of(12L, null)
              .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::longs)));
    }

    @Test
    public void testToUnmodifiableNumericIntsCanNotAddNull() {
      assertThrows(NullPointerException.class,
          () -> Stream.of(12, null)
              .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::ints)));
    }

    @Test
    public void testToUnmodifiableNumericDoublesCanNotAddNull() {
      assertThrows(NullPointerException.class,
          () -> Stream.of(12., null)
              .collect(NumericVec.toUnmodifiableNumericVec(NumericVec::doubles)));
    }
  }
}