/*
 * Mesa 3-D graphics library
 *
 * Copyright (C) 2010 LunarG Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Authors:
 *    Chia-I Wu <olv@lunarg.com>
 */

#include "util/u_memory.h"
#include "util/u_atomic.h"
#include "os/os_thread.h"
#include "eglsync.h"
#include "eglcurrent.h"

#include "egl_g3d.h"
#include "egl_g3d_sync.h"

/**
 * Wait for the conditional variable.
 */
static EGLint
egl_g3d_wait_sync_condvar(struct egl_g3d_sync *gsync, EGLTimeKHR timeout)
{
   _EGLDisplay *dpy = gsync->base.Resource.Display;

   pipe_mutex_lock(gsync->mutex);

   /* unlock display lock just before waiting */
   _eglUnlockMutex(&dpy->Mutex);

   /* No timed wait.  Always treat timeout as EGL_FOREVER_KHR */
   pipe_condvar_wait(gsync->condvar, gsync->mutex);

   _eglLockMutex(&dpy->Mutex);

   pipe_mutex_unlock(gsync->mutex);

   return EGL_CONDITION_SATISFIED_KHR;
}

/**
 * Signal the conditional variable.
 */
static void
egl_g3d_signal_sync_condvar(struct egl_g3d_sync *gsync)
{
   pipe_mutex_lock(gsync->mutex);
   pipe_condvar_broadcast(gsync->condvar);
   pipe_mutex_unlock(gsync->mutex);
}

/**
 * Insert a fence command to the command stream of the current context.
 */
static EGLint
egl_g3d_insert_fence_sync(struct egl_g3d_sync *gsync)
{
   _EGLContext *ctx = _eglGetCurrentContext();
   struct egl_g3d_context *gctx = egl_g3d_context(ctx);

   /* already checked in egl_g3d_create_sync */
   assert(gctx);

   /* insert the fence command */
   gctx->stctxi->flush(gctx->stctxi, 0x0, &gsync->fence);
   if (!gsync->fence)
      gsync->base.SyncStatus = EGL_SIGNALED_KHR;

   return EGL_SUCCESS;
}

/**
 * Wait for the fence sync to be signaled.
 */
static EGLint
egl_g3d_wait_fence_sync(struct egl_g3d_sync *gsync, EGLTimeKHR timeout)
{
   EGLint ret;

   if (gsync->fence) {
      _EGLDisplay *dpy = gsync->base.Resource.Display;
      struct egl_g3d_display *gdpy = egl_g3d_display(dpy);
      struct pipe_screen *screen = gdpy->native->screen;
      struct pipe_fence_handle *fence = gsync->fence;

      gsync->fence = NULL;

      _eglUnlockMutex(&dpy->Mutex);
      /* no timed finish? */
      screen->fence_finish(screen, fence, PIPE_TIMEOUT_INFINITE);
      ret = EGL_CONDITION_SATISFIED_KHR;
      _eglLockMutex(&dpy->Mutex);

      gsync->base.SyncStatus = EGL_SIGNALED_KHR;

      screen->fence_reference(screen, &fence, NULL);
      egl_g3d_signal_sync_condvar(gsync);
   }
   else {
      ret = egl_g3d_wait_sync_condvar(gsync, timeout);
   }

   return ret;
}

static INLINE void
egl_g3d_ref_sync(struct egl_g3d_sync *gsync)
{
   _eglGetSync(&gsync->base);
}

static INLINE void
egl_g3d_unref_sync(struct egl_g3d_sync *gsync)
{
   if (_eglPutSync(&gsync->base)) {
      pipe_condvar_destroy(gsync->condvar);
      pipe_mutex_destroy(gsync->mutex);

      if (gsync->fence) {
         struct egl_g3d_display *gdpy =
            egl_g3d_display(gsync->base.Resource.Display);
         struct pipe_screen *screen = gdpy->native->screen;

         screen->fence_reference(screen, &gsync->fence, NULL);
      }

      FREE(gsync);
   }
}

