/* 
 Copyright (c) 2008, 2009 Apple 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 ABOVE LISTED COPYRIGHT
 HOLDER(S) 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.
 
 Except as contained in this notice, the name(s) of the above
 copyright holders shall not be used in advertising or otherwise to
 promote the sale, use or other dealings in this Software without
 prior written authorization.
*/

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <assert.h>
#include <pthread.h>

#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

// Get the newer glext.h first
#include <GL/gl.h>
#include <GL/glext.h>

#include <OpenGL/CGLTypes.h>
#include <OpenGL/CGLCurrent.h>
#include <OpenGL/OpenGL.h>

#include "glxclient.h"

#include "apple_glx.h"
#include "apple_glx_context.h"
#include "appledri.h"
#include "apple_visual.h"
#include "apple_cgl.h"
#include "apple_glx_drawable.h"

static pthread_mutex_t context_lock = PTHREAD_MUTEX_INITIALIZER;

/*
 * This should be locked on creation and destruction of the 
 * apple_glx_contexts.
 *
 * It's also locked when the surface_notify_handler is searching
 * for a uid associated with a surface.
 */
static struct apple_glx_context *context_list = NULL;

/* This guards the context_list above. */
static void
lock_context_list(void)
{
   int err;

   err = pthread_mutex_lock(&context_lock);

   if (err) {
      fprintf(stderr, "pthread_mutex_lock failure in %s: %d\n",
              __func__, err);
      abort();
   }
}

static void
unlock_context_list(void)
{
   int err;

   err = pthread_mutex_unlock(&context_lock);

   if (err) {
      fprintf(stderr, "pthread_mutex_unlock failure in %s: %d\n",
              __func__, err);
      abort();
   }
}

static bool
is_context_valid(struct apple_glx_context *ac)
{
   struct apple_glx_context *i;

   lock_context_list();

   for (i = context_list; i; i = i->next) {
      if (ac == i) {
         unlock_context_list();
         return true;
      }
   }

   unlock_context_list();

   return false;
}

/* This creates an apple_private_context struct.  
 *
 * It's typically called to save the struct in a GLXContext.
 *
 * This is also where the CGLContextObj is created, and the CGLPixelFormatObj.
 */
bool
apple_glx_create_context(void **ptr, Display * dpy, int screen,
                         const void *mode, void *sharedContext,
                         int *errorptr, bool * x11errorptr)
{
   struct apple_glx_context *ac;
   struct apple_glx_context *sharedac = sharedContext;
   CGLError error;

   *ptr = NULL;

   ac = malloc(sizeof *ac);

   if (NULL == ac) {
      *errorptr = BadAlloc;
      *x11errorptr = true;
      return true;
   }

   if (sharedac && !is_context_valid(sharedac)) {
      *errorptr = GLXBadContext;
      *x11errorptr = false;
      return true;
   }

   ac->context_obj = NULL;
   ac->pixel_format_obj = NULL;
   ac->drawable = NULL;
   ac->thread_id = pthread_self();
   ac->screen = screen;
   ac->double_buffered = false;
   ac->uses_stereo = false;
   ac->need_update = false;
   ac->is_current = false;
   ac->made_current = false;
   ac->last_surface_window = None;

   apple_visual_create_pfobj(&ac->pixel_format_obj, mode,
                             &ac->double_buffered, &ac->uses_stereo,
                             /*offscreen */ false);

   error = apple_cgl.create_context(ac->pixel_format_obj,
                                    sharedac ? sharedac->context_obj : NULL,
                                    &ac->context_obj);


   if (error) {
      (void) apple_cgl.destroy_pixel_format(ac->pixel_format_obj);

      free(ac);

      if (kCGLBadMatch == error) {
         *errorptr = BadMatch;
         *x11errorptr = true;
      }
      else {
         *errorptr = GLXBadContext;
         *x11errorptr = false;
      }

      if (getenv("LIBGL_DIAGNOSTIC"))
         fprintf(stderr, "error: %s\n", apple_cgl.error_string(error));

      return true;
   }

   /* The context creation succeeded, so we can link in the new context. */
   lock_context_list();

   if (context_list)
      context_list->previous = ac;

   ac->previous = NULL;
   ac->next = context_list;
   context_list = ac;

   *ptr = ac;

   apple_glx_diagnostic("%s: ac %p ac->context_obj %p\n",
                        __func__, (void *) ac, (void *) ac->context_obj);

   unlock_context_list();

   return false;
}

