package word;

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

import static java.util.stream.IntStream.range;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class ByteCounterTest {
  @Nested
  public class Q1 {
    @Test
    public void testEmptyArray() {
      var data = new byte[0];
      var counts = ByteCounter.countSpacesAndNewlines(data);
      assertEquals(0, counts.spaces());
      assertEquals(0, counts.newlines());
    }

    @Test
    public void testNoSpacesOrNewlines() {
      var data = "HelloWorld".getBytes();
      var counts = ByteCounter.countSpacesAndNewlines(data);
      assertEquals(0, counts.spaces());
      assertEquals(0, counts.newlines());
    }

    @Test
    public void testOnlySpaces() {
      var data = "     ".getBytes();
      var counts = ByteCounter.countSpacesAndNewlines(data);
      assertEquals(5, counts.spaces());
      assertEquals(0, counts.newlines());
    }

    @Test
    public void testOnlyNewlines() {
      var data = "\n\n\n".getBytes();
      var counts = ByteCounter.countSpacesAndNewlines(data);
      assertEquals(0, counts.spaces());
      assertEquals(3, counts.newlines());
    }

    @Test
    public void testSpacesAndNewlines() {
      var data = "Hello world\nThis is a test\n".getBytes();
      var counts = ByteCounter.countSpacesAndNewlines(data);
      assertEquals(4, counts.spaces());
      assertEquals(2, counts.newlines());
    }


    @Test
    public void testMixedContent() {
      var data = " \n \n  \n".getBytes();  // 4 spaces, 3 newlines
      var counts = ByteCounter.countSpacesAndNewlines(data);
      assertEquals(4, counts.spaces());
      assertEquals(3, counts.newlines());
    }

    @Test
    public void testLargeInput() {
      var data = new byte[1_000_000];
      for (var i = 0; i < data.length; i++) {
        data[i] = (i % 10 == 0) ? (byte) ' ' : (i % 15 == 0) ? (byte) '\n' : (byte) 'a';
      }
      var expectedSpaces = range(0, data.length)
          .filter(i -> i % 10 == 0)
          .count();
      var expectedNewlines = range(0, data.length)
          .filter(i -> i % 10 != 0 && i % 15 == 0)
          .count();

      var counts = ByteCounter.countSpacesAndNewlines(data);
      assertEquals(expectedSpaces, counts.spaces());
      assertEquals(expectedNewlines, counts.newlines());
    }

    @Test
    public void testLargeInput2() {
      var data = new byte[1_000_000];
      for (var i = 0; i < data.length; i++) {
        data[i] = (i % 5 == 0) ? (byte) ' ' : (i % 17 == 0) ? (byte) '\n' : (byte) 'a';
      }
      var expectedSpaces = range(0, data.length)
          .filter(i -> i % 5 == 0)
          .count();
      var expectedNewlines = range(0, data.length)
          .filter(i -> i % 5 != 0 && i % 17 == 0)
          .count();

      var counts = ByteCounter.countSpacesAndNewlines(data);
      assertEquals(expectedSpaces, counts.spaces());
      assertEquals(expectedNewlines, counts.newlines());
    }

    @Test
    public void testTextBlockSimple() {
      var data = """
          Hello world
          This is a test
          """.getBytes();

      var counts = ByteCounter.countSpacesAndNewlines(data);
      assertEquals(4, counts.spaces());
      assertEquals(2, counts.newlines());
    }


    @Test
    public void testTextBlockWithLeadingAndTrailingSpaces() {
      var data = """
          Hello   world   \
          This   is   a   test\
          """.getBytes();
      var counts = ByteCounter.countSpacesAndNewlines(data);
      assertEquals(15, counts.spaces());
      assertEquals(0, counts.newlines());
    }
  }


  @Nested
  public class Q2 {
    @Test
    public void testEmptyArray() {
      var emptyArray = new byte[0];
      var result = ByteCounter.countSpacesAndNewlines(emptyArray, 0, 0);

      assertEquals(0, result.spaces());
      assertEquals(0, result.newlines());
    }

    @Test
    public void testNoSpacesOrNewlines() {
      var array = "abc123".getBytes();
      var result = ByteCounter.countSpacesAndNewlines(array, 0, array.length);

      assertEquals(0, result.spaces());
      assertEquals(0, result.newlines());
    }

    @Test
    public void testOnlySpaces() {
      var array = "     ".getBytes();
      var result = ByteCounter.countSpacesAndNewlines(array, 0, array.length);

      assertEquals(5, result.spaces());
      assertEquals(0, result.newlines());
    }

    @Test
    public void testOnlyNewlines() {
      var array = "\n\n\n".getBytes();
      var result = ByteCounter.countSpacesAndNewlines(array, 0, array.length);

      assertEquals(0, result.spaces());
      assertEquals(3, result.newlines());
    }

    @Test
    public void testMixedContent() {
      var array = "Hello World\nThis is a test\nWith spaces and newlines".getBytes();
      var result = ByteCounter.countSpacesAndNewlines(array, 0, array.length);

      assertEquals(7, result.spaces());
      assertEquals(2, result.newlines());
    }

    @Test
    public void testPartialRange() {
      var array = "Hello World\nThis is a test\nWith spaces and newlines".getBytes();
      // Count only in "World\nThis"
      var start = 6;
      var end = 17;
      var result = ByteCounter.countSpacesAndNewlines(array, start, end);

      assertEquals(1, result.spaces());
      assertEquals(1, result.newlines());
    }

    @Test
    public void testZeroLengthRange() {
      var array = "Hello World".getBytes();
      var index = 5;
      var result = ByteCounter.countSpacesAndNewlines(array, index, index);

      assertEquals(0, result.spaces());
      assertEquals(0, result.newlines());
    }

    @Test
    public void testStartAtBeginningEndBeforeLength() {
      var array = "Hello World\nTest".getBytes();
      var result = ByteCounter.countSpacesAndNewlines(array, 0, 5); // Only "Hello"

      assertEquals(0, result.spaces());
      assertEquals(0, result.newlines());
    }

    @Test
    public void testStartAfterBeginningEndAtLength() {
      var array = "Hello World\nTest".getBytes();
      var result = ByteCounter.countSpacesAndNewlines(array, 6, array.length); // "World\nTest"

      assertEquals(0, result.spaces());
      assertEquals(1, result.newlines());
    }

    @Test
    public void testStartAfterBeginningEndAtLength2() {
      var array = "Hello World\nTest".getBytes();
      var result = ByteCounter.countSpacesAndNewlines(array, 12, array.length); // "Test"

      assertEquals(0, result.spaces());
      assertEquals(0, result.newlines());
    }

    @Test
    public void testLargeArray() {
      // Create an array larger than the vector size to ensure vector operations are used
      var sb = new StringBuilder();
      for (var i = 0; i < 100_000; i++) {
        sb.append(i % 10 == 0 ? " " : "a");
        if (i % 50 == 0) sb.append("\n");
      }
      var text = sb.toString();

      var array = text.getBytes();
      var result = ByteCounter.countSpacesAndNewlines(array, 0, array.length);

      assertEquals(text.chars().filter(c -> c == ' ').count(), result.spaces());
      assertEquals(text.chars().filter(c -> c == '\n').count(), result.newlines());
    }

    @Test
    public void testInvalidRange() {
      var array = "Hello".getBytes();

      assertThrows(IndexOutOfBoundsException.class, () -> ByteCounter.countSpacesAndNewlines(array, 2, 10));
      assertThrows(IndexOutOfBoundsException.class, () -> ByteCounter.countSpacesAndNewlines(array, -1, 3));
      assertThrows(IndexOutOfBoundsException.class, () -> ByteCounter.countSpacesAndNewlines(array, 3, 2));
    }


    @Test
    public void testNoSpacesOrNewlinesPartial() {
      var array = "No spaces or newlines".getBytes();
      var result = ByteCounter.countSpacesAndNewlines(array, 0, 3);

      assertEquals(1, result.spaces());
      assertEquals(0, result.newlines());
    }

    @Test
    public void testTwoSpaces() {
      var array = "Two spaces here".getBytes();
      var result = ByteCounter.countSpacesAndNewlines(array, 0, 15);

      assertEquals(2, result.spaces());
      assertEquals(0, result.newlines());
    }

    @Test
    public void testTwoNewlines() {
      var array = "Two\nnewlines\nhere".getBytes();
      var result = ByteCounter.countSpacesAndNewlines(array, 0, 13);

      assertEquals(0, result.spaces());
      assertEquals(2, result.newlines());
    }

    @Test
    public void testMixedPartialRange() {
      var array = "Mixed content\nwith spaces".getBytes();
      var result = ByteCounter.countSpacesAndNewlines(array, 5, 22);

      assertEquals(2, result.spaces());
      assertEquals(1, result.newlines());
    }

    @Test
    public void testMultipleSpacesAndNewlines() {
      var array = "  \n\n  ".getBytes();
      var result = ByteCounter.countSpacesAndNewlines(array, 0, 6);

      assertEquals(4, result.spaces());
      assertEquals(2, result.newlines());
    }

    @Test
    public void testEmptyString() {
      var array = "".getBytes();
      var result = ByteCounter.countSpacesAndNewlines(array, 0, 0);

      assertEquals(0, result.spaces());
      assertEquals(0, result.newlines());
    }

    @Test
    public void testSpacesAndNewlines() {
      var data = "Hello world\nThis is a test\n".getBytes();
      var counts = ByteCounter.countSpacesAndNewlines(data, 0, data.length);
      assertEquals(4, counts.spaces());
      assertEquals(2, counts.newlines());
    }

    @Test
    public void testLargeInput() {
      var data = new byte[1_000_000];
      for (var i = 0; i < data.length; i++) {
        data[i] = (i % 10 == 0) ? (byte) ' ' : (i % 15 == 0) ? (byte) '\n' : (byte) 'a';
      }
      var expectedSpaces = range(0, data.length)
          .filter(i -> i % 10 == 0)
          .count();
      var expectedNewlines = range(0, data.length)
          .filter(i -> i % 10 != 0 && i % 15 == 0)
          .count();

      var counts = ByteCounter.countSpacesAndNewlines(data, 0, data.length);
      assertEquals(expectedSpaces, counts.spaces());
      assertEquals(expectedNewlines, counts.newlines());
    }

    @Test
    public void testLargeInput2() {
      var data = new byte[1_000_000];
      for (var i = 0; i < data.length; i++) {
        data[i] = (i % 5 == 0) ? (byte) ' ' : (i % 17 == 0) ? (byte) '\n' : (byte) 'a';
      }
      var expectedSpaces = range(0, data.length)
          .filter(i -> i % 5 == 0)
          .count();
      var expectedNewlines = range(0, data.length)
          .filter(i -> i % 5 != 0 && i % 17 == 0)
          .count();

      var counts = ByteCounter.countSpacesAndNewlines(data, 0, data.length);
      assertEquals(expectedSpaces, counts.spaces());
      assertEquals(expectedNewlines, counts.newlines());
    }

    @Test
    public void testTextBlockSimple() {
      var data = """
          Hello world
          This is a test
          """.getBytes();

      var counts = ByteCounter.countSpacesAndNewlines(data, 0, data.length);
      assertEquals(4, counts.spaces());
      assertEquals(2, counts.newlines());
    }


    @Test
    public void testTextBlockWithLeadingAndTrailingSpaces() {
      var data = """
          Hello   world   \
          This   is   a   test\
          """.getBytes();
      var counts = ByteCounter.countSpacesAndNewlines(data, 0, data.length);
      assertEquals(15, counts.spaces());
      assertEquals(0, counts.newlines());
    }

    @Test
    public void testTextBlockWithLeadingAndTrailingSpaces2() {
      var data = """
          Hello   world   \
          This   is   a   test\
          Hello   world   \
          This   is   a   test\
          """.getBytes();
      var counts = ByteCounter.countSpacesAndNewlines(data, 1, data.length);
      assertEquals(30, counts.spaces());
      assertEquals(0, counts.newlines());
    }
  }

  @Nested
  public class Q3 {
    @Test
    public void testEmptyArray() {
      var data = new byte[0];
      var counts = ByteCounter.parallelCountSpacesAndNewlines(data);
      assertEquals(0, counts.spaces());
      assertEquals(0, counts.newlines());
    }

    @Test
    public void testNoSpacesOrNewlines() {
      var data = "HelloWorld".getBytes();
      var counts = ByteCounter.parallelCountSpacesAndNewlines(data);
      assertEquals(0, counts.spaces());
      assertEquals(0, counts.newlines());
    }

    @Test
    public void testOnlySpaces() {
      var data = "     ".getBytes();
      var counts = ByteCounter.parallelCountSpacesAndNewlines(data);
      assertEquals(5, counts.spaces());
      assertEquals(0, counts.newlines());
    }

    @Test
    public void testOnlyNewlines() {
      var data = "\n\n\n".getBytes();
      var counts = ByteCounter.parallelCountSpacesAndNewlines(data);
      assertEquals(0, counts.spaces());
      assertEquals(3, counts.newlines());
    }

    @Test
    public void testSpacesAndNewlines() {
      var data = "Hello world\nThis is a test\n".getBytes();
      var counts = ByteCounter.parallelCountSpacesAndNewlines(data);
      assertEquals(4, counts.spaces());
      assertEquals(2, counts.newlines());
    }


    @Test
    public void testMixedContent() {
      var data = " \n \n  \n".getBytes();  // 4 spaces, 3 newlines
      var counts = ByteCounter.parallelCountSpacesAndNewlines(data);
      assertEquals(4, counts.spaces());
      assertEquals(3, counts.newlines());
    }

    @Test
    public void testLargeInput() {
      var data = new byte[1_000_000];
      for (var i = 0; i < data.length; i++) {
        data[i] = (i % 10 == 0) ? (byte) ' ' : (i % 15 == 0) ? (byte) '\n' : (byte) 'a';
      }
      var expectedSpaces = range(0, data.length)
          .filter(i -> i % 10 == 0)
          .count();
      var expectedNewlines = range(0, data.length)
          .filter(i -> i % 10 != 0 && i % 15 == 0)
          .count();

      var counts = ByteCounter.parallelCountSpacesAndNewlines(data);
      assertEquals(expectedSpaces, counts.spaces());
      assertEquals(expectedNewlines, counts.newlines());
    }

    @Test
    public void testLargeInput2() {
      var data = new byte[1_000_000];
      for (var i = 0; i < data.length; i++) {
        data[i] = (i % 5 == 0) ? (byte) ' ' : (i % 17 == 0) ? (byte) '\n' : (byte) 'a';
      }
      var expectedSpaces = range(0, data.length)
          .filter(i -> i % 5 == 0)
          .count();
      var expectedNewlines = range(0, data.length)
          .filter(i -> i % 5 != 0 && i % 17 == 0)
          .count();

      var counts = ByteCounter.parallelCountSpacesAndNewlines(data);
      assertEquals(expectedSpaces, counts.spaces());
      assertEquals(expectedNewlines, counts.newlines());
    }

    @Test
    public void testTextBlockSimple() {
      var data = """
          Hello world
          This is a test
          """.getBytes();

      var counts = ByteCounter.parallelCountSpacesAndNewlines(data);
      assertEquals(4, counts.spaces());
      assertEquals(2, counts.newlines());
    }


    @Test
    public void testTextBlockWithLeadingAndTrailingSpaces() {
      var data = """
          Hello   world   \
          This   is   a   test\
          """.getBytes();
      var counts = ByteCounter.parallelCountSpacesAndNewlines(data);
      assertEquals(15, counts.spaces());
      assertEquals(0, counts.newlines());
    }
  }
}