/******************************************************************************
 *                       ____    _    _____                                   *
 *                      / ___|  / \  |  ___|    C++                           *
 *                     | |     / _ \ | |_       Actor                         *
 *                     | |___ / ___ \|  _|      Framework                     *
 *                      \____/_/   \_|_|                                      *
 *                                                                            *
 * Copyright 2011-2018 Dominik Charousset                                     *
 *                                                                            *
 * Distributed under the terms and conditions of the BSD 3-Clause License or  *
 * (at your option) under the terms and conditions of the Boost Software      *
 * License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE.       *
 *                                                                            *
 * If you did not receive a copy of the license files, see                    *
 * http://opensource.org/licenses/BSD-3-Clause and                            *
 * http://www.boost.org/LICENSE_1_0.txt.                                      *
 ******************************************************************************/

#pragma once

#include <type_traits>

#include "caf/catch_all.hpp"
#include "caf/message_id.hpp"
#include "caf/sec.hpp"
#include "caf/system_messages.hpp"
#include "caf/typed_behavior.hpp"

#include "caf/detail/type_list.hpp"
#include "caf/detail/typed_actor_util.hpp"

namespace caf {

/// This helper class identifies an expected response message and enables
/// `request(...).then(...)`.
template <class Self, class Output, bool IsBlocking>
class response_handle;

/******************************************************************************
 *                                 nonblocking                                *
 ******************************************************************************/
template <class Self, class Output>
class response_handle<Self, Output, false> {
public:
  response_handle() = delete;
  response_handle(const response_handle&) = default;
  response_handle& operator=(const response_handle&) = default;

  response_handle(message_id mid, Self* self) : mid_(mid), self_(self) {
    // nop
  }

  template <class F, class E = detail::is_callable_t<F>>
  void await(F f) const {
    await_impl(f);
  }

  template <class F, class OnError, class E1 = detail::is_callable_t<F>,
            class E2 = detail::is_handler_for_ef<OnError, error>>
  void await(F f, OnError e) const {
    await_impl(f, e);
  }

  template <class F, class E = detail::is_callable_t<F>>
  void then(F f) const {
    then_impl(f);
  }

  template <class F, class OnError, class E1 = detail::is_callable_t<F>,
            class E2 = detail::is_handler_for_ef<OnError, error>>
  void then(F f, OnError e) const {
    then_impl(f, e);
  }

  Self* self() {
    return self_;
  }

private:
  template <class F, class OnError>
  void await_impl(F& f, OnError& g) const {
    using result_type = typename detail::get_callable_trait<F>::result_type;
    static_assert(std::is_same<void, result_type>::value,
                  "response handlers are not allowed to have a return "
                  "type other than void");
    detail::type_checker<Output, F>::check();
    self_->add_awaited_response_handler(mid_,
                                        behavior{std::move(f), std::move(g)});
  }

  template <class F>
  void await_impl(F& f) const {
    auto self = self_;
    auto on_error = [self](error& err) { self->call_error_handler(err); };
    return await_impl(f, on_error);
  }

  template <class F, class OnError>
  void then_impl(F& f, OnError& g) const {
    using result_type = typename detail::get_callable_trait<F>::result_type;
    static_assert(std::is_same<void, result_type>::value,
                  "response handlers are not allowed to have a return "
                  "type other than void");
    detail::type_checker<Output, F>::check();
    self_->add_multiplexed_response_handler(mid_, behavior{std::move(f),
                                                           std::move(g)});
  }

  template <class F>
  void then_impl(F& f) const {
    auto self = self_;
    auto on_error = [self](error& err) { self->call_error_handler(err); };
    return then_impl(f, on_error);
  }

  message_id mid_;
  Self* self_;
};

/******************************************************************************
 *                                  blocking                                  *
 ******************************************************************************/
template <class Self, class Output>
class response_handle<Self, Output, true> {
public:
  response_handle() = delete;
  response_handle(const response_handle&) = default;
  response_handle& operator=(const response_handle&) = default;

  response_handle(message_id mid, Self* self) : mid_(mid), self_(self) {
    // nop
  }

  using error_handler = std::function<void(error&)>;

  template <class F, class OnError,
            class E = detail::is_handler_for_ef<OnError, error>>
  detail::is_callable_t<F> receive(F f, OnError g) {
    using result_type = typename detail::get_callable_trait<F>::result_type;
    static_assert(std::is_same<void, result_type>::value,
                  "response handlers are not allowed to have a return "
                  "type other than void");
    detail::type_checker<Output, F>::check();
    typename Self::accept_one_cond rc;
    self_->varargs_receive(rc, mid_, std::move(f), std::move(g));
  }

  template <class OnError, class F, class E = detail::is_callable_t<F>>
  detail::is_handler_for_ef<OnError, error> receive(OnError g, F f) {
    receive(std::move(f), std::move(g));
  }

  template <class OnError, class F,
            class E = detail::is_handler_for_ef<OnError, error>>
  void receive(OnError g, catch_all<F> f) {
    typename Self::accept_one_cond rc;
    self_->varargs_receive(rc, mid_, std::move(g), std::move(f));
  }

  Self* self() {
    return self_;
  }

private:
  message_id mid_;
  Self* self_;
};

} // namespace caf
