/*
    This file is part of TON Blockchain Library.
    TON Blockchain Library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.
    TON Blockchain Library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License
    along with TON Blockchain Library.  If not, see .
    Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "td/utils/common.h"
#include "td/utils/invoke.h"
#include "td/utils/logging.h"
#include 
#include 
#include 
#include 
//
// Essentially we have:
// (ActorT::func, arg1, arg2, ..., argn)
// We want to call:
// actor->func(arg1, arg2, ..., argn)
// And in some cases we would like to delay this call.
//
// First attempt would be
// [a1=arg1, a2=arg2, ..., an=argn](ActorT *actor) {
//   actor->func(a1, a2, ..., an)
// }
//
// But there are some difficulties with elimitation on unnecessary copies.
// We want to use move constructor when it is possible
//
// We may pass
// Tmp. Temporary / rvalue reference
// Var. Variable / reference
// CnstRef. const reference
//
//
// Function may expect
// Val. Value
// CnstRef. const reference
// Ref. rvalue reverence / reference
//
// TODO:
//    Immediate call / Delayed call
// Tmp->Val       move / move->move
// Tmp->CnstRef      + / move->+
// Tmp->Ref          + / move->+
// Var->Val       copy / copy->move
// Var->CnstRef      + / copy->
// Var->Ref          + / copy->+   // khm. It will complile, but won't work
//
// So I will use common idiom: forward references
// If delay is needed, just std::forward data to temporary storage, and std::move them when call is executed.
//
//
// create_immediate_closure(&ActorT::func, arg1, arg2, ..., argn).run(actor)
// to_delayed_closure(std::move(immediate)).run(actor)
namespace td {
template 
class DelayedClosure;
template 
class ImmediateClosure {
 public:
  using Delayed = DelayedClosure;
  friend Delayed;
  using ActorType = ActorT;
  // no &&. just save references as references.
  explicit ImmediateClosure(FunctionT func, ArgsT... args) : args(func, std::forward(args)...) {
  }
 private:
  std::tuple args;
 public:
  auto run(ActorT *actor) -> decltype(mem_call_tuple(actor, std::move(args))) {
    return mem_call_tuple(actor, std::move(args));
  }
};
template 
ImmediateClosure create_immediate_closure(
    ResultT (ActorT::*func)(DestArgsT...), SrcArgsT &&... args) {
  return ImmediateClosure(func,
                                                                                     std::forward(args)...);
}
template 
class DelayedClosure {
 public:
  using ActorType = ActorT;
  using Delayed = DelayedClosure;
  DelayedClosure clone() const {
    return do_clone(*this);
  }
  explicit DelayedClosure(ImmediateClosure &&other) : args(std::move(other.args)) {
  }
  explicit DelayedClosure(FunctionT func, ArgsT... args) : args(func, std::forward(args)...) {
  }
  template 
  void for_each(const F &f) {
    tuple_for_each(args, f);
  }
 private:
  using ArgsStorageT = std::tuple::type...>;
  ArgsStorageT args;
  template 
  explicit DelayedClosure(const DelayedClosure &other,
                          std::enable_if_t::value...>::value, int> = 0)
      : args(other.args) {
  }
  template 
  explicit DelayedClosure(
      const DelayedClosure &other,
      std::enable_if_t::value...>::value, int> = 0) {
    LOG(FATAL) << "Deleted constructor";
    std::abort();
  }
  template 
  std::enable_if_t::value...>::value,
                   DelayedClosure>
  do_clone(const DelayedClosure &value) const {
    LOG(FATAL) << "Trying to clone DelayedClosure that contains noncopyable elements";
    std::abort();
  }
  template 
  std::enable_if_t::value...>::value,
                   DelayedClosure>
  do_clone(const DelayedClosure &value) const {
    return DelayedClosure(value);
  }
 public:
  auto run(ActorT *actor) -> decltype(mem_call_tuple(actor, std::move(args))) {
    return mem_call_tuple(actor, std::move(args));
  }
};
template 
typename ImmediateClosure::Delayed to_delayed_closure(ImmediateClosure &&other) {
  return typename ImmediateClosure::Delayed(std::move(other));
}
template 
DelayedClosure to_delayed_closure(DelayedClosure &&other) {
  return std::move(other);
}
template 
auto create_delayed_closure(ResultT (ActorT::*func)(DestArgsT...), SrcArgsT &&... args) {
  return DelayedClosure(func,
                                                                                   std::forward(args)...);
}
}  // namespace td