//
// detail/impl/win_object_handle_service.ipp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2023 Christopher M. Kohlhoff (chris at kohlhoff dot com)
// Copyright (c) 2011 Boris Schaeling (boris@highscore.de)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef ASIO_DETAIL_IMPL_WIN_OBJECT_HANDLE_SERVICE_IPP
#define ASIO_DETAIL_IMPL_WIN_OBJECT_HANDLE_SERVICE_IPP

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)

#include "asio/detail/config.hpp"

#if defined(ASIO_HAS_WINDOWS_OBJECT_HANDLE)

#include "asio/detail/win_object_handle_service.hpp"

#include "asio/detail/push_options.hpp"

namespace asio {
namespace detail {

win_object_handle_service::win_object_handle_service(execution_context& context)
  : execution_context_service_base<win_object_handle_service>(context),
    scheduler_(asio::use_service<scheduler_impl>(context)),
    mutex_(),
    impl_list_(0),
    shutdown_(false)
{
}

void win_object_handle_service::shutdown()
{
  mutex::scoped_lock lock(mutex_);

  // Setting this flag to true prevents new objects from being registered, and
  // new asynchronous wait operations from being started. We only need to worry
  // about cleaning up the operations that are currently in progress.
  shutdown_ = true;

  op_queue<operation> ops;
  for (implementation_type* impl = impl_list_; impl; impl = impl->next_)
    ops.push(impl->op_queue_);

  lock.unlock();

  scheduler_.abandon_operations(ops);
}

void win_object_handle_service::construct(
    win_object_handle_service::implementation_type& impl)
{
  impl.handle_ = INVALID_HANDLE_VALUE;
  impl.wait_handle_ = INVALID_HANDLE_VALUE;
  impl.owner_ = this;

  // Insert implementation into linked list of all implementations.
  mutex::scoped_lock lock(mutex_);
  if (!shutdown_)
  {
    impl.next_ = impl_list_;
    impl.prev_ = 0;
    if (impl_list_)
      impl_list_->prev_ = &impl;
    impl_list_ = &impl;
  }
}

void win_object_handle_service::move_construct(
    win_object_handle_service::implementation_type& impl,
    win_object_handle_service::implementation_type& other_impl)
{
  mutex::scoped_lock lock(mutex_);

  // Insert implementation into linked list of all implementations.
  if (!shutdown_)
  {
    impl.next_ = impl_list_;
    impl.prev_ = 0;
    if (impl_list_)
      impl_list_->prev_ = &impl;
    impl_list_ = &impl;
  }

  impl.handle_ = other_impl.handle_;
  other_impl.handle_ = INVALID_HANDLE_VALUE;
  impl.wait_handle_ = other_impl.wait_handle_;
  other_impl.wait_handle_ = INVALID_HANDLE_VALUE;
  impl.op_queue_.push(other_impl.op_queue_);
  impl.owner_ = this;

  // We must not hold the lock while calling UnregisterWaitEx. This is because
  // the registered callback function might be invoked while we are waiting for
  // UnregisterWaitEx to complete.
  lock.unlock();

  if (impl.wait_handle_ != INVALID_HANDLE_VALUE)
    ::UnregisterWaitEx(impl.wait_handle_, INVALID_HANDLE_VALUE);

  if (!impl.op_queue_.empty())
    register_wait_callback(impl, lock);
}

void win_object_handle_service::move_assign(
    win_object_handle_service::implementation_type& impl,
    win_object_handle_service& other_service,
    win_object_handle_service::implementation_type& other_impl)
{
  asio::error_code ignored_ec;
  close(impl, ignored_ec);

  mutex::scoped_lock lock(mutex_);

  if (this != &other_service)
  {
    // Remove implementation from linked list of all implementations.
    if (impl_list_ == &impl)
      impl_list_ = impl.next_;
    if (impl.prev_)
      impl.prev_->next_ = impl.next_;
    if (impl.next_)
      impl.next_->prev_= impl.prev_;
    impl.next_ = 0;
    impl.prev_ = 0;
  }

  impl.handle_ = other_impl.handle_;
  other_impl.handle_ = INVALID_HANDLE_VALUE;
  impl.wait_handle_ = other_impl.wait_handle_;
  other_impl.wait_handle_ = INVALID_HANDLE_VALUE;
  impl.op_queue_.push(other_impl.op_queue_);
  impl.owner_ = this;

  if (this != &other_service)
  {
    // Insert implementation into linked list of all implementations.
    impl.next_ = other_service.impl_list_;
    impl.prev_ = 0;
    if (other_service.impl_list_)
      other_service.impl_list_->prev_ = &impl;
    other_service.impl_list_ = &impl;
  }

  // We must not hold the lock while calling UnregisterWaitEx. This is because
  // the registered callback function might be invoked while we are waiting for
  // UnregisterWaitEx to complete.
  lock.unlock();

  if (impl.wait_handle_ != INVALID_HANDLE_VALUE)
    ::UnregisterWaitEx(impl.wait_handle_, INVALID_HANDLE_VALUE);

  if (!impl.op_queue_.empty())
    register_wait_callback(impl, lock);
}

void win_object_handle_service::destroy(
    win_object_handle_service::implementation_type& impl)
{
  mutex::scoped_lock lock(mutex_);

  // Remove implementation from linked list of all implementations.
  if (impl_list_ == &impl)
    impl_list_ = impl.next_;
  if (impl.prev_)
    impl.prev_->next_ = impl.next_;
  if (impl.next_)
    impl.next_->prev_= impl.prev_;
  impl.next_ = 0;
  impl.prev_ = 0;

  if (is_open(impl))
  {
    ASIO_HANDLER_OPERATION((scheduler_.context(), "object_handle",
          &impl, reinterpret_cast<uintmax_t>(impl.wait_handle_), "close"));

    HANDLE wait_handle = impl.wait_handle_;
    impl.wait_handle_ = INVALID_HANDLE_VALUE;

    op_queue<operation> ops;
    while (wait_op* op = impl.op_queue_.front())
    {
      op->ec_ = asio::error::operation_aborted;
      impl.op_queue_.pop();
      ops.push(op);
    }

    // We must not hold the lock while calling UnregisterWaitEx. This is
    // because the registered callback function might be invoked while we are
    // waiting for UnregisterWaitEx to complete.
    lock.unlock();

    if (wait_handle != INVALID_HANDLE_VALUE)
      ::UnregisterWaitEx(wait_handle, INVALID_HANDLE_VALUE);

    ::CloseHandle(impl.handle_);
    impl.handle_ = INVALID_HANDLE_VALUE;

    scheduler_.post_deferred_completions(ops);
  }
}

asio::error_code win_object_handle_service::assign(
    win_object_handle_service::implementation_type& impl,
    const native_handle_type& handle, asio::error_code& ec)
{
  if (is_open(impl))
  {
    ec = asio::error::already_open;
    ASIO_ERROR_LOCATION(ec);
    return ec;
  }

  impl.handle_ = handle;
  ec = asio::error_code();
  return ec;
}

asio::error_code win_object_handle_service::close(
    win_object_handle_service::implementation_type& impl,
    asio::error_code& ec)
{
  if (is_open(impl))
  {
    ASIO_HANDLER_OPERATION((scheduler_.context(), "object_handle",
          &impl, reinterpret_cast<uintmax_t>(impl.wait_handle_), "close"));

    mutex::scoped_lock lock(mutex_);

    HANDLE wait_handle = impl.wait_handle_;
    impl.wait_handle_ = INVALID_HANDLE_VALUE;

    op_queue<operation> completed_ops;
    while (wait_op* op = impl.op_queue_.front())
    {
      impl.op_queue_.pop();
      op->ec_ = asio::error::operation_aborted;
      completed_ops.push(op);
    }

    // We must not hold the lock while calling UnregisterWaitEx. This is
    // because the registered callback function might be invoked while we are
    // waiting for UnregisterWaitEx to complete.
    lock.unlock();

    if (wait_handle != INVALID_HANDLE_VALUE)
      ::UnregisterWaitEx(wait_handle, INVALID_HANDLE_VALUE);

    if (::CloseHandle(impl.handle_))
    {
      impl.handle_ = INVALID_HANDLE_VALUE;
      ec = asio::error_code();
    }
    else
    {
      DWORD last_error = ::GetLastError();
      ec = asio::error_code(last_error,
          asio::error::get_system_category());
    }

    scheduler_.post_deferred_completions(completed_ops);
  }
  else
  {
    ec = asio::error_code();
  }

  ASIO_ERROR_LOCATION(ec);
  return ec;
}

asio::error_code win_object_handle_service::cancel(
    win_object_handle_service::implementation_type& impl,
    asio::error_code& ec)
{
  if (is_open(impl))
  {
    ASIO_HANDLER_OPERATION((scheduler_.context(), "object_handle",
          &impl, reinterpret_cast<uintmax_t>(impl.wait_handle_), "cancel"));

    mutex::scoped_lock lock(mutex_);

    HANDLE wait_handle = impl.wait_handle_;
    impl.wait_handle_ = INVALID_HANDLE_VALUE;

    op_queue<operation> completed_ops;
    while (wait_op* op = impl.op_queue_.front())
    {
      op->ec_ = asio::error::operation_aborted;
      impl.op_queue_.pop();
      completed_ops.push(op);
    }

    // We must not hold the lock while calling UnregisterWaitEx. This is
    // because the registered callback function might be invoked while we are
    // waiting for UnregisterWaitEx to complete.
    lock.unlock();

    if (wait_handle != INVALID_HANDLE_VALUE)
      ::UnregisterWaitEx(wait_handle, INVALID_HANDLE_VALUE);

    ec = asio::error_code();

    scheduler_.post_deferred_completions(completed_ops);
  }
  else
  {
    ec = asio::error::bad_descriptor;
  }

  ASIO_ERROR_LOCATION(ec);
  return ec;
}

void win_object_handle_service::wait(
    win_object_handle_service::implementation_type& impl,
    asio::error_code& ec)
{
  switch (::WaitForSingleObject(impl.handle_, INFINITE))
  {
  case WAIT_FAILED:
    {
      DWORD last_error = ::GetLastError();
      ec = asio::error_code(last_error,
          asio::error::get_system_category());
      ASIO_ERROR_LOCATION(ec);
      break;
    }
  case WAIT_OBJECT_0:
  case WAIT_ABANDONED:
  default:
    ec = asio::error_code();
    break;
  }
}

void win_object_handle_service::start_wait_op(
    win_object_handle_service::implementation_type& impl, wait_op* op)
{
  scheduler_.work_started();

  if (is_open(impl))
  {
    mutex::scoped_lock lock(mutex_);

    if (!shutdown_)
    {
      impl.op_queue_.push(op);

      // Only the first operation to be queued gets to register a wait callback.
      // Subsequent operations have to wait for the first to finish.
      if (impl.op_queue_.front() == op)
        register_wait_callback(impl, lock);
    }
    else
    {
      lock.unlock();
      scheduler_.post_deferred_completion(op);
    }
  }
  else
  {
    op->ec_ = asio::error::bad_descriptor;
    scheduler_.post_deferred_completion(op);
  }
}

void win_object_handle_service::register_wait_callback(
    win_object_handle_service::implementation_type& impl,
    mutex::scoped_lock& lock)
{
  lock.lock();

  if (!RegisterWaitForSingleObject(&impl.wait_handle_,
        impl.handle_, &win_object_handle_service::wait_callback,
        &impl, INFINITE, WT_EXECUTEONLYONCE))
  {
    DWORD last_error = ::GetLastError();
    asio::error_code ec(last_error,
        asio::error::get_system_category());

    op_queue<operation> completed_ops;
    while (wait_op* op = impl.op_queue_.front())
    {
      op->ec_ = ec;
      impl.op_queue_.pop();
      completed_ops.push(op);
    }

    lock.unlock();
    scheduler_.post_deferred_completions(completed_ops);
  }
}

void win_object_handle_service::wait_callback(PVOID param, BOOLEAN)
{
  implementation_type* impl = static_cast<implementation_type*>(param);
  mutex::scoped_lock lock(impl->owner_->mutex_);

  if (impl->wait_handle_ != INVALID_HANDLE_VALUE)
  {
    ::UnregisterWaitEx(impl->wait_handle_, NULL);
    impl->wait_handle_ = INVALID_HANDLE_VALUE;
  }

  if (wait_op* op = impl->op_queue_.front())
  {
    op_queue<operation> completed_ops;

    op->ec_ = asio::error_code();
    impl->op_queue_.pop();
    completed_ops.push(op);

    if (!impl->op_queue_.empty())
    {
      if (!RegisterWaitForSingleObject(&impl->wait_handle_,
            impl->handle_, &win_object_handle_service::wait_callback,
            param, INFINITE, WT_EXECUTEONLYONCE))
      {
        DWORD last_error = ::GetLastError();
        asio::error_code ec(last_error,
            asio::error::get_system_category());

        while ((op = impl->op_queue_.front()) != 0)
        {
          op->ec_ = ec;
          impl->op_queue_.pop();
          completed_ops.push(op);
        }
      }
    }

    scheduler_impl& sched = impl->owner_->scheduler_;
    lock.unlock();
    sched.post_deferred_completions(completed_ops);
  }
}

} // namespace detail
} // namespace asio

#include "asio/detail/pop_options.hpp"

#endif // defined(ASIO_HAS_WINDOWS_OBJECT_HANDLE)

#endif // ASIO_DETAIL_IMPL_WIN_OBJECT_HANDLE_SERVICE_IPP
