/** Functions that wait.
 */
#include "pqxx-source.hxx"

#include "pqxx/internal/config.h"

#include <array>

// TODO: Prune #includes once we have PQsocketPoll().

// For WSAPoll().
// Normally we'd do this *after* including <thread>, but MinGW complains: it
// issues a warning telling us to include winsock2.h before windows.h.
// We don't actually include windows.h ourselves, but it looks as if MinGW's
// <thread> does.
#if __has_include(<winsock2.h>)
#  include <winsock2.h>
#  define PQXX_HAVE_SELECT
#endif

// The <thread> header is still broken on MinGW.  :-(
#if defined(PQXX_HAVE_SLEEP_FOR)
#  include <thread>
#endif

#if __has_include(<ws2tcpip.h>)
#  include <ws2tcpip.h>
#endif
#if __has_include(<mstcpip.h>)
#  include <mstcpip.h>
#endif

// For poll():
#if __has_include(<poll.h>)
#  include <poll.h>
#endif

// For select() on recent POSIX systems.
#if __has_include(<sys/select.h>)
#  include <sys/select.h>
#  define PQXX_HAVE_SELECT
#endif

// For select() on some older POSIX systems.
#if __has_include(<sys/types.h>)
#  include <sys/types.h>
#  define PQXX_HAVE_SELECT
#endif
#if __has_include(<unistd.h>)
#  include <unistd.h>
#endif
#if __has_include(<sys/time.h>)
#  include <sys/time.h>
#endif


#include "pqxx/internal/header-pre.hxx"

#include "pqxx/internal/wait.hxx"
#include "pqxx/util.hxx"

#include "pqxx/internal/header-post.hxx"


namespace
{
// TODO: No longer needed once we have PQsocketPoll().
template<typename T>
T to_milli(unsigned seconds, unsigned microseconds, pqxx::sl loc)
{
  return pqxx::check_cast<T>(
    (seconds * 1000) + (microseconds / 1000),
    "Wait timeout value out of bounds.", loc);
}


// TODO: No longer needed once we have PQsocketPoll().
#if !defined(PQXX_HAVE_POLL) && defined(PQXX_HAVE_SELECT)
/// Set a bit on an fd_set.
[[maybe_unused]] void set_fdbit(fd_set &bits, auto fd)
{
#  ifdef _MSC_VER
// Suppress pointless, unfixable warnings in Visual Studio.
#    pragma warning(push)
#    pragma warning(disable : 4389) // Signed/unsigned mismatch.
#    pragma warning(disable : 4127) // Conditional expression is constant.
#  endif
  FD_SET(fd, &bits);
#  ifdef _MSV_VER
// Restore prevalent warning settings.
#    pragma warning(pop)
#  endif
}
#endif
} // namespace


void pqxx::internal::wait_fd(
  int fd, bool for_read, bool for_write, unsigned seconds,
  unsigned microseconds, [[maybe_unused]] sl loc)
{
// TODO: As of libpq 17, replace with PQsocketPoll()/PQgetCurrentTimeUSec().
// WSAPoll is available in winsock2.h only for versions of Windows >= 0x0600
#if defined(_WIN32) && (_WIN32_WINNT >= 0x0600)
  static_assert(SOCKET_ERROR == -1);
  short const events{static_cast<short>(
    (for_read ? POLLRDNORM : 0) | (for_write ? POLLWRNORM : 0))};
  WSAPOLLFD fdarray{SOCKET(fd), events, 0};
  int const code{WSAPoll(
    &fdarray, 1u,
    check_cast<int>(
      to_milli<unsigned>(seconds, microseconds, loc), "Timeout too large."))};
#elif defined(PQXX_HAVE_POLL)
  auto const events{static_cast<short>(
    POLLERR | POLLHUP | POLLNVAL | (for_read ? POLLIN : 0) |
    (for_write ? POLLOUT : 0))};
  pollfd pfd{fd, events, 0};
  int const code{poll(&pfd, 1, to_milli<int>(seconds, microseconds, loc))};
#else
  // No poll()?  Our last option is select().
  fd_set read_fds;
  FD_ZERO(&read_fds);
  if (for_read)
    set_fdbit(read_fds, fd);

  fd_set write_fds;
  FD_ZERO(&write_fds);
  if (for_write)
    set_fdbit(write_fds, fd);

  fd_set except_fds;
  FD_ZERO(&except_fds);
  set_fdbit(except_fds, fd);

  timeval tv = {seconds, microseconds};
  int const code{select(fd + 1, &read_fds, &write_fds, &except_fds, &tv)};
#endif

  if (code == -1)
  {
    constexpr std::size_t buf_size{200u};
    std::array<char, buf_size> errbuf{};
    int const err_code{
#if defined(_WIN32) && (_WIN32_WINNT >= 0x0600)
      WSAGetLastError()
#else
      errno
#endif
    };
    throw std::runtime_error{pqxx::internal::error_string(err_code, errbuf)};
  }
}


PQXX_COLD void pqxx::internal::wait_for(unsigned int microseconds)
{
#if defined(PQXX_HAVE_SLEEP_FOR)
  std::this_thread::sleep_for(std::chrono::microseconds{microseconds});
#else
  // MinGW still does not have a functioning <thread> header.  Work around this
  // using select().
  // Not worth optimising for though -- they'll have to fix it at some point.
  // TODO: As of libpq 17, replace with PQsocketPoll()/PQgetCurrentTimeUSec()?
  timeval tv{microseconds / 1'000'000u, microseconds % 1'000'000u};
  select(0, nullptr, nullptr, nullptr, &tv);
#endif
}
