#include <catch.hpp>
#include <array>

#include "pls/pls.h"

using namespace pls;

TEST_CASE("for_each functions correctly", "[algorithms/for_each.h]") {
  malloc_scheduler_memory my_scheduler_memory{8, 2 << 12};
  scheduler my_scheduler{&my_scheduler_memory, 8};
  my_scheduler.perform_work([]() {
    constexpr int SIZE = 1000;
    std::array<int, SIZE> result_array{};
    result_array.fill(0);

    SECTION("integer ranges are processed exactly once") {
      pls::for_each_range(0, SIZE, [&result_array](int i) {
        result_array[i]++;
      });

      bool all_equal = true;
      for (int i = 0; i < SIZE; i++) {
        all_equal &= result_array[i] == 1;
      }
      REQUIRE (all_equal);
    }

    SECTION("iterators are processed exactly once") {
      std::array<int, SIZE> iterator_array{};
      for (int i = 0; i < SIZE; i++) {
        iterator_array[i] = i;
      }

      pls::for_each(iterator_array.begin(), iterator_array.end(), [&result_array](int i) {
        result_array[i]++;
      });

      bool all_equal = true;
      for (int i = 0; i < SIZE; i++) {
        all_equal &= result_array[i] == 1;
      }
      REQUIRE (all_equal);
    }
  });
}

TEST_CASE("scan functions correctly", "[algorithms/scan.h]") {
  malloc_scheduler_memory my_scheduler_memory{8, 2 << 12};
  scheduler my_scheduler{&my_scheduler_memory, 8};
  my_scheduler.perform_work([]() {
    constexpr int SIZE = 10000;
    std::array<int, SIZE> input_array{}, result_array{};
    input_array.fill(1);

    pls::scan(input_array.begin(), input_array.end(), result_array.begin(), std::plus<int>(), 0);

    bool all_correct = true;
    for (int i = 0; i < SIZE; i++) {
      all_correct &= result_array[i] == (i + 1);
    }
    REQUIRE (all_correct);
  });
}

long fib(long n) {
  if (n <= 2) {
    return 1;
  }

  long a, b;

  pls::invoke(
      [&a, n]() { a = fib(n - 1); },
      [&b, n]() { b = fib(n - 2); }
  );

  return a + b;
}

TEST_CASE("invoke functions correctly", "[algorithms/invoke.h]") {
  constexpr long fib_30 = 832040;

  malloc_scheduler_memory my_scheduler_memory{8, 2u << 14};
  scheduler my_scheduler{&my_scheduler_memory, 8};
  my_scheduler.perform_work([=]() {
    REQUIRE(fib(30) == fib_30);
  });
}
