// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2022 Ivan Baidakou

#if defined(__ANDROID__)
#undef __ANDROID__
#endif

#include "catch.hpp"

#include "rotor-light/planner.hpp"
#include <limits>
#include <vector>

using namespace rotor_light;

TEST_CASE("1 event planner", "[planner]") {
  using AppPlanner = Planner<1>;
  auto planner = AppPlanner();
  CHECK(!planner.consume(15));
  CHECK(planner.next_event() == 0);

  static int invoked = 0;
  static void *ptr = nullptr;
  auto cb = [](void *data) {
    ++invoked;
    ptr = data;
  };
  auto event_id = planner.add_event<ctx::thread>(10, cb, &planner);
  CHECK(event_id);
  CHECK(planner.next_event() == 10);
  CHECK(!planner.add_event<ctx::thread>(11, cb, &planner));
  CHECK(!planner.consume(9));

  SECTION("consume") {
    auto consumer = planner.consume(10);
    CHECK(consumer);
    consumer.call_next();
    CHECK(invoked == 1);
    CHECK(ptr == &planner);
    CHECK(!consumer);
  }

  SECTION("remove") {
    planner.remove_event(event_id);
    planner.remove_event(event_id);
  }

  CHECK(!planner.consume(15));
  CHECK(planner.next_event() == 0);
}

TEST_CASE("4 events planner", "[planner]") {
  using Vector = std::vector<int>;
  using AppPlanner = Planner<4>;
  auto planner = AppPlanner();
  CHECK(!planner.consume(15));

  static Vector order{};
  static int invoked = 0;
  static void *ptr = nullptr;
  static void *ptr2 = nullptr;
  static void *ptr3 = nullptr;
  static void *ptr4 = nullptr;
  auto refresh = []() {
    ptr = ptr2 = ptr3 = ptr4 = nullptr;
    invoked = 0;
    order = {};
  };

  auto cb1 = [](void *data) {
    ++invoked;
    ptr = data;
    order.emplace_back(1);
  };
  planner.add_event<ctx::thread>(10, cb1, &planner);

  auto cb2 = [](void *data) {
    ++invoked;
    ptr2 = data;
    order.emplace_back(2);
  };
  planner.add_event<ctx::thread>(8, cb2, &ptr);

  auto cb3 = [](void *data) {
    ++invoked;
    ptr3 = data;
    order.emplace_back(3);
  };
  planner.add_event<ctx::thread>(9, cb3, &ptr2);

  auto cb4 = [](void *data) {
    ++invoked;
    ptr4 = data;
    order.emplace_back(4);
  };
  planner.add_event<ctx::thread>(99, cb4, &ptr3);

  SECTION("consume all at once") {
    refresh();
    auto consumer = planner.consume(100);
    while (consumer) {
      consumer.call_next();
    }
  }

  SECTION("consume by one") {
    refresh();
    planner.consume(8).call_next();
    planner.consume(9).call_next();
    planner.consume(10).call_next();
    planner.consume(199).call_next();
  }

  SECTION("consume 2 and 2") {
    refresh();
    {
      CHECK(planner.next_event() == 8);
      auto c = planner.consume(9);
      c.call_next();
      c.call_next();
      CHECK(!c);
    }

    {
      CHECK(planner.next_event() == 10);
      auto c = planner.consume(100);
      c.call_next();
      c.call_next();
      CHECK(!c);
    }
  }

  SECTION("consume 1 and 3") {
    refresh();
    {
      auto c = planner.consume(8);
      c.call_next();
      CHECK(!c);
    }

    {
      auto c = planner.consume(100);
      c.call_next();
      c.call_next();
      c.call_next();
      CHECK(!c);
    }
  }

  SECTION("consume 3 and 1") {
    refresh();
    {
      auto c = planner.consume(50);
      c.call_next();
      c.call_next();
      c.call_next();
      CHECK(!c);
    }

    {
      auto c = planner.consume(100);
      c.call_next();
      CHECK(!c);
    }
  }

  CHECK(invoked == 4);
  CHECK(!planner.consume(200));
  CHECK(ptr == &planner);
  CHECK(ptr2 == &ptr);
  CHECK(ptr3 == &ptr2);
  CHECK(ptr4 == &ptr3);
  CHECK(order == Vector{2, 3, 1, 4});
}

TEST_CASE("3 events planner & events removal", "[planner]") {
  using AppPlanner = Planner<3>;
  auto planner = AppPlanner();
  auto some_val = int{5};
  auto some_val_2 = int{6};
  auto e1 = planner.add_event<ctx::thread>(
      5, [](void *) {}, &planner);
  auto e2 = planner.add_event<ctx::thread>(
      5, [](void *) {}, &some_val);
  auto e3 = planner.add_event<ctx::thread>(
      6, [](void *) {}, &some_val_2);

  SECTION("remove 1-2-3") {
    planner.remove_event(e1);
    planner.remove_event(e2);
    planner.remove_event(e3);
  }

  SECTION("remove 3-2-1") {
    planner.remove_event(e3);
    planner.remove_event(e2);
    planner.remove_event(e1);
  }

  SECTION("remove 2-3-1") {
    planner.remove_event(e2);
    planner.remove_event(e3);
    planner.remove_event(e1);
  }

  SECTION("remove 3-1-2") {
    planner.remove_event(e3);
    planner.remove_event(e1);
    planner.remove_event(e2);
  }

  CHECK(planner.next_event() == 0);
}

TEST_CASE("event id uniquenesss", "[planner]") {
  using AppPlanner = Planner<3>;
  auto planner = AppPlanner();
  auto callback = [](void *) {};
  auto e1 = planner.add_event<ctx::thread>(6, callback, nullptr);
  auto e2 = planner.add_event<ctx::thread>(5, callback, nullptr);
  auto e3 = std::numeric_limits<EventId>::max();
  while (true) {
    auto e = planner.add_event<ctx::thread>(15, callback, nullptr);
    if (e == e3) {
      break;
    }
    planner.remove_event(e);
  }
  planner.remove_event(e2);
  auto e4 = planner.add_event<ctx::thread>(4, callback, nullptr);
  CHECK(e4 == e2 + 1);

  planner.remove_event(e1);
  planner.remove_event(e4);
  planner.remove_event(e3);
  CHECK(planner.next_event() == 0);
}
