package forkjoin;

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

import java.util.Optional;
import java.util.OptionalInt;
import java.util.Random;
import java.util.stream.IntStream;
import java.util.stream.Stream;

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

public class AnyOpForkJoinTest {
  @Test @Tag("Q1")
  public void testAnyOpForkJoinIntSimple() {
    assertAll(
        () -> assertTrue(anyOpForkJoinInt(IntStream.empty(), (a, b) -> fail()).isEmpty()),
        () -> assertEquals(OptionalInt.of(42), anyOpForkJoinInt(IntStream.of(42), Integer::sum)),
        () -> assertEquals(OptionalInt.of(64), anyOpForkJoinInt(IntStream.of(56, 8), Integer::sum))
    );
  }

  @Test @Tag("Q1")
  public void testAnyOpForkJoinInt() {
    var stream = new Random(0).ints(1_000_000, 0, Integer.MAX_VALUE);
    assertEquals(665, anyOpForkJoinInt(stream, Math::min).orElseThrow());
  }

  @Test @Tag("Q1")
  public void testAnyOpForkJoinMerge() {
    var stream = IntStream.range(0, 1_000__000).filter(_ -> false);
    assertTrue(anyOpForkJoinInt(stream, Math::min).isEmpty());
  }

  @Test @Tag("Q1")
  public void testAnyOpForkJoinMerge2() {
    var stream = IntStream.range(0, 1_000_000).filter(i -> i >= 500_000);
    assertEquals(500_000, anyOpForkJoinInt(stream, Math::min).orElseThrow());
  }

  @Test @Tag("Q1")
  public void testAnyOpForkJoinMerge3() {
    var stream = IntStream.range(0, 1_000_000).filter(i -> i < 500_000);
    assertEquals(0, anyOpForkJoinInt(stream, Math::min).orElseThrow());
  }

  @Test @Tag("Q1")
  public void testAnyOpForkJoinIntPrecondition() {
    assertAll(
        () -> assertThrows(NullPointerException.class, () -> anyOpForkJoinInt(null, Integer::sum)),
        () -> assertThrows(NullPointerException.class, () -> anyOpForkJoinInt(IntStream.empty(), null))
    );
  }


  @Test @Tag("Q2")
  public void testAnyOpForkJoinObjSimple() {
    assertAll(
        () -> assertTrue(anyOpForkJoinObj(Stream.empty(), (a, b) -> fail()).isEmpty()),
        () -> assertEquals(Optional.of(42), anyOpForkJoinObj(Stream.of(42), Integer::sum)),
        () -> assertEquals(Optional.of(64), anyOpForkJoinObj(Stream.of(56, 8), Integer::sum)),
        () -> assertEquals(Optional.of("foo"), anyOpForkJoinObj(Stream.of("foo"), String::concat)),
        () -> assertEquals(Optional.of("foobar"), anyOpForkJoinObj(Stream.of("foo", "bar"), String::concat))
    );
  }

  @Test @Tag("Q2")
  public void testAnyOpForkJoinObj() {
    var stream = new Random(0).ints(1_000_000, 0, Integer.MAX_VALUE).boxed();
    assertEquals(665, anyOpForkJoinObj(stream, Math::min).orElseThrow());
  }

  @Test @Tag("Q1")
  public void testAnyOpForkJoinMerge() {
    var stream = IntStream.range(0, 1_000__000).boxed().filter(_ -> false);
    assertTrue(anyOpForkJoinObj(stream, Math::min).isEmpty());
  }

  @Test @Tag("Q1")
  public void testAnyOpForkJoinMerge2() {
    var stream = IntStream.range(0, 1_000_000).boxed().filter(i -> i >= 500_000);
    assertEquals(500_000, anyOpForkJoinObj(stream, Math::min).orElseThrow());
  }

  @Test @Tag("Q1")
  public void testAnyOpForkJoinMerge3() {
    var stream = IntStream.range(0, 1_000_000).boxed().filter(i -> i < 500_000);
    assertEquals(0, anyOpForkJoinObj(stream, Math::min).orElseThrow());
  }

  @Test @Tag("Q2")
  public void testAnyOpForkJoinObjPrecondition() {
    assertAll(
        () -> assertThrows(NullPointerException.class, () -> anyOpForkJoinObj(null, Integer::sum)),
        () -> assertThrows(NullPointerException.class, () -> anyOpForkJoinObj(Stream.empty(), null))
    );
  }
}