/******************************************************************************\
 * This example is a very basic, non-interactive math service implemented     *
 * for both the blocking and the event-based API.                             *
\******************************************************************************/

#include <iostream>

#include "caf/all.hpp"

using std::endl;
using namespace caf;

namespace {

// --(rst-calculator-actor-begin)--
using calculator_actor
  = typed_actor<result<int32_t>(add_atom, int32_t, int32_t),
                result<int32_t>(sub_atom, int32_t, int32_t)>;
// --(rst-calculator-actor-end)--

// --(rst-prototypes-begin)--
behavior calculator_fun(event_based_actor* self);
void blocking_calculator_fun(blocking_actor* self);
calculator_actor::behavior_type typed_calculator_fun();
class calculator;
class blocking_calculator;
class typed_calculator;
// --(rst-prototypes-end)--

// --(rst-function-based-begin)--
// function-based, dynamically typed, event-based API
behavior calculator_fun(event_based_actor*) {
  return {
    [](add_atom, int32_t a, int32_t b) { return a + b; },
    [](sub_atom, int32_t a, int32_t b) { return a - b; },
  };
}

// function-based, dynamically typed, blocking API
void blocking_calculator_fun(blocking_actor* self) {
  bool running = true;
  self->receive_while(running)( //
    [](add_atom, int32_t a, int32_t b) { return a + b; },
    [](sub_atom, int32_t a, int32_t b) { return a - b; },
    [&](exit_msg& em) {
      if (em.reason) {
        self->fail_state(std::move(em.reason));
        running = false;
      }
    });
}

// function-based, statically typed, event-based API
calculator_actor::behavior_type typed_calculator_fun() {
  return {
    [](add_atom, int32_t a, int32_t b) { return a + b; },
    [](sub_atom, int32_t a, int32_t b) { return a - b; },
  };
}
// --(rst-function-based-end)--

// --(rst-class-based-begin)--
// class-based, dynamically typed, event-based API
class calculator : public event_based_actor {
public:
  calculator(actor_config& cfg) : event_based_actor(cfg) {
    // nop
  }

  behavior make_behavior() override {
    return calculator_fun(this);
  }
};

// class-based, dynamically typed, blocking API
class blocking_calculator : public blocking_actor {
public:
  blocking_calculator(actor_config& cfg) : blocking_actor(cfg) {
    // nop
  }

  void act() override {
    blocking_calculator_fun(this);
  }
};

// class-based, statically typed, event-based API
class typed_calculator : public calculator_actor::base {
public:
  typed_calculator(actor_config& cfg) : calculator_actor::base(cfg) {
    // nop
  }

  behavior_type make_behavior() override {
    return typed_calculator_fun();
  }
};
// --(rst-class-based-end)--

void tester(scoped_actor&) {
  // end of recursion
}

// tests a calculator instance
template <class Handle, class... Ts>
void tester(scoped_actor& self, const Handle& hdl, int32_t x, int32_t y,
            Ts&&... xs) {
  auto handle_err = [&](const error& err) {
    aout(self) << "AUT (actor under test) failed: " << to_string(err) << endl;
  };
  // first test: x + y = z
  self->request(hdl, infinite, add_atom_v, x, y)
    .receive(
      [&](int32_t res1) {
        aout(self) << x << " + " << y << " = " << res1 << endl;
        // second test: x - y = z
        self->request(hdl, infinite, sub_atom_v, x, y)
          .receive(
            [&](int32_t res2) {
              aout(self) << x << " - " << y << " = " << res2 << endl;
            },
            handle_err);
      },
      handle_err);
  tester(self, std::forward<Ts>(xs)...);
}

void caf_main(actor_system& sys) {
  // --(rst-spawn-begin)--
  auto a1 = sys.spawn(blocking_calculator_fun);
  auto a2 = sys.spawn(calculator_fun);
  auto a3 = sys.spawn(typed_calculator_fun);
  auto a4 = sys.spawn<blocking_calculator>();
  auto a5 = sys.spawn<calculator>();
  auto a6 = sys.spawn<typed_calculator>();
  // --(rst-spawn-end)--
  scoped_actor self{sys};
  tester(self, a1, 1, 2, a2, 3, 4, a3, 5, 6, a4, 7, 8, a5, 9, 10, a6, 11, 12);
  self->send_exit(a1, exit_reason::user_shutdown);
  self->send_exit(a4, exit_reason::user_shutdown);
}

} // namespace

CAF_MAIN()