_EGLSync *
egl_g3d_create_sync(_EGLDriver *drv, _EGLDisplay *dpy,
                    EGLenum type, const EGLint *attrib_list)
{
   _EGLContext *ctx = _eglGetCurrentContext();
   struct egl_g3d_sync *gsync;
   EGLint err;

   if (!ctx || ctx->Resource.Display != dpy) {
      _eglError(EGL_BAD_MATCH, "eglCreateSyncKHR");
      return NULL;
   }

   gsync = CALLOC_STRUCT(egl_g3d_sync);
   if (!gsync) {
      _eglError(EGL_BAD_ALLOC, "eglCreateSyncKHR");
      return NULL;
   }

   if (!_eglInitSync(&gsync->base, dpy, type, attrib_list)) {
      FREE(gsync);
      return NULL;
   }

   switch (type) {
   case EGL_SYNC_REUSABLE_KHR:
      err = EGL_SUCCESS;
      break;
   case EGL_SYNC_FENCE_KHR:
      err = egl_g3d_insert_fence_sync(gsync);
      break;
   default:
      err = EGL_BAD_ATTRIBUTE;
      break;
   }

   if (err != EGL_SUCCESS) {
      _eglError(err, "eglCreateSyncKHR");
      FREE(gsync);
      return NULL;
   }

   pipe_mutex_init(gsync->mutex);
   pipe_condvar_init(gsync->condvar);

   return &gsync->base;
}

EGLBoolean
egl_g3d_destroy_sync(_EGLDriver *drv, _EGLDisplay *dpy, _EGLSync *sync)
{
   struct egl_g3d_sync *gsync = egl_g3d_sync(sync);

   switch (gsync->base.Type) {
   case EGL_SYNC_REUSABLE_KHR:
      /* signal the waiters */
      if (gsync->base.SyncStatus != EGL_SIGNALED_KHR) {
         gsync->base.SyncStatus = EGL_SIGNALED_KHR;
         egl_g3d_signal_sync_condvar(gsync);
      }
      break;
   default:
      break;
   }

   egl_g3d_unref_sync(gsync);

   return EGL_TRUE;
}

EGLint
egl_g3d_client_wait_sync(_EGLDriver *drv, _EGLDisplay *dpy, _EGLSync *sync,
                         EGLint flags, EGLTimeKHR timeout)
{
   struct egl_g3d_sync *gsync = egl_g3d_sync(sync);
   EGLint ret = EGL_CONDITION_SATISFIED_KHR;

   if (gsync->base.SyncStatus != EGL_SIGNALED_KHR) {
      /* flush if there is a current context */
      if (flags & EGL_SYNC_FLUSH_COMMANDS_BIT_KHR) {
         _EGLContext *ctx = _eglGetCurrentContext();
         struct egl_g3d_context *gctx = egl_g3d_context(ctx);

         if (gctx)
            gctx->stctxi->flush(gctx->stctxi, ST_FLUSH_FRONT, NULL);
      }

      if (timeout) {
         /* reference the sync object in case it is destroyed while waiting */
         egl_g3d_ref_sync(gsync);

         switch (gsync->base.Type) {
         case EGL_SYNC_REUSABLE_KHR:
            ret = egl_g3d_wait_sync_condvar(gsync, timeout);
            break;
         case EGL_SYNC_FENCE_KHR:
            ret = egl_g3d_wait_fence_sync(gsync, timeout);
         default:
            break;
         }

         egl_g3d_unref_sync(gsync);
      }
      else {
         ret = EGL_TIMEOUT_EXPIRED_KHR;
      }
   }

   return ret;
}

EGLBoolean
egl_g3d_signal_sync(_EGLDriver *drv, _EGLDisplay *dpy, _EGLSync *sync,
                    EGLenum mode)
{
   struct egl_g3d_sync *gsync = egl_g3d_sync(sync);

   /* only for reusable sync */
   if (sync->Type != EGL_SYNC_REUSABLE_KHR)
      return _eglError(EGL_BAD_MATCH, "eglSignalSyncKHR");

   if (gsync->base.SyncStatus != mode) {
      gsync->base.SyncStatus = mode;
      if (mode == EGL_SIGNALED_KHR)
         egl_g3d_signal_sync_condvar(gsync);
   }

   return EGL_TRUE;
}
