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

#if defined(__ANDROID__)
#undef __ANDROID__
#endif

#include "catch.hpp"

#include "rotor-light/context.hpp"
#include "rotor-light/planner.hpp"
#include "rotor-light/queue.hpp"
#include "rotor-light/supervisor.hpp"

using namespace rotor_light;

using MessageStorage =
    traits::MessageStorage<message::ChangeState, message::ChangeStateAck>;
using AppQueue = Queue<MessageStorage, 5>;
using AppPlanner = Planner<1>;

TEST_CASE("start & finish empty supervisor", "[sup]") {
  using AppSupervisor = Supervisor<3>;
  AppPlanner planner;
  AppQueue queue;
  AppSupervisor sup;
  Context context{&queue, &planner, nullptr};
  CHECK(sup.get_state() == State::off);
  sup.bind(context);

  sup.start();
  sup.process();
  CHECK(sup.get_state() == State::operational);

  sup.stop();
  sup.process();
  CHECK(sup.get_state() == State::off);
}

TEST_CASE("supervisor & succesfull actor", "[sup]") {
  using DummyActor = Actor<1>;
  using AppSupervisor = Supervisor<3, DummyActor>;
  AppPlanner planner;
  AppQueue queue;
  AppSupervisor sup;
  Context context{&queue, &planner, nullptr};
  CHECK(sup.get_state() == State::off);
  sup.bind(context);
  CHECK(sup.get_id() == 1 << 0);

  auto act = sup.get_child<0>();
  CHECK(act->get_state() == State::off);
  CHECK(act->get_id() == 1 << 1);

  sup.start();
  sup.process();
  CHECK(sup.get_state() == State::operational);
  CHECK(act->get_state() == State::operational);

  sup.stop();
  sup.process();
  CHECK(sup.get_state() == State::off);
  CHECK(act->get_state() == State::off);
}

TEST_CASE("supervisor & failing actor, fail policy = restart (init)", "[sup]") {
  struct DummyActor : Actor<1> {
    DummyActor() {
      inits = 0;
      fail_policy = FailPolicy::restart;
    }
    void advance_init() override {
      send<ctx::thread, message::ChangeStateAck>(0, supervisor->get_id(), id,
                                                 State::initialized, false);
      if (++inits >= 2) {
        fail_policy = FailPolicy::ignore;
      }
    }
    size_t inits;
  };

  using AppSupervisor = Supervisor<3, DummyActor>;
  AppPlanner planner;
  AppQueue queue;
  AppSupervisor sup;
  Context context{&queue, &planner, nullptr};
  sup.bind(context);
  CHECK(sup.get_state() == State::off);
  CHECK(sup.get_id() == 1 << 0);

  auto act = sup.get_child<0>();
  CHECK(act->get_state() == State::off);
  CHECK(act->get_id() == 1 << 1);

  sup.start();
  sup.process();
  CHECK(sup.get_state() == State::initializing);
  CHECK(act->get_state() == State::off);
  CHECK(act->inits == 2);

  sup.stop();
  sup.process();
  CHECK(sup.get_state() == State::off);
  CHECK(act->get_state() == State::off);
}

TEST_CASE("supervisor & failing actor, fail policy = init (operational)",
          "[sup]") {
  struct DummyActor : Actor<1> {
    using Parent = Actor<1>;
    DummyActor() {
      starts = 0;
      fail_policy = FailPolicy::restart;
    }
    void advance_start() override {
      Parent::advance_start();
      stop();
      ++starts;
    }
    size_t starts;
  };

  using AppSupervisor = Supervisor<3, DummyActor>;
  AppPlanner planner;
  AppQueue queue;
  AppSupervisor sup;
  Context context{&queue, &planner, nullptr};
  sup.bind(context);
  auto act = sup.get_child<0>();

  sup.start();
  sup.process();
  CHECK(sup.get_state() == State::operational);
  CHECK(act->get_state() == State::off);
  CHECK(act->starts == 1);

  sup.stop();
  sup.process();
  CHECK(sup.get_state() == State::off);
  CHECK(act->get_state() == State::off);
}

TEST_CASE("supervisor & failing actor, fail policy = force_restart (init)",
          "[sup]") {
  struct DummyActor : Actor<1> {
    DummyActor() {
      inits = 0;
      fail_policy = FailPolicy::force_restart;
    }
    void advance_init() override {
      send<ctx::thread, message::ChangeStateAck>(0, supervisor->get_id(), id,
                                                 State::initialized, false);
      if (++inits >= 2) {
        fail_policy = FailPolicy::ignore;
      }
    }
    size_t inits;
  };

  using AppSupervisor = Supervisor<3, DummyActor>;
  AppPlanner planner;
  AppQueue queue;
  AppSupervisor sup;
  Context context{&queue, &planner, nullptr};
  sup.bind(context);

  auto act = sup.get_child<0>();
  sup.start();
  sup.process();
  CHECK(sup.get_state() == State::initializing);
  CHECK(act->get_state() == State::off);
  CHECK(act->inits == 2);

  sup.stop();
  sup.process();
  CHECK(sup.get_state() == State::off);
  CHECK(act->get_state() == State::off);
}

TEST_CASE("supervisor & failing actor, fail policy = force_restart (start)",
          "[sup]") {
  struct DummyActor : Actor<1> {
    using Parent = Actor<1>;
    DummyActor() {
      starts = 0;
      fail_policy = FailPolicy::force_restart;
    }
    void advance_start() override {
      Parent::advance_start();
      if (++starts >= 2) {
        fail_policy = FailPolicy::ignore;
      }
      stop();
    }
    size_t starts;
  };

  using AppSupervisor = Supervisor<3, DummyActor>;
  AppPlanner planner;
  AppQueue queue;
  AppSupervisor sup;
  Context context{&queue, &planner, nullptr};
  sup.bind(context);
  CHECK(sup.get_state() == State::off);
  CHECK(sup.get_id() == 1 << 0);

  auto act = sup.get_child<0>();
  CHECK(act->get_state() == State::off);
  CHECK(act->get_id() == 1 << 1);

  sup.start();
  sup.process();
  CHECK(sup.get_state() == State::operational);
  CHECK(act->get_state() == State::off);
  CHECK(act->starts == 2);

  sup.stop();
  sup.process();
  CHECK(sup.get_state() == State::off);
  CHECK(act->get_state() == State::off);
}

TEST_CASE("supervisor & failing actor, fail policy = escalate (init)",
          "[sup]") {
  struct DummyActor : Actor<1> {
    DummyActor() {
      inits = 0;
      fail_policy = FailPolicy::escalate;
    }
    void advance_init() override {
      send<ctx::thread, message::ChangeStateAck>(0, supervisor->get_id(), id,
                                                 State::initialized, false);
      ++inits;
    }
    size_t inits;
  };

  using AppSupervisor = Supervisor<3, DummyActor>;
  AppPlanner planner;
  AppQueue queue;
  AppSupervisor sup;
  Context context{&queue, &planner, nullptr};
  sup.bind(context);
  CHECK(sup.get_state() == State::off);
  CHECK(sup.get_id() == 1 << 0);

  auto act = sup.get_child<0>();
  CHECK(act->get_state() == State::off);
  CHECK(act->get_id() == 1 << 1);

  sup.start();
  sup.process();
  CHECK(sup.get_state() == State::off);
  CHECK(act->get_state() == State::off);
  CHECK(act->inits == 1);
}

TEST_CASE("supervisor & failing actor, fail policy = escalate (operational)",
          "[sup]") {
  struct DummyActor : Actor<1> {
    using Parent = Actor<1>;
    DummyActor() {
      starts = 0;
      fail_policy = FailPolicy::escalate;
    }
    void advance_start() override {
      Parent::advance_start();
      stop();
      ++starts;
    }
    size_t starts;
  };

  using AppSupervisor = Supervisor<3, DummyActor>;
  AppPlanner planner;
  AppQueue queue;
  AppSupervisor sup;
  Context context{&queue, &planner, nullptr};
  sup.bind(context);
  auto act = sup.get_child<0>();

  sup.start();
  sup.process();
  CHECK(sup.get_state() == State::operational);
  CHECK(act->get_state() == State::off);
  CHECK(act->starts == 1);

  sup.stop();
  sup.process();
  CHECK(sup.get_state() == State::off);
  CHECK(act->get_state() == State::off);
}

TEST_CASE("supervisor & failing actor, fail policy = force_escalate (init)",
          "[sup]") {
  struct DummyActor : Actor<1> {
    DummyActor() {
      inits = 0;
      fail_policy = FailPolicy::force_escalate;
    }
    void advance_init() override {
      send<ctx::thread, message::ChangeStateAck>(0, supervisor->get_id(), id,
                                                 State::initialized, false);
      ++inits;
    }
    size_t inits;
  };

  using AppSupervisor = Supervisor<3, DummyActor>;
  AppPlanner planner;
  AppQueue queue;
  AppSupervisor sup;
  Context context{&queue, &planner, nullptr};
  sup.bind(context);

  auto act = sup.get_child<0>();
  sup.start();
  sup.process();
  CHECK(sup.get_state() == State::off);
  CHECK(act->get_state() == State::off);
  CHECK(act->inits == 1);
}

TEST_CASE(
    "supervisor & failing actor, fail policy = force_escalate (operational)",
    "[sup]") {
  struct DummyActor : Actor<1> {
    using Parent = Actor<1>;
    DummyActor() {
      starts = 0;
      fail_policy = FailPolicy::force_escalate;
    }
    void advance_start() override {
      Parent::advance_start();
      stop();
      ++starts;
    }
    size_t starts;
  };

  using AppSupervisor = Supervisor<3, DummyActor>;
  AppPlanner planner;
  AppQueue queue;
  AppSupervisor sup;
  Context context{&queue, &planner, nullptr};
  sup.bind(context);
  auto act = sup.get_child<0>();

  sup.start();
  sup.process();
  CHECK(sup.get_state() == State::off);
  CHECK(act->get_state() == State::off);
  CHECK(act->starts == 1);
}
