/*
 * 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_string.h"
#include "util/u_inlines.h"
#include "util/u_pointer.h"
#include "util/u_dl.h"
#include "egldriver.h"
#include "eglimage.h"
#include "eglmutex.h"

#include "egl_g3d.h"
#include "egl_g3d_st.h"

struct egl_g3d_st_manager {
   struct st_manager base;
   _EGLDisplay *display;
};

static INLINE struct egl_g3d_st_manager *
egl_g3d_st_manager(struct st_manager *smapi)
{
   return (struct egl_g3d_st_manager *) smapi;
}

static boolean
egl_g3d_st_manager_get_egl_image(struct st_manager *smapi,
                                 void *egl_image,
                                 struct st_egl_image *out)
{
   struct egl_g3d_st_manager *gsmapi = egl_g3d_st_manager(smapi);
   EGLImageKHR handle = (EGLImageKHR) egl_image;
   _EGLImage *img;
   struct egl_g3d_image *gimg;

   /* this is called from state trackers */
   _eglLockMutex(&gsmapi->display->Mutex);

   img = _eglLookupImage(handle, gsmapi->display);
   if (!img) {
      _eglUnlockMutex(&gsmapi->display->Mutex);
      return FALSE;
   }

   gimg = egl_g3d_image(img);

   out->texture = NULL;
   pipe_resource_reference(&out->texture, gimg->texture);
   out->level = gimg->level;
   out->layer = gimg->layer;

   _eglUnlockMutex(&gsmapi->display->Mutex);

   return TRUE;
}

static int
egl_g3d_st_manager_get_param(struct st_manager *smapi,
                             enum st_manager_param param)
{
   return 0;
}

struct st_manager *
egl_g3d_create_st_manager(_EGLDisplay *dpy)
{
   struct egl_g3d_display *gdpy = egl_g3d_display(dpy);
   struct egl_g3d_st_manager *gsmapi;

   gsmapi = CALLOC_STRUCT(egl_g3d_st_manager);
   if (gsmapi) {
      gsmapi->display = dpy;

      gsmapi->base.screen = gdpy->native->screen;
      gsmapi->base.get_egl_image = egl_g3d_st_manager_get_egl_image;
      gsmapi->base.get_param = egl_g3d_st_manager_get_param;
   }

   return &gsmapi->base;;
}

void
egl_g3d_destroy_st_manager(struct st_manager *smapi)
{
   struct egl_g3d_st_manager *gsmapi = egl_g3d_st_manager(smapi);
   FREE(gsmapi);
}

static boolean
egl_g3d_st_framebuffer_flush_front_pbuffer(struct st_context_iface *stctx,
                                           struct st_framebuffer_iface *stfbi,
                                           enum st_attachment_type statt)
{
   return TRUE;
}

static void
pbuffer_reference_openvg_image(struct egl_g3d_surface *gsurf)
{
   /* TODO */
}

static void
pbuffer_allocate_pbuffer_texture(struct egl_g3d_surface *gsurf)
{
   struct egl_g3d_display *gdpy =
      egl_g3d_display(gsurf->base.Resource.Display);
   struct pipe_screen *screen = gdpy->native->screen;
   struct pipe_resource templ, *ptex;

   memset(&templ, 0, sizeof(templ));
   templ.target = PIPE_TEXTURE_2D;
   templ.last_level = 0;
   templ.width0 = gsurf->base.Width;
   templ.height0 = gsurf->base.Height;
   templ.depth0 = 1;
   templ.array_size = 1;
   templ.format = gsurf->stvis.color_format;
   /* for rendering and binding to texture */
   templ.bind = PIPE_BIND_RENDER_TARGET | PIPE_BIND_SAMPLER_VIEW;

   ptex = screen->resource_create(screen, &templ);
   gsurf->render_texture = ptex;
}

static boolean
egl_g3d_st_framebuffer_validate_pbuffer(struct st_context_iface *stctx,
                                        struct st_framebuffer_iface *stfbi,
                                        const enum st_attachment_type *statts,
                                        unsigned count,
                                        struct pipe_resource **out)
{
   _EGLSurface *surf = (_EGLSurface *) stfbi->st_manager_private;
   struct egl_g3d_surface *gsurf = egl_g3d_surface(surf);
   unsigned i;

   for (i = 0; i < count; i++) {
      out[i] = NULL;

      if (gsurf->stvis.render_buffer != statts[i])
         continue;

      if (!gsurf->render_texture) {
         switch (gsurf->client_buffer_type) {
         case EGL_NONE:
            pbuffer_allocate_pbuffer_texture(gsurf);
            break;
         case EGL_OPENVG_IMAGE:
            pbuffer_reference_openvg_image(gsurf);
            break;
         default:
            break;
         }

         if (!gsurf->render_texture)
            return FALSE;
      }

      pipe_resource_reference(&out[i], gsurf->render_texture);
   }

