/**************************************************************************
 *
 * Copyright 2009, VMware, Inc.
 * All Rights Reserved.
 * Copyright 2010 George Sapountzis <gsapountzis@gmail.com>
 *
 * 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, sub license, 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 (including the
 * next paragraph) 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 NON-INFRINGEMENT.
 * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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.
 *
 **************************************************************************/

#include "pipe/p_compiler.h"
#include "pipe/p_format.h"
#include "util/u_inlines.h"
#include "util/u_format.h"
#include "util/u_math.h"
#include "util/u_memory.h"

#include "state_tracker/sw_winsys.h"
#include "state_tracker/drm_driver.h"
#include "dri_sw_winsys.h"

#ifdef HAVE_ANDROID_PLATFORM
#include <system/graphics.h>
#include <system/window.h>
#include <hardware/gralloc.h>
#endif

struct dri_sw_displaytarget
{
   enum pipe_format format;
   unsigned width;
   unsigned height;
   unsigned stride;

   unsigned map_flags;
#ifdef HAVE_ANDROID_PLATFORM
   struct ANativeWindowBuffer *androidBuffer;
#endif
   void *data;
   void *mapped;
   const void *front_private;
};

#ifdef HAVE_ANDROID_PLATFORM
const struct gralloc_module_t* get_gralloc()
{
   static const struct gralloc_module_t* gr_module = NULL;
   const hw_module_t *mod;
   int err;

   if (!gr_module) {
      err =  hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &mod);
      if (!err) {
         gr_module = (gralloc_module_t *) mod;
      }
   }
   return gr_module;
}
#endif

struct dri_sw_winsys
{
   struct sw_winsys base;

   struct drisw_loader_funcs *lf;
};

static inline struct dri_sw_displaytarget *
dri_sw_displaytarget( struct sw_displaytarget *dt )
{
   return (struct dri_sw_displaytarget *)dt;
}

static inline struct dri_sw_winsys *
dri_sw_winsys( struct sw_winsys *ws )
{
   return (struct dri_sw_winsys *)ws;
}


static boolean
dri_sw_is_displaytarget_format_supported( struct sw_winsys *ws,
                                          unsigned tex_usage,
                                          enum pipe_format format )
{
   /* TODO: check visuals or other sensible thing here */
   return TRUE;
}

static struct sw_displaytarget *
dri_sw_displaytarget_create(struct sw_winsys *winsys,
                            unsigned tex_usage,
                            enum pipe_format format,
                            unsigned width, unsigned height,
                            unsigned alignment,
                            const void *front_private,
                            unsigned *stride)
{
   struct dri_sw_displaytarget *dri_sw_dt;
   unsigned nblocksy, size, format_stride;

   dri_sw_dt = CALLOC_STRUCT(dri_sw_displaytarget);
   if(!dri_sw_dt)
      goto no_dt;

   dri_sw_dt->format = format;
   dri_sw_dt->width = width;
   dri_sw_dt->height = height;
   dri_sw_dt->front_private = front_private;

   format_stride = util_format_get_stride(format, width);
   dri_sw_dt->stride = align(format_stride, alignment);

   nblocksy = util_format_get_nblocksy(format, height);
   size = dri_sw_dt->stride * nblocksy;

   dri_sw_dt->data = align_malloc(size, alignment);
   if(!dri_sw_dt->data)
      goto no_data;

   *stride = dri_sw_dt->stride;
   return (struct sw_displaytarget *)dri_sw_dt;

no_data:
   FREE(dri_sw_dt);
no_dt:
   return NULL;
}

static void
dri_sw_displaytarget_destroy(struct sw_winsys *ws,
                             struct sw_displaytarget *dt)
{
   struct dri_sw_displaytarget *dri_sw_dt = dri_sw_displaytarget(dt);

#ifdef HAVE_ANDROID_PLATFORM
   if (dri_sw_dt->androidBuffer) {
      dri_sw_dt->androidBuffer->common.decRef(&dri_sw_dt->androidBuffer->common);
   }
#endif

   align_free(dri_sw_dt->data);

   FREE(dri_sw_dt);
}

static void *
dri_sw_displaytarget_map(struct sw_winsys *ws,
                         struct sw_displaytarget *dt,
                         unsigned flags)
{
   struct dri_sw_displaytarget *dri_sw_dt = dri_sw_displaytarget(dt);
#ifdef HAVE_ANDROID_PLATFORM
   if (dri_sw_dt->androidBuffer) {
      if (!get_gralloc()->lock(get_gralloc(), dri_sw_dt->androidBuffer->handle,
                              GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
                              0, 0, dri_sw_dt->androidBuffer->width, dri_sw_dt->androidBuffer->height,
                              (void**)&dri_sw_dt->mapped)) {
         dri_sw_dt->map_flags = flags;
         return dri_sw_dt->mapped;
      }
   }
#endif
   dri_sw_dt->mapped = dri_sw_dt->data;

   if (dri_sw_dt->front_private && (flags & PIPE_TRANSFER_READ)) {
      struct dri_sw_winsys *dri_sw_ws = dri_sw_winsys(ws);
      dri_sw_ws->lf->get_image((void *)dri_sw_dt->front_private, 0, 0, dri_sw_dt->width, dri_sw_dt->height, dri_sw_dt->stride, dri_sw_dt->data);
   }
   dri_sw_dt->map_flags = flags;
   return dri_sw_dt->mapped;
}