void
apple_glx_destroy_context(void **ptr, Display * dpy)
{
   struct apple_glx_context *ac = *ptr;

   if (NULL == ac)
      return;

   apple_glx_diagnostic("%s: ac %p ac->context_obj %p\n",
                        __func__, (void *) ac, (void *) ac->context_obj);

   if (apple_cgl.get_current_context() == ac->context_obj) {
      apple_glx_diagnostic("%s: context ac->context_obj %p "
                           "is still current!\n", __func__,
                           (void *) ac->context_obj);
      if (apple_cgl.set_current_context(NULL)) {
         abort();
      }
   }

   /* Remove ac from the context_list as soon as possible. */
   lock_context_list();

   if (ac->previous) {
      ac->previous->next = ac->next;
   }
   else {
      context_list = ac->next;
   }

   if (ac->next) {
      ac->next->previous = ac->previous;
   }

   unlock_context_list();


   if (apple_cgl.clear_drawable(ac->context_obj)) {
      fprintf(stderr, "error: while clearing drawable!\n");
      abort();
   }

   /*
    * This potentially causes surface_notify_handler to be called in
    * apple_glx.c... 
    * We can NOT have a lock held at this point.  It would result in 
    * an abort due to an attempted deadlock.  This is why we earlier
    * removed the ac pointer from the double-linked list.
    */
   if (ac->drawable) {
      ac->drawable->destroy(ac->drawable);
   }

   if (apple_cgl.destroy_pixel_format(ac->pixel_format_obj)) {
      fprintf(stderr, "error: destroying pixel format in %s\n", __func__);
      abort();
   }

   if (apple_cgl.destroy_context(ac->context_obj)) {
      fprintf(stderr, "error: destroying context_obj in %s\n", __func__);
      abort();
   }

   free(ac);

   *ptr = NULL;

   apple_glx_garbage_collect_drawables(dpy);
}


