/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
#define cmUVHandlePtr_cxx
#include "cmUVHandlePtr.h"

#include <assert.h>
#include <mutex>
#include <stdlib.h>

#include "cm_uv.h"

namespace cm
{
static void
close_delete(uv_handle_t* h)
{
    free(h);
}

template <typename T>
static void
default_delete(T* type_handle)
{
    auto handle = reinterpret_cast<uv_handle_t*>(type_handle);
    if(handle)
    {
        assert(!uv_is_closing(handle));
        if(!uv_is_closing(handle))
        {
            uv_close(handle, &close_delete);
        }
    }
}

/**
 * Encapsulates delete logic for a given handle type T
 */
template <typename T> struct uv_handle_deleter
{
    void operator()(T* type_handle) const { default_delete(type_handle); }
};

template <typename T>
void
uv_handle_ptr_base_<T>::allocate(void* data)
{
    reset();

    /*
      We use calloc since we know all these types are c structs
      and we just want to 0 init them. New would do the same thing;
      but casting from uv_handle_t to certain other types -- namely
      uv_timer_t -- triggers a cast_align warning on certain systems.
    */
    handle.reset(static_cast<T*>(calloc(1, sizeof(T))), uv_handle_deleter<T>());
    handle->data = data;
}

template <typename T>
void
uv_handle_ptr_base_<T>::reset()
{
    handle.reset();
}

template <typename T> uv_handle_ptr_base_<T>::operator uv_handle_t*()
{
    return reinterpret_cast<uv_handle_t*>(handle.get());
}

template <typename T> T* uv_handle_ptr_base_<T>::operator->() const noexcept
{
    return handle.get();
}

template <typename T>
T*
uv_handle_ptr_base_<T>::get() const
{
    return handle.get();
}

template <typename T> uv_handle_ptr_<T>::operator T*() const
{
    return this->handle.get();
}

#ifdef CMAKE_BUILD_WITH_CMAKE
template <> struct uv_handle_deleter<uv_async_t>
{
    /***
     * Wile uv_async_send is itself thread-safe, there are
     * no strong guarantees that close hasn't already been
     * called on the handle; and that it might be deleted
     * as the send call goes through. This mutex guards
     * against that.
     *
     * The shared_ptr here is to allow for copy construction
     * which is mandated by the standard for Deleter on
     * shared_ptrs.
     */
    std::shared_ptr<std::mutex> handleMutex;

    uv_handle_deleter()
    : handleMutex(std::make_shared<std::mutex>())
    {}

    void operator()(uv_async_t* handle)
    {
        std::lock_guard<std::mutex> lock(*handleMutex);
        default_delete(handle);
    }
};

void
uv_async_ptr::send()
{
    auto deleter =
        std::get_deleter<uv_handle_deleter<uv_async_t>>(this->handle);
    assert(deleter);

    std::lock_guard<std::mutex> lock(*deleter->handleMutex);
    if(this->handle)
    {
        uv_async_send(*this);
    }
}

int
uv_async_ptr::init(uv_loop_t& loop, uv_async_cb async_cb, void* data)
{
    allocate(data);
    return uv_async_init(&loop, handle.get(), async_cb);
}
#endif

template <> struct uv_handle_deleter<uv_signal_t>
{
    void operator()(uv_signal_t* handle) const
    {
        if(handle)
        {
            uv_signal_stop(handle);
            default_delete(handle);
        }
    }
};

int
uv_signal_ptr::init(uv_loop_t& loop, void* data)
{
    allocate(data);
    return uv_signal_init(&loop, handle.get());
}

int
uv_signal_ptr::start(uv_signal_cb cb, int signum)
{
    assert(handle);
    return uv_signal_start(*this, cb, signum);
}

void
uv_signal_ptr::stop()
{
    if(handle)
    {
        uv_signal_stop(*this);
    }
}

int
uv_pipe_ptr::init(uv_loop_t& loop, int ipc, void* data)
{
    allocate(data);
    return uv_pipe_init(&loop, *this, ipc);
}

uv_pipe_ptr::operator uv_stream_t*() const
{
    return reinterpret_cast<uv_stream_t*>(handle.get());
}

#ifdef CMAKE_BUILD_WITH_CMAKE
int
uv_process_ptr::spawn(uv_loop_t& loop, uv_process_options_t const& options,
                      void* data)
{
    allocate(data);
    return uv_spawn(&loop, *this, &options);
}

int
uv_timer_ptr::init(uv_loop_t& loop, void* data)
{
    allocate(data);
    return uv_timer_init(&loop, *this);
}

int
uv_timer_ptr::start(uv_timer_cb cb, uint64_t timeout, uint64_t repeat)
{
    assert(handle);
    return uv_timer_start(*this, cb, timeout, repeat);
}

uv_tty_ptr::operator uv_stream_t*() const
{
    return reinterpret_cast<uv_stream_t*>(handle.get());
}

int
uv_tty_ptr::init(uv_loop_t& loop, int fd, int readable, void* data)
{
    allocate(data);
    return uv_tty_init(&loop, *this, fd, readable);
}
#endif

template class uv_handle_ptr_base_<uv_handle_t>;

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

UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(signal)

UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(pipe)

UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(stream)

#ifdef CMAKE_BUILD_WITH_CMAKE
UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(async)

UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(process)

UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(timer)

UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(tty)
#endif
}