static void
dri_sw_displaytarget_unmap(struct sw_winsys *ws,
                           struct sw_displaytarget *dt)
{
   struct dri_sw_displaytarget *dri_sw_dt = dri_sw_displaytarget(dt);
   if (dri_sw_dt->front_private && (dri_sw_dt->map_flags & PIPE_TRANSFER_WRITE)) {
      struct dri_sw_winsys *dri_sw_ws = dri_sw_winsys(ws);
      dri_sw_ws->lf->put_image2((void *)dri_sw_dt->front_private, dri_sw_dt->data, 0, 0, dri_sw_dt->width, dri_sw_dt->height, dri_sw_dt->stride);
   }
   dri_sw_dt->map_flags = 0;
#ifdef HAVE_ANDROID_PLATFORM
   if (dri_sw_dt->androidBuffer) {
      get_gralloc()->unlock(get_gralloc(), dri_sw_dt->androidBuffer->handle);
   }
#endif
   dri_sw_dt->mapped = NULL;
}

static struct sw_displaytarget *
dri_sw_displaytarget_from_handle(struct sw_winsys *winsys,
                                 const struct pipe_resource *templ,
                                 struct winsys_handle *whandle,
                                 unsigned *stride)
{
#ifdef HAVE_ANDROID_PLATFORM
   struct dri_sw_displaytarget *dri_sw_dt;

   if (whandle->type == DRM_API_HANDLE_TYPE_BUFFER) {
      dri_sw_dt = CALLOC_STRUCT(dri_sw_displaytarget);
      dri_sw_dt->width = templ->width0;
      dri_sw_dt->height = templ->height0;
      dri_sw_dt->androidBuffer = whandle->externalBuffer;
      dri_sw_dt->stride = whandle->stride;

      dri_sw_dt->androidBuffer->common.incRef(&dri_sw_dt->androidBuffer->common);
      *stride = dri_sw_dt->stride;

      return dri_sw_dt;
   }
#endif
   assert(0);
   return NULL;
}

static boolean
dri_sw_displaytarget_get_handle(struct sw_winsys *winsys,
                                struct sw_displaytarget *dt,
                                struct winsys_handle *whandle)
{
   assert(0);
   return FALSE;
}

static void
dri_sw_displaytarget_display(struct sw_winsys *ws,
                             struct sw_displaytarget *dt,
                             void *context_private,
                             struct pipe_box *box)
{
   struct dri_sw_winsys *dri_sw_ws = dri_sw_winsys(ws);
   struct dri_sw_displaytarget *dri_sw_dt = dri_sw_displaytarget(dt);
   struct dri_drawable *dri_drawable = (struct dri_drawable *)context_private;
   unsigned width, height;
   unsigned blsize = util_format_get_blocksize(dri_sw_dt->format);

   /* Set the width to 'stride / cpp'.
    *
    * PutImage correctly clips to the width of the dst drawable.
    */
   width = dri_sw_dt->stride / blsize;

   height = dri_sw_dt->height;

   if (box) {
       void *data;
       data = dri_sw_dt->data + (dri_sw_dt->stride * box->y) + box->x * blsize;
       dri_sw_ws->lf->put_image2(dri_drawable, data,
                                 box->x, box->y, box->width, box->height, dri_sw_dt->stride);
   } else {
       dri_sw_ws->lf->put_image(dri_drawable, dri_sw_dt->data, width, height);
   }
}

static void
dri_destroy_sw_winsys(struct sw_winsys *winsys)
{
   FREE(winsys);
}

struct sw_winsys *
dri_create_sw_winsys(struct drisw_loader_funcs *lf)
{
   struct dri_sw_winsys *ws;

   ws = CALLOC_STRUCT(dri_sw_winsys);
   if (!ws)
      return NULL;

   ws->lf = lf;
   ws->base.destroy = dri_destroy_sw_winsys;

   ws->base.is_displaytarget_format_supported = dri_sw_is_displaytarget_format_supported;

   /* screen texture functions */
   ws->base.displaytarget_create = dri_sw_displaytarget_create;
   ws->base.displaytarget_destroy = dri_sw_displaytarget_destroy;
   ws->base.displaytarget_from_handle = dri_sw_displaytarget_from_handle;
   ws->base.displaytarget_get_handle = dri_sw_displaytarget_get_handle;

   /* texture functions */
   ws->base.displaytarget_map = dri_sw_displaytarget_map;
   ws->base.displaytarget_unmap = dri_sw_displaytarget_unmap;

   ws->base.displaytarget_display = dri_sw_displaytarget_display;

   return &ws->base;
}

/* vim: set sw=3 ts=8 sts=3 expandtab: */