/* Return true if an error occured. */
bool
apple_glx_make_current_context(Display * dpy, void *oldptr, void *ptr,
                               GLXDrawable drawable)
{
   struct apple_glx_context *oldac = oldptr;
   struct apple_glx_context *ac = ptr;
   struct apple_glx_drawable *newagd = NULL;
   CGLError cglerr;
   bool same_drawable = false;

#if 0
   apple_glx_diagnostic("%s: oldac %p ac %p drawable 0x%lx\n",
                        __func__, (void *) oldac, (void *) ac, drawable);

   apple_glx_diagnostic("%s: oldac->context_obj %p ac->context_obj %p\n",
                        __func__,
                        (void *) (oldac ? oldac->context_obj : NULL),
                        (void *) (ac ? ac->context_obj : NULL));
#endif

   /* This a common path for GLUT and other apps, so special case it. */
   if (ac && ac->drawable && ac->drawable->drawable == drawable) {
      same_drawable = true;

      if (ac->is_current)
         return false;
   }

   /* Reset the is_current state of the old context, if non-NULL. */
   if (oldac && (ac != oldac))
      oldac->is_current = false;

   if (NULL == ac) {
      /*Clear the current context for this thread. */
      apple_cgl.set_current_context(NULL);

      if (oldac) {
         oldac->is_current = false;

         if (oldac->drawable) {
            oldac->drawable->destroy(oldac->drawable);
            oldac->drawable = NULL;
         }

         /* Invalidate this to prevent surface recreation. */
         oldac->last_surface_window = None;
      }

      return false;
   }

   if (None == drawable) {
      bool error = false;

      /* Clear the current drawable for this context_obj. */

      if (apple_cgl.set_current_context(ac->context_obj))
         error = true;

      if (apple_cgl.clear_drawable(ac->context_obj))
         error = true;

      if (ac->drawable) {
         ac->drawable->destroy(ac->drawable);
         ac->drawable = NULL;
      }

      /* Invalidate this to prevent surface recreation. */
      ac->last_surface_window = None;

      apple_glx_diagnostic("%s: drawable is None, error is: %d\n",
                           __func__, error);

      return error;
   }

   /* This is an optimisation to avoid searching for the current drawable. */
   if (ac->drawable && ac->drawable->drawable == drawable) {
      newagd = ac->drawable;
   }
   else {
      /* Find the drawable if possible, and retain a reference to it. */
      newagd =
         apple_glx_drawable_find(drawable, APPLE_GLX_DRAWABLE_REFERENCE);
   }

   /*
    * Try to destroy the old drawable, so long as the new one
    * isn't the old. 
    */
   if (ac->drawable && !same_drawable) {
      ac->drawable->destroy(ac->drawable);
      ac->drawable = NULL;
   }

   if (NULL == newagd) {
      if (apple_glx_surface_create(dpy, ac->screen, drawable, &newagd))
         return true;

      /* The drawable is referenced once by apple_glx_surface_create. */

      /*
       * FIXME: We actually need 2 references to prevent premature surface 
       * destruction.  The problem is that the surface gets destroyed in 
       * the case of the context being reused for another window, and
       * we then lose the surface contents.  Wait for destruction of a
       * window to destroy a surface.
       *
       * Note: this may leave around surfaces we don't want around, if
       * say we are using X for raster drawing after OpenGL rendering, 
       * but it will be compatible with the old libGL's behavior.
       *
       * Someday the X11 and OpenGL rendering must be unified at some
       * layer.  I suspect we can do that via shared memory and 
       * multiple threads in the X server (1 for each context created
       * by a client).  This would also allow users to render from 
       * multiple clients to the same OpenGL surface.  In fact it could
       * all be OpenGL.
       *
       */
      newagd->reference(newagd);

      /* Save the new drawable with the context structure. */
      ac->drawable = newagd;
   }
   else {
      /* We are reusing an existing drawable structure. */

      if (same_drawable) {
         assert(ac->drawable == newagd);
         /* The drawable_find above retained a reference for us. */
      }
      else {
         ac->drawable = newagd;
      }
   }

   /* 
    * Avoid this costly path if this is the same drawable and the
    * context is already current. 
    */

   if (same_drawable && ac->is_current) {
      apple_glx_diagnostic("%s: same_drawable and ac->is_current\n");
      return false;
   }

   cglerr = apple_cgl.set_current_context(ac->context_obj);

   if (kCGLNoError != cglerr) {
      fprintf(stderr, "set current error: %s\n",
              apple_cgl.error_string(cglerr));
      return true;
   }

   ac->is_current = true;

   assert(NULL != ac->context_obj);
   assert(NULL != ac->drawable);

   ac->thread_id = pthread_self();

   /* This will be set if the pending_destroy code indicates it should be: */
   ac->last_surface_window = None;

   switch (ac->drawable->type) {
   case APPLE_GLX_DRAWABLE_PBUFFER:
   case APPLE_GLX_DRAWABLE_SURFACE:
   case APPLE_GLX_DRAWABLE_PIXMAP:
      if (ac->drawable->callbacks.make_current) {
         if (ac->drawable->callbacks.make_current(ac, ac->drawable))
            return true;
      }
      break;

   default:
      fprintf(stderr, "internal error: invalid drawable type: %d\n",
              ac->drawable->type);
      abort();
   }

   return false;
}

