/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file LICENSE.rst or https://cmake.org/licensing for details.  */
#pragma once
#include "cmConfigure.h" // IWYU pragma: keep

#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <type_traits>

#include <cm3p/uv.h>

#if defined(__SUNPRO_CC)

#  include <utility>

#  define CM_INHERIT_CTOR(Class, Base, Tpl)                                   \
    template <typename... Args>                                               \
    Class(Args&&... args)                                                     \
      : Base Tpl(std::forward<Args>(args)...)                                 \
    {                                                                         \
    }

#else

#  define CM_INHERIT_CTOR(Class, Base, Tpl) using Base Tpl ::Base

#endif

namespace cm {

/** Whether to call uv_update_time before starting a timer.
 *
 * uv_loop_t caches a "now" time that uv_loop_init initializes and
 * uv_run updates on each event loop iteration.  uv_timer_start
 * computes timer expiry relative to the loop's cached "now" time.
 * For short timeouts started before the event loop, we may need to
 * update the "now" time when starting the timer.
 */
enum class uv_update_time
{
  no,
  yes,
};

/***
 * RAII class to simplify and ensure the safe usage of uv_loop_t. This includes
 * making sure resources are properly freed.
 */
class uv_loop_ptr
{
protected:
  std::shared_ptr<uv_loop_t> loop;

public:
  uv_loop_ptr(uv_loop_ptr const&) = delete;
  uv_loop_ptr& operator=(uv_loop_ptr const&) = delete;
  uv_loop_ptr(uv_loop_ptr&&) noexcept;
  uv_loop_ptr& operator=(uv_loop_ptr&&) noexcept;

  // Dtor and ctor need to be inline defined like this for default ctors and
  // dtors to work.  Some compilers do not like '= default' here.
  uv_loop_ptr() {} // NOLINT(modernize-use-equals-default)
  uv_loop_ptr(std::nullptr_t) {}
  ~uv_loop_ptr() { this->reset(); }

  int init(void* data = nullptr);

  /**
   * Properly close the handle if needed and sets the inner handle to nullptr
   */
  void reset();

  /**
   * Allow less verbose calling of uv_loop_* functions
   * @return reinterpreted handle
   */
  operator uv_loop_t*() const;

  uv_loop_t* get() const;
  uv_loop_t* operator->() const noexcept;
  uv_loop_t& operator*() const;
};

/***
 * RAII class to simplify and ensure the safe usage of uv_*_t types. This
 * includes making sure resources are properly freed and contains casting
 * operators which allow for passing into relevant uv_* functions.
 *
 *@tparam T actual uv_*_t type represented.
 */
template <typename T>
class uv_handle_ptr_base_
{
protected:
  template <typename U>
  friend class uv_handle_ptr_base_;

  /**
   * This must be a pointer type since the handle can outlive this class.
   * When uv_close is eventually called on the handle, the memory the
   * handle inhabits must be valid until the close callback is called
   * which can be later on in the loop.
   */
  std::shared_ptr<T> handle;

  /**
   * Allocate memory for the type and optionally set it's 'data' pointer.
   * Protected since this should only be called for an appropriate 'init'
   * call.
   *
   * @param data data pointer to set
   */
  void allocate(void* data = nullptr);

public:
  uv_handle_ptr_base_(uv_handle_ptr_base_ const&) = delete;
  uv_handle_ptr_base_& operator=(uv_handle_ptr_base_ const&) = delete;
  uv_handle_ptr_base_(uv_handle_ptr_base_&&) noexcept;
  uv_handle_ptr_base_& operator=(uv_handle_ptr_base_&&) noexcept;

  /**
   * This move constructor allows us to move out of a more specialized
   * uv type into a less specialized one. The only constraint is that
   * the right hand side is castable to T.
   *
   * This allows you to return uv_handle_ptr or uv_stream_ptr from a function
   * that initializes something like uv_pipe_ptr or uv_tcp_ptr and interact
   * and clean up after it without caring about the exact type.
   */
  template <typename S,
            typename = typename std::enable_if<
              std::is_rvalue_reference<S&&>::value>::type>
  uv_handle_ptr_base_(S&& rhs)
  {
    // This will force a compiler error if rhs doesn't have a casting
    // operator to get T*
    this->handle = std::shared_ptr<T>(rhs.handle, rhs);
    rhs.handle.reset();
  }

  // Dtor and ctor need to be inline defined like this for default ctors and
  // dtors to work.  Some compilers do not like '= default' here.
  uv_handle_ptr_base_() {} // NOLINT(modernize-use-equals-default)
  uv_handle_ptr_base_(std::nullptr_t) {}
  ~uv_handle_ptr_base_() { this->reset(); }

#if defined(__SUNPRO_CC)
  // The Oracle Studio compiler recognizes 'explicit operator bool()' in
  // 'if(foo)' but not 'if(foo && ...)'.  The purpose of 'explicit' here
  // is to avoid accidental conversion in non-boolean contexts.  Just
  // leave it out on this compiler so we can compile valid code.
  operator bool() const;
#else
  explicit operator bool() const;
#endif

  /**
   * Properly close the handle if needed and sets the inner handle to nullptr
   */
  void reset();

  /**
   * Allow less verbose calling of uv_handle_* functions
   * @return reinterpreted handle
   */
  operator uv_handle_t*() const;

  T* get() const;
  T* operator->() const noexcept;
};

template <typename T>
uv_handle_ptr_base_<T>::uv_handle_ptr_base_(
  uv_handle_ptr_base_<T>&&) noexcept = default;
template <typename T>
uv_handle_ptr_base_<T>& uv_handle_ptr_base_<T>::operator=(
  uv_handle_ptr_base_<T>&&) noexcept = default;

/**
 * While uv_handle_ptr_base_ only exposes uv_handle_t*, this exposes uv_T_t*
 * too. It is broken out like this so we can reuse most of the code for the
 * uv_handle_ptr class.
 */
template <typename T>
class uv_handle_ptr_ : public uv_handle_ptr_base_<T>
{
  template <typename U>
  friend class uv_handle_ptr_;

public:
  CM_INHERIT_CTOR(uv_handle_ptr_, uv_handle_ptr_base_, <T>);

  /***
   * Allow less verbose calling of uv_<T> functions
   * @return reinterpreted handle
   */
  operator T*() const;
};

/***
 * This specialization is required to avoid duplicate 'operator uv_handle_t*()'
 * declarations
 */
template <>
class uv_handle_ptr_<uv_handle_t> : public uv_handle_ptr_base_<uv_handle_t>
{
public:
  CM_INHERIT_CTOR(uv_handle_ptr_, uv_handle_ptr_base_, <uv_handle_t>);
};

class uv_async_ptr : public uv_handle_ptr_<uv_async_t>
{
public:
  CM_INHERIT_CTOR(uv_async_ptr, uv_handle_ptr_, <uv_async_t>);

  int init(uv_loop_t& loop, uv_async_cb async_cb, void* data = nullptr);

  void send();
};

struct uv_idle_ptr : public uv_handle_ptr_<uv_idle_t>
{
  CM_INHERIT_CTOR(uv_idle_ptr, uv_handle_ptr_, <uv_idle_t>);

  int init(uv_loop_t& loop, void* data = nullptr);

  int start(uv_idle_cb cb);

  void stop();
};

struct uv_signal_ptr : public uv_handle_ptr_<uv_signal_t>
{
  CM_INHERIT_CTOR(uv_signal_ptr, uv_handle_ptr_, <uv_signal_t>);

  int init(uv_loop_t& loop, void* data = nullptr);

  int start(uv_signal_cb cb, int signum);

  void stop();
};

struct uv_pipe_ptr : public uv_handle_ptr_<uv_pipe_t>
{
  CM_INHERIT_CTOR(uv_pipe_ptr, uv_handle_ptr_, <uv_pipe_t>);

  operator uv_stream_t*() const;

  int init(uv_loop_t& loop, int ipc, void* data = nullptr);
};

struct uv_process_ptr : public uv_handle_ptr_<uv_process_t>
{
  CM_INHERIT_CTOR(uv_process_ptr, uv_handle_ptr_, <uv_process_t>);

  int spawn(uv_loop_t& loop, uv_process_options_t const& options,
            void* data = nullptr);
};

struct uv_timer_ptr : public uv_handle_ptr_<uv_timer_t>
{
  CM_INHERIT_CTOR(uv_timer_ptr, uv_handle_ptr_, <uv_timer_t>);

  int init(uv_loop_t& loop, void* data = nullptr);

  int start(uv_timer_cb cb, uint64_t timeout, uint64_t repeat,
            uv_update_time update_time);

  void stop();
};

struct uv_tty_ptr : public uv_handle_ptr_<uv_tty_t>
{
  CM_INHERIT_CTOR(uv_tty_ptr, uv_handle_ptr_, <uv_tty_t>);

  operator uv_stream_t*() const;

  int init(uv_loop_t& loop, int fd, int readable, void* data = nullptr);
};

using uv_stream_ptr = uv_handle_ptr_<uv_stream_t>;
using uv_handle_ptr = uv_handle_ptr_<uv_handle_t>;

#ifndef cmUVHandlePtr_cxx

extern template class uv_handle_ptr_base_<uv_handle_t>;

#  define UV_HANDLE_PTR_INSTANTIATE_EXTERN(NAME)                              \
    extern template class uv_handle_ptr_base_<uv_##NAME##_t>;                 \
    extern template class uv_handle_ptr_<uv_##NAME##_t>;

UV_HANDLE_PTR_INSTANTIATE_EXTERN(async)

UV_HANDLE_PTR_INSTANTIATE_EXTERN(idle)

UV_HANDLE_PTR_INSTANTIATE_EXTERN(signal)

UV_HANDLE_PTR_INSTANTIATE_EXTERN(pipe)

UV_HANDLE_PTR_INSTANTIATE_EXTERN(process)

UV_HANDLE_PTR_INSTANTIATE_EXTERN(stream)

UV_HANDLE_PTR_INSTANTIATE_EXTERN(timer)

UV_HANDLE_PTR_INSTANTIATE_EXTERN(tty)

#  undef UV_HANDLE_PTR_INSTANTIATE_EXTERN

#endif

/**
 * Wraps uv_write to add synchronous cancellation.
 *
 * libuv provides no way to synchronously cancel a write request.
 * Closing a write handle will cancel its pending write request, but its
 * callback will still be called asynchronously later with UV_ECANCELED.
 *
 * This wrapper provides a solution by handing ownership of the uv_write_t
 * request object to the event loop and taking it back in the callback.
 * Use this in combination with uv_loop_ptr to ensure the event loop
 * runs to completion and cleans up all resources.
 *
 * The caller may optionally provide a callback it owns with std::shared_ptr.
 * If the caller's lifetime ends before the write request completes, the
 * callback can be safely deleted and will not be called.
 *
 * The bufs array does not need to live beyond this call, but the memory
 * referenced by the uv_buf_t values must remain alive until the callback
 * is made or the stream is closed.
 */
int uv_write(uv_stream_t* handle, uv_buf_t const bufs[], unsigned int nbufs,
             std::weak_ptr<std::function<void(int)>> cb);
}