   return TRUE;
}

static boolean
egl_g3d_st_framebuffer_flush_front(struct st_context_iface *stctx,
                                   struct st_framebuffer_iface *stfbi,
                                   enum st_attachment_type statt)
{
   _EGLSurface *surf = (_EGLSurface *) stfbi->st_manager_private;
   struct egl_g3d_surface *gsurf = egl_g3d_surface(surf);
   struct native_present_control ctrl;

   memset(&ctrl, 0, sizeof(ctrl));
   ctrl.natt = NATIVE_ATTACHMENT_FRONT_LEFT;

   return gsurf->native->present(gsurf->native, &ctrl);
}

static boolean 
egl_g3d_st_framebuffer_validate(struct st_context_iface *stctx,
                                struct st_framebuffer_iface *stfbi,
                                const enum st_attachment_type *statts,
                                unsigned count,
                                struct pipe_resource **out)
{
   _EGLSurface *surf = (_EGLSurface *) stfbi->st_manager_private;
   struct egl_g3d_surface *gsurf = egl_g3d_surface(surf);
   struct pipe_resource *textures[NUM_NATIVE_ATTACHMENTS];
   uint attachment_mask = 0;
   unsigned i;

   for (i = 0; i < count; i++) {
      int natt;

      switch (statts[i]) {
      case ST_ATTACHMENT_FRONT_LEFT:
         natt = NATIVE_ATTACHMENT_FRONT_LEFT;
         break;
      case ST_ATTACHMENT_BACK_LEFT:
         natt = NATIVE_ATTACHMENT_BACK_LEFT;
         break;
      case ST_ATTACHMENT_FRONT_RIGHT:
         natt = NATIVE_ATTACHMENT_FRONT_RIGHT;
         break;
      case ST_ATTACHMENT_BACK_RIGHT:
         natt = NATIVE_ATTACHMENT_BACK_RIGHT;
         break;
      default:
         natt = -1;
         break;
      }

      if (natt >= 0)
         attachment_mask |= 1 << natt;
   }

   if (!gsurf->native->validate(gsurf->native, attachment_mask,
         &gsurf->sequence_number, textures, &gsurf->base.Width,
         &gsurf->base.Height))
      return FALSE;

   for (i = 0; i < count; i++) {
      struct pipe_resource *tex;
      int natt;

      switch (statts[i]) {
      case ST_ATTACHMENT_FRONT_LEFT:
         natt = NATIVE_ATTACHMENT_FRONT_LEFT;
         break;
      case ST_ATTACHMENT_BACK_LEFT:
         natt = NATIVE_ATTACHMENT_BACK_LEFT;
         break;
      case ST_ATTACHMENT_FRONT_RIGHT:
         natt = NATIVE_ATTACHMENT_FRONT_RIGHT;
         break;
      case ST_ATTACHMENT_BACK_RIGHT:
         natt = NATIVE_ATTACHMENT_BACK_RIGHT;
         break;
      default:
         natt = -1;
         break;
      }

      if (natt >= 0) {
         tex = textures[natt];

         if (statts[i] == stfbi->visual->render_buffer)
            pipe_resource_reference(&gsurf->render_texture, tex);

         if (attachment_mask & (1 << natt)) {
            /* transfer the ownership to the caller */
            out[i] = tex;
            attachment_mask &= ~(1 << natt);
         }
         else {
            /* the attachment is listed more than once */
            pipe_resource_reference(&out[i], tex);
         }
      }
   }

   return TRUE;
}

struct st_framebuffer_iface *
egl_g3d_create_st_framebuffer(_EGLSurface *surf)
{
   struct egl_g3d_surface *gsurf = egl_g3d_surface(surf);
   struct st_framebuffer_iface *stfbi;

   stfbi = CALLOC_STRUCT(st_framebuffer_iface);
   if (!stfbi)
      return NULL;

   stfbi->visual = &gsurf->stvis;
   p_atomic_set(&stfbi->stamp, 1);

   if (gsurf->base.Type != EGL_PBUFFER_BIT) {
      stfbi->flush_front = egl_g3d_st_framebuffer_flush_front;
      stfbi->validate = egl_g3d_st_framebuffer_validate;
   }
   else {
      stfbi->flush_front = egl_g3d_st_framebuffer_flush_front_pbuffer;
      stfbi->validate = egl_g3d_st_framebuffer_validate_pbuffer;
   }
   stfbi->st_manager_private = (void *) &gsurf->base;

   return stfbi;
}

void
egl_g3d_destroy_st_framebuffer(struct st_framebuffer_iface *stfbi)
{
   FREE(stfbi);
}