bool
apple_glx_is_current_drawable(Display * dpy, void *ptr, GLXDrawable drawable)
{
   struct apple_glx_context *ac = ptr;

   if (ac->drawable && ac->drawable->drawable == drawable) {
      return true;
   }
   else if (NULL == ac->drawable && None != ac->last_surface_window) {
      apple_glx_context_update(dpy, ac);

      return (ac->drawable && ac->drawable->drawable == drawable);
   }

   return false;
}

bool
apple_glx_copy_context(void *currentptr, void *srcptr, void *destptr,
                       unsigned long mask, int *errorptr, bool * x11errorptr)
{
   struct apple_glx_context *src, *dest;
   CGLError err;

   src = srcptr;
   dest = destptr;

   if (src->screen != dest->screen) {
      *errorptr = BadMatch;
      *x11errorptr = true;
      return true;
   }

   if (dest == currentptr || dest->is_current) {
      *errorptr = BadAccess;
      *x11errorptr = true;
      return true;
   }

   /* 
    * If srcptr is the current context then we should do an implicit glFlush.
    */
   if (currentptr == srcptr)
      glFlush();

   err = apple_cgl.copy_context(src->context_obj, dest->context_obj,
                                (GLbitfield) mask);

   if (kCGLNoError != err) {
      *errorptr = GLXBadContext;
      *x11errorptr = false;
      return true;
   }

   return false;
}

/* 
 * The value returned is the total number of contexts set to update. 
 * It's meant for debugging/introspection.
 */
int
apple_glx_context_surface_changed(unsigned int uid, pthread_t caller)
{
   struct apple_glx_context *ac;
   int updated = 0;

   lock_context_list();

   for (ac = context_list; ac; ac = ac->next) {
      if (ac->drawable && APPLE_GLX_DRAWABLE_SURFACE == ac->drawable->type
          && ac->drawable->types.surface.uid == uid) {

         if (caller == ac->thread_id) {
            apple_glx_diagnostic("caller is the same thread for uid %u\n",
                                 uid);

            xp_update_gl_context(ac->context_obj);
         }
         else {
            ac->need_update = true;
            ++updated;
         }
      }
   }

   unlock_context_list();

   return updated;
}

void
apple_glx_context_update(Display * dpy, void *ptr)
{
   struct apple_glx_context *ac = ptr;

   if (NULL == ac->drawable && None != ac->last_surface_window) {
      bool failed;

      /* Attempt to recreate the surface for a destroyed drawable. */
      failed =
         apple_glx_make_current_context(dpy, ac, ac, ac->last_surface_window);

      apple_glx_diagnostic("%s: surface recreation failed? %s\n", __func__,
                           failed ? "YES" : "NO");
   }

   if (ac->need_update) {
      xp_update_gl_context(ac->context_obj);
      ac->need_update = false;

      apple_glx_diagnostic("%s: updating context %p\n", __func__, ptr);
   }

   if (ac->drawable && APPLE_GLX_DRAWABLE_SURFACE == ac->drawable->type
       && ac->drawable->types.surface.pending_destroy) {
      apple_glx_diagnostic("%s: clearing drawable %p\n", __func__, ptr);
      apple_cgl.clear_drawable(ac->context_obj);

      if (ac->drawable) {
         struct apple_glx_drawable *d;

         apple_glx_diagnostic("%s: attempting to destroy drawable %p\n",
                              __func__, ptr);
         apple_glx_diagnostic("%s: ac->drawable->drawable is 0x%lx\n",
                              __func__, ac->drawable->drawable);

         d = ac->drawable;

         ac->last_surface_window = d->drawable;

         ac->drawable = NULL;

         /* 
          * This will destroy the surface drawable if there are 
          * no references to it.  
          * It also subtracts 1 from the reference_count.
          * If there are references to it, then it's probably made
          * current in another context.
          */
         d->destroy(d);
      }
   }
}

bool
apple_glx_context_uses_stereo(void *ptr)
{
   struct apple_glx_context *ac = ptr;

   return ac->uses_stereo;
}
