/*
 * Copyright © 2022 Red Hat, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting documentation, and
 * that the name of the copyright holders not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  The copyright holders make no representations
 * about the suitability of this software for any purpose.  It is provided "as
 * is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 */

#include <poll.h>
#include <errno.h>

#include "util/perf/cpu_trace.h"

#include "loader_wayland_helper.h"

#ifndef HAVE_WL_DISPATCH_QUEUE_TIMEOUT
static int
wl_display_poll(struct wl_display *display,
                short int events,
                const struct timespec *timeout)
{
   int ret;
   struct pollfd pfd[1];
   struct timespec now;
   struct timespec deadline = {0};
   struct timespec result;
   struct timespec *remaining_timeout = NULL;

   if (timeout) {
      clock_gettime(CLOCK_MONOTONIC, &now);
      timespec_add(&deadline, &now, timeout);
   }

   pfd[0].fd = wl_display_get_fd(display);
   pfd[0].events = events;
   do {
      if (timeout) {
         clock_gettime(CLOCK_MONOTONIC, &now);
         timespec_sub_saturate(&result, &deadline, &now);
         remaining_timeout = &result;
      }
      ret = ppoll(pfd, 1, remaining_timeout, NULL);
   } while (ret == -1 && errno == EINTR);

   return ret;
}

int
wl_display_dispatch_queue_timeout(struct wl_display *display,
                                  struct wl_event_queue *queue,
                                  const struct timespec *timeout)
{
   int ret;
   struct timespec now;
   struct timespec deadline = {0};
   struct timespec result;
   struct timespec *remaining_timeout = NULL;

   if (timeout) {
      clock_gettime(CLOCK_MONOTONIC, &now);
      timespec_add(&deadline, &now, timeout);
   }

   if (wl_display_prepare_read_queue(display, queue) == -1)
      return wl_display_dispatch_queue_pending(display, queue);

   while (true) {
      ret = wl_display_flush(display);

      if (ret != -1 || errno != EAGAIN)
         break;

      if (timeout) {
         clock_gettime(CLOCK_MONOTONIC, &now);
         timespec_sub_saturate(&result, &deadline, &now);
         remaining_timeout = &result;
      }
      ret = wl_display_poll(display, POLLOUT, remaining_timeout);

      if (ret <= 0) {
         wl_display_cancel_read(display);
         return ret;
      }
   }

   /* Don't stop if flushing hits an EPIPE; continue so we can read any
    * protocol error that may have triggered it. */
   if (ret < 0 && errno != EPIPE) {
      wl_display_cancel_read(display);
      return -1;
   }

   while (true) {
      if (timeout) {
         clock_gettime(CLOCK_MONOTONIC, &now);
         timespec_sub_saturate(&result, &deadline, &now);
         remaining_timeout = &result;
      }

      ret = wl_display_poll(display, POLLIN, remaining_timeout);
      if (ret <= 0) {
         wl_display_cancel_read(display);
         break;
      }

      ret = wl_display_read_events(display);
      if (ret == -1)
         break;

      ret = wl_display_dispatch_queue_pending(display, queue);
      if (ret != 0)
         break;

      /* wl_display_dispatch_queue_pending can return 0 if we ended up reading
       * from WL fd, but there was no complete event to dispatch yet.
       * Try reading again. */
      if (wl_display_prepare_read_queue(display, queue) == -1)
         return wl_display_dispatch_queue_pending(display, queue);
   }

   return ret;
}
#endif

#ifndef HAVE_WL_CREATE_QUEUE_WITH_NAME
struct wl_event_queue *
wl_display_create_queue_with_name(struct wl_display *display, const char *name)
{
   return wl_display_create_queue(display);
}
#endif

int
loader_wayland_dispatch(struct wl_display *wl_display,
                        struct wl_event_queue *queue,
                        struct timespec *end_time)
{
   struct timespec current_time;
   struct timespec remaining_timeout;

   MESA_TRACE_FUNC();

   if (!end_time)
      return wl_display_dispatch_queue(wl_display, queue);

   clock_gettime(CLOCK_MONOTONIC, &current_time);
   timespec_sub_saturate(&remaining_timeout, end_time, &current_time);
   return wl_display_dispatch_queue_timeout(wl_display,
                                            queue,
                                            &remaining_timeout);
}
