content: /*
   * Copyright 2015, 2024 StreamEx contributors
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package one.util.streamex.api;
  
  import one.util.streamex.DoubleStreamEx.DoubleEmitter;
  import one.util.streamex.IntStreamEx.IntEmitter;
  import one.util.streamex.LongStreamEx;
  import one.util.streamex.LongStreamEx.LongEmitter;
  import one.util.streamex.StreamEx;
  import one.util.streamex.StreamEx.Emitter;
  import one.util.streamex.TestHelpers;
  import org.junit.FixMethodOrder;
  import org.junit.Test;
  import org.junit.runners.MethodSorters;
  
  import java.math.BigInteger;
  import java.util.Iterator;
  import java.util.function.BinaryOperator;
  import java.util.function.LongPredicate;
  import java.util.function.Predicate;
  import java.util.function.UnaryOperator;
  
  import static java.util.Arrays.asList;
  import static one.util.streamex.TestHelpers.checkSpliterator;
  import static org.junit.Assert.*;
  
  /**
   * @author Tagir Valeev
   */
  @FixMethodOrder(MethodSorters.NAME_ASCENDING)
  public class EmitterTest {
      // Like Java-9 Stream.iterate(seed, test, op)
      public static <T> Emitter<T> iterate(T seed, Predicate<? super T> test, UnaryOperator<T> op) {
          return test.test(seed) ? action -> {
              action.accept(seed);
              return iterate(op.apply(seed), test, op);
          } : null;
      }
  
      // Collatz sequence starting from given number
      public static Emitter<Integer> collatz(int start) {
          return action -> {
              action.accept(start);
              return start == 1 ? null : collatz(start % 2 == 0 ? start / 2 : start * 3 + 1);
          };
      }
  
      public static IntEmitter collatzInt(int start) {
          return action -> {
              action.accept(start);
              return start == 1 ? null : collatzInt(start % 2 == 0 ? start / 2 : start * 3 + 1);
          };
      }
      
      public static LongEmitter collatzLong(long start) {
          return action -> {
              action.accept(start);
              return start == 1 ? null : collatzLong(start % 2 == 0 ? start / 2 : start * 3 + 1);
          };
      }
  
      // Stream of Fibonacci numbers
      public static StreamEx<BigInteger> fibonacci() {
          return fibonacci(BigInteger.ONE, BigInteger.ZERO).stream();
      }
  
      private static Emitter<BigInteger> fibonacci(BigInteger first, BigInteger second) {
          return action -> {
              BigInteger next = first.add(second);
              action.accept(next);
              return fibonacci(second, next);
          };
      }
  
      // Perform scanLeft on the iterator
      public static <T> Emitter<T> scanLeft(Iterator<T> iter, T initial, BinaryOperator<T> reducer) {
          return action -> {
              if (!iter.hasNext())
                  return null;
              T sum = reducer.apply(initial, iter.next());
              action.accept(sum);
              return scanLeft(iter, sum, reducer);
          };
      }
  
      public static Emitter<Integer> flatTest(int start) {
          return action -> {
              for (int i = 0; i < start; i++)
                  action.accept(start);
              return start == 0 ? null : flatTest(start - 1);
          };
      }
  
      public static IntEmitter flatTestInt(int start) {
          return action -> {
              for (int i = 0; i < start; i++)
                  action.accept(start);
              return start == 0 ? null : flatTestInt(start - 1);
          };
      }
      
      public static LongEmitter flatTestLong(int start) {
          return action -> {
              for (int i = 0; i < start; i++)
                  action.accept(start);
              return start == 0 ? null : flatTestLong(start - 1);
          };
      }
      
      public static DoubleEmitter flatTestDouble(int start) {
          return action -> {
              for (int i = 0; i < start; i++)
                  action.accept(start);
              return start == 0 ? null : flatTestDouble(start - 1);
          };
      }
      
      public static LongStreamEx primes() {
          return ((LongEmitter) (action -> {
              action.accept(2);
              return primes(3, x -> x % 2 != 0);
          })).stream();
      }
      
      private static LongEmitter primes(long start, LongPredicate isPrime) {
          return action -> {
              long nextPrime = LongStreamEx.range(start, Long.MAX_VALUE, 2).findFirst(isPrime).getAsLong();
              action.accept(nextPrime);
              return primes(nextPrime + 2, isPrime.and(x -> x % nextPrime != 0));
          };
      }
      
      @Test
      public void testEmitter() {
          assertEquals(asList(17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1), collatz(17).stream().toList());
          checkSpliterator("collatz", asList(17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1), collatz(17)::spliterator);
          assertArrayEquals(new int[] { 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1 },
              collatzInt(17).stream().toArray());
          checkSpliterator("collatzInt", asList(17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1),
              collatzInt(17)::spliterator);
          assertArrayEquals(new long[] { 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1 },
              collatzLong(17).stream().toArray());
          checkSpliterator("collatzLong", asList(17L, 52L, 26L, 13L, 40L, 20L, 10L, 5L, 16L, 8L, 4L, 2L, 1L),
              collatzLong(17)::spliterator);
          assertTrue(collatz(17).stream().has(1));
          assertTrue(collatzInt(17).stream().has(1));
          assertFalse(collatzLong(17).stream().has(0));
          assertEquals(asList(1, 2, 3, 4, 5, 6, 7, 8, 9), iterate(1, x -> x < 10, x -> x + 1).stream().toList());
  
          // Extracting to variables is necessary to work-around javac <8u40 bug
          String expected = "354224848179261915075";
          String actual = fibonacci().skip(99).findFirst().get().toString();
          assertEquals(expected, actual);
          assertEquals(asList(1, 1, 2, 3, 5, 8, 13, 21, 34, 55), fibonacci().map(BigInteger::intValueExact).limit(10)
                  .toList());
  
          assertEquals(asList("aa", "aabbb", "aabbbc"), scanLeft(asList("aa", "bbb", "c").iterator(), "", String::concat)
                  .stream().toList());
          
          assertEquals(asList(4, 4, 4, 4, 3, 3, 3, 2, 2, 1), flatTest(4).stream().toList());
          assertEquals(asList(4, 4, 4, 4, 3, 3, 3, 2, 2), flatTest(4).stream().limit(9).toList());
  
          TestHelpers.checkSpliterator("flatTest", asList(4, 4, 4, 4, 3, 3, 3, 2, 2, 1),
              flatTest(4)::spliterator);
          TestHelpers.checkSpliterator("flatTest", asList(4, 4, 4, 4, 3, 3, 3, 2, 2, 1),
              flatTestInt(4)::spliterator);
          TestHelpers.checkSpliterator("flatTest", asList(4L, 4L, 4L, 4L, 3L, 3L, 3L, 2L, 2L, 1L),
              flatTestLong(4)::spliterator);
          TestHelpers.checkSpliterator("flatTest", asList(4.0, 4.0, 4.0, 4.0, 3.0, 3.0, 3.0, 2.0, 2.0, 1.0),
              flatTestDouble(4)::spliterator);
          
          assertEquals(7919L, primes().skip(999).findFirst().getAsLong());
      }
  }
  