text: #include <catch.hxx>
  #include <rapidcheck/catch.h>
  
  #include "rapidcheck/seq/Create.h"
  #include "rapidcheck/seq/Transform.h"
  
  #include "util/Meta.h"
  #include "util/Util.h"
  #include "util/TypeListMacros.h"
  #include "util/SeqUtils.h"
  #include "util/Logger.h"
  
  using namespace rc;
  using namespace rc::test;
  
  TEST_CASE("seq::repeat") {
    prop("repeatedly returns the given value",
         [](const std::string &value) {
           auto seq = seq::repeat(value);
           for (int i = 0; i < 2000; i++) {
             auto x = seq.next();
             RC_ASSERT(x);
             RC_ASSERT(*x == value);
           }
         });
  
    prop("copies are equal",
         [](const std::string &value) {
           assertEqualCopies(seq::take(200, seq::repeat(value)));
         });
  
    SECTION("does not copy value on construction") {
      auto seq = seq::repeat(Logger());
      REQUIRE(seq.next()->numberOf("copy") <= 1);
    }
  }
  
  TEST_CASE("seq::just") {
    SECTION("does not copy values") {
      auto seq = seq::just(Logger(), Logger(), Logger());
      REQUIRE(seq.next()->numberOf("copy") == 0);
      REQUIRE(seq.next()->numberOf("copy") == 0);
      REQUIRE(seq.next()->numberOf("copy") == 0);
    }
  
    prop("returns the passed in objects",
         [](const std::string &a, const std::string &b, const std::string &c) {
           auto seq = seq::just(a, b, c);
           RC_ASSERT(*seq.next() == a);
           RC_ASSERT(*seq.next() == b);
           RC_ASSERT(*seq.next() == c);
           RC_ASSERT(!seq.next());
         });
  
    prop("copies are equal",
         [](const std::string &a, const std::string &b, const std::string &c) {
           assertEqualCopies(seq::just(a, b, c));
         });
  }
  
  struct FromContainerTests {
    template <typename T>
    static void exec() {
      templatedProp<T>(
          "the returned elements are equal to the elements"
          " returned by iterating",
          [](const T &elements) {
            auto seq = seq::fromContainer(elements);
            for (const auto &x : elements) {
              RC_ASSERT(*seq.next() == x);
            }
            RC_ASSERT(!seq.next());
          });
  
      templatedProp<T>("copies are equal",
                       [](const T &elements) {
                         assertEqualCopies(seq::fromContainer(elements));
                       });
    }
  };
  
  struct FromContainerCopyTests {
    template <typename T>
    static void exec() {
      TEMPLATED_SECTION(T, "does not copy elements") {
        auto seq = seq::fromContainer(T{Logger(), Logger(), Logger()});
        while (const auto value = seq.next()) {
          REQUIRE(value->numberOf("copy") <= 1);
        }
      }
    }
  };
  
  TEST_CASE("seq::fromContainer") {
    forEachType<FromContainerTests,
                RC_ORDERED_CONTAINERS(std::string),
                RC_STRING_TYPES,
                std::array<std::string, 100>>();
  
    // TODO Weirdly arbitrary to run this for a category called
    // RC_SEQUENCE_CONTAINERS
    forEachType<FromContainerCopyTests,
                RC_SEQUENCE_CONTAINERS(Logger),
                std::array<Logger, 100>>();
  }
  
  TEST_CASE("seq::fromIteratorRange") {
    prop("creates a sequence that returns the values in the range",
         [](const std::vector<int> &elements) {
           const auto r1 = *gen::inRange<std::size_t>(0, elements.size() + 1);
           const auto r2 = *gen::inRange<std::size_t>(0, elements.size() + 1);
           const auto start = std::min(r1, r2);
           const auto end = std::max(r1, r2);
           const auto startIt = elements.begin() + start;
           const auto endIt = elements.begin() + end;
           std::vector<int> subElements(startIt, endIt);
           RC_ASSERT(seq::fromIteratorRange(startIt, endIt) ==
                     seq::fromContainer(subElements));
         });
  }
  
  TEST_CASE("seq::iterate") {
    prop(
        "returns an infinite sequence of applying the given function to the"
        " value",
        [](int start, int inc) {
          const auto func = [=](int x) { return x + inc; };
          auto seq = seq::iterate(start, func);
          int x = start;
          // Arbitrary number of iterations, sequence is infinite
          for (int i = 0; i < 1000; i++) {
            RC_ASSERT(*seq.next() == x);
            x = func(x);
          }
        });
  
    prop("copies are equal",
         [](int start, int inc) {
           const auto func = [=](int x) { return x + inc; };
           assertEqualCopies(seq::take(1000, seq::iterate(start, func)));
         });
  }
  
  TEST_CASE("seq::range") {
    static const auto smallInt = gen::scale(0.25, gen::arbitrary<int>());
  
    prop("returns a sequence from start to end",
         [] {
           int start = *smallInt;
           int end = *smallInt;
  
           auto seq = seq::range(start, end);
           int inc = (start < end) ? 1 : -1;
           int i = start;
           while (i != end) {
             auto x = seq.next();
             RC_ASSERT(x);
             RC_ASSERT(*x == i);
             i += inc;
           }
           RC_ASSERT(!seq.next());
         });
  
    prop("copies are equal",
         [&] { assertEqualCopies(seq::range(*smallInt, *smallInt)); });
  }
  
  TEST_CASE("seq::index") {
    SECTION("returns an infinite sequence of increasing indexes from 0") {
      auto seq = seq::index();
      for (std::size_t i = 0; i < 2000; i++) {
        auto x = seq.next();
        RC_ASSERT(x);
        RC_ASSERT(*x == i);
      }
    }
  
    prop("copies are equal",
         [] { assertEqualCopies(seq::take(2000, seq::index())); });
  }
  
  TEST_CASE("seq::subranges") {
    // TODO some kind of "small int" would be nice
    static const auto smallInt = gen::inRange<std::size_t>(0, 100);
  
    prop("ranges successively decrease in size",
         [] {
           // TODO range generator
           const auto a = *smallInt;
           const auto b = *gen::distinctFrom(smallInt, a);
           const auto start = std::min(a, b);
           const auto end = std::max(a, b);
  
           auto lastSize = end - start;
           using Range = std::pair<std::size_t, std::size_t>;
           seq::forEach(seq::subranges(start, end),
                        [&](const Range &r) {
                          const auto size = r.second - r.first;
                          RC_ASSERT(size <= lastSize);
                          lastSize = size;
                        });
         });
  
    prop("yields all possible subranges",
         [] {
           // TODO range generator
           const auto a = *smallInt;
           const auto b = *gen::distinctFrom(smallInt, a);
           const auto start = std::min(a, b);
           const auto end = std::max(a, b);
  
           const auto indexInRange = gen::inRange<std::size_t>(start, end);
           const auto as = *indexInRange;
           const auto bs = *gen::distinctFrom(indexInRange, as);
           const auto starts = std::min(as, bs);
           const auto ends = std::max(as, bs);
  
           RC_ASSERT(seq::contains(seq::subranges(start, end), {starts, ends}));
         });
  }
  