//    OpenVPN -- An application to securely tunnel IP networks
//               over a single port, with support for SSL/TLS-based
//               session authentication and key exchange,
//               packet encryption, packet authentication, and
//               packet compression.
//
//    Copyright (C) 2012-2015 OpenVPN Technologies, Inc.
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License Version 3
//    as published by the Free Software Foundation.
//
//    This program 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 General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program in the COPYING file.
//    If not, see <http://www.gnu.org/licenses/>.

// These classes define function objects to be used as asynchronous callbacks
// for Asio methods.  Think of these as optimized special cases of function
// objects that could be more generally (but perhaps less optimally) defined
// with lambdas.

#ifndef OPENVPN_COMMON_ASIODISPATCH_H
#define OPENVPN_COMMON_ASIODISPATCH_H

#include <asio.hpp>

#include <openvpn/common/size.hpp>
#include <openvpn/common/rc.hpp>

namespace openvpn {
  // Dispatcher for asio async_write

  template <typename C, typename Handler>
  class AsioDispatchWrite
  {
  public:
    AsioDispatchWrite(Handler handle_write, C* obj)
      : handle_write_(handle_write), obj_(obj) {}

    void operator()(const asio::error_code& error, const size_t bytes_sent)
    {
      (obj_.get()->*handle_write_)(error, bytes_sent);
    }

  private:
    Handler handle_write_;
    RCPtr<C> obj_;
  };

  template <typename C, typename Handler>
  AsioDispatchWrite<C, Handler> asio_dispatch_write(Handler handle_write, C* obj)
  {
    return AsioDispatchWrite<C, Handler>(handle_write, obj);
  }

  // Dispatcher for asio async_read with argument

  template <typename C, typename Handler, typename Data>
  class AsioDispatchRead
  {
  public:
    AsioDispatchRead(Handler handle_read, C* obj, Data data)
      : handle_read_(handle_read), obj_(obj), data_(data) {}

    void operator()(const asio::error_code& error, const size_t bytes_recvd)
    {
      (obj_.get()->*handle_read_)(data_, error, bytes_recvd);
    }

  private:
    Handler handle_read_;
    RCPtr<C> obj_;
    Data data_;
  };

  template <typename C, typename Handler, typename Data>
  AsioDispatchRead<C, Handler, Data> asio_dispatch_read(Handler handle_read, C* obj, Data data)
  {
    return AsioDispatchRead<C, Handler, Data>(handle_read, obj, data);
  }

  // Dispatcher for asio async_read without argument

  template <typename C, typename Handler>
  class AsioDispatchReadNoArg
  {
  public:
    AsioDispatchReadNoArg(Handler handle_read, C* obj)
      : handle_read_(handle_read), obj_(obj) {}

    void operator()(const asio::error_code& error, const size_t bytes_recvd)
    {
      (obj_.get()->*handle_read_)(error, bytes_recvd);
    }

  private:
    Handler handle_read_;
    RCPtr<C> obj_;
  };

  template <typename C, typename Handler>
  AsioDispatchReadNoArg<C, Handler> asio_dispatch_read_noarg(Handler handle_read, C* obj)
  {
    return AsioDispatchReadNoArg<C, Handler>(handle_read, obj);
  }

  // Dispatcher for asio async_wait with argument

  template <typename C, typename Handler, typename Data>
  class AsioDispatchTimerArg
  {
  public:
    AsioDispatchTimerArg(Handler handler, C* obj, Data data)
      : handler_(handler), obj_(obj), data_(data) {}

    void operator()(const asio::error_code& error)
    {
      (obj_.get()->*handler_)(data_, error);
    }

  private:
    Handler handler_;
    RCPtr<C> obj_;
    Data data_;
  };

  template <typename C, typename Handler, typename Data>
  AsioDispatchTimerArg<C, Handler, Data> asio_dispatch_timer_arg(Handler handler, C* obj, Data data)
  {
    return AsioDispatchTimerArg<C, Handler, Data>(handler, obj, data);
  }

  // Dispatcher for asio async_wait without argument

  template <typename C, typename Handler>
  class AsioDispatchTimer
  {
  public:
    AsioDispatchTimer(Handler handler, C* obj)
      : handler_(handler), obj_(obj) {}

    void operator()(const asio::error_code& error)
    {
      (obj_.get()->*handler_)(error);
    }

  private:
    Handler handler_;
    RCPtr<C> obj_;
  };

  template <typename C, typename Handler>
  AsioDispatchTimer<C, Handler> asio_dispatch_timer(Handler handler, C* obj)
  {
    return AsioDispatchTimer<C, Handler>(handler, obj);
  }

  // Dispatcher for asio async_connect with argument

  template <typename C, typename Handler, typename Data>
  class AsioDispatchConnectArg
  {
  public:
    AsioDispatchConnectArg(Handler handler, C* obj, Data data)
      : handler_(handler), obj_(obj), data_(data) {}

    void operator()(const asio::error_code& error)
    {
      (obj_.get()->*handler_)(data_, error);
    }

  private:
    Handler handler_;
    RCPtr<C> obj_;
    Data data_;
  };

  template <typename C, typename Handler, typename Data>
  AsioDispatchConnectArg<C, Handler, Data> asio_dispatch_connect_arg(Handler handler, C* obj, Data data)
  {
    return AsioDispatchConnectArg<C, Handler, Data>(handler, obj, data);
  }

  // Dispatcher for asio async_connect without argument

  template <typename C, typename Handler>
  class AsioDispatchConnect
  {
  public:
    AsioDispatchConnect(Handler handler, C* obj)
      : handler_(handler), obj_(obj) {}

    void operator()(const asio::error_code& error)
    {
      (obj_.get()->*handler_)(error);
    }

  private:
    Handler handler_;
    RCPtr<C> obj_;
  };

  template <typename C, typename Handler>
  AsioDispatchConnect<C, Handler> asio_dispatch_connect(Handler handler, C* obj)
  {
    return AsioDispatchConnect<C, Handler>(handler, obj);
  }

  // Dispatcher for asio async_connect (ComposedConnectHandler) without argument

  template <typename C, typename Handler>
  class AsioDispatchComposedConnect
  {
  public:
    AsioDispatchComposedConnect(Handler handler, C* obj)
      : handler_(handler), obj_(obj) {}

    void operator()(const asio::error_code& error, asio::ip::tcp::resolver::iterator endpoint_iterator)
    {
      (obj_.get()->*handler_)(error, endpoint_iterator);
    }

  private:
    Handler handler_;
    RCPtr<C> obj_;
  };

  template <typename C, typename Handler>
  AsioDispatchComposedConnect<C, Handler> asio_dispatch_composed_connect(Handler handler, C* obj)
  {
    return AsioDispatchComposedConnect<C, Handler>(handler, obj);
  }

  // Dispatcher for asio async_accept with argument

  template <typename C, typename Handler, typename Data>
  class AsioDispatchAcceptArg
  {
  public:
    AsioDispatchAcceptArg(Handler handler, C* obj, Data data)
      : handler_(handler), obj_(obj), data_(data) {}

    void operator()(const asio::error_code& error)
    {
      (obj_.get()->*handler_)(data_, error);
    }

  private:
    Handler handler_;
    RCPtr<C> obj_;
    Data data_;
  };

  template <typename C, typename Handler, typename Data>
  AsioDispatchAcceptArg<C, Handler, Data> asio_dispatch_accept_arg(Handler handler, C* obj, Data data)
  {
    return AsioDispatchAcceptArg<C, Handler, Data>(handler, obj, data);
  }

  // Dispatcher for asio post with argument

  template <typename C, typename Handler, typename Data>
  class AsioDispatchPostArg
  {
  public:
    AsioDispatchPostArg(Handler handler, C* obj, Data data)
      : handler_(handler), obj_(obj), data_(data) {}

    void operator()()
    {
      (obj_.get()->*handler_)(data_);
    }

  private:
    Handler handler_;
    RCPtr<C> obj_;
    Data data_;
  };

  template <typename C, typename Handler, typename Data>
  AsioDispatchPostArg<C, Handler, Data> asio_dispatch_post_arg(Handler handler, C* obj, Data data)
  {
    return AsioDispatchPostArg<C, Handler, Data>(handler, obj, data);
  }

  // Dispatcher for asio post without argument

  template <typename C, typename Handler>
  class AsioDispatchPost
  {
  public:
    AsioDispatchPost(Handler handler, C* obj)
      : handler_(handler), obj_(obj) {}

    void operator()()
    {
      (obj_.get()->*handler_)();
    }

  private:
    Handler handler_;
    RCPtr<C> obj_;
  };

  template <typename C, typename Handler>
  AsioDispatchPost<C, Handler> asio_dispatch_post(Handler handler, C* obj)
  {
    return AsioDispatchPost<C, Handler>(handler, obj);
  }

  // Dispatcher for asynchronous resolver without argument

  template <typename C, typename Handler, typename EndpointIterator>
  class AsioDispatchResolve
  {
  public:
    AsioDispatchResolve(Handler handler, C* obj)
      : handler_(handler), obj_(obj) {}

    void operator()(const asio::error_code& error, EndpointIterator iter)
    {
      (obj_.get()->*handler_)(error, iter);
    }

  private:
    Handler handler_;
    RCPtr<C> obj_;
  };

  // Dispatcher for asynchronous resolver with argument

  template <typename C, typename Handler, typename EndpointIterator, typename Data>
  class AsioDispatchResolveArg
  {
  public:
    AsioDispatchResolveArg(Handler handler, C* obj, Data data)
      : handler_(handler), obj_(obj), data_(data) {}

    void operator()(const asio::error_code& error, EndpointIterator iter)
    {
      (obj_.get()->*handler_)(error, iter, data_);
    }

  private:
    Handler handler_;
    RCPtr<C> obj_;
    Data data_;
  };


  // Dispatcher for asio signal

  template <typename C, typename Handler>
  class AsioDispatchSignal
  {
  public:
    AsioDispatchSignal(Handler handler, C* obj)
      : handler_(handler), obj_(obj) {}

    void operator()(const asio::error_code& error, int signal_number)
    {
      (obj_.get()->*handler_)(error, signal_number);
    }

  private:
    Handler handler_;
    RCPtr<C> obj_;
  };

  template <typename C, typename Handler>
  AsioDispatchSignal<C, Handler> asio_dispatch_signal(Handler handler, C* obj)
  {
    return AsioDispatchSignal<C, Handler>(handler, obj);
  }

  // General purpose dispatcher with data

  template <typename C, typename Handler, typename Data>
  class SimpleDispatch
  {
  public:
    SimpleDispatch(Handler handler, C* obj)
      : handler_(handler), obj_(obj) {}

    void operator()(Data data)
    {
      (obj_->*handler_)(data);
    }

  private:
    Handler handler_;
    C* obj_;
  };

} // namespace openvpn

#endif // OPENVPN_COMMON_ASIODISPATCH_H
