/**************************************************************************
 *
 * Copyright 2009 VMware, Inc.  All Rights Reserved.
 *
 * 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 "vg_context.h"
#include "vg_tracker.h"
#include "mask.h"

#include "pipe/p_context.h"
#include "pipe/p_inlines.h"
#include "pipe/p_screen.h"
#include "util/u_memory.h"
#include "util/u_math.h"
#include "util/u_rect.h"

static struct pipe_texture *
create_texture(struct pipe_context *pipe, enum pipe_format format,
               VGint width, VGint height)
{
   struct pipe_texture templ;

   memset(&templ, 0, sizeof(templ));

   if (format != PIPE_FORMAT_NONE) {
      templ.format = format;
   }
   else {
      templ.format = PIPE_FORMAT_A8R8G8B8_UNORM;
   }

   templ.target = PIPE_TEXTURE_2D;
   pf_get_block(templ.format, &templ.block);
   templ.width[0] = width;
   templ.height[0] = height;
   templ.depth[0] = 1;
   templ.last_level = 0;

   if (pf_get_component_bits(format, PIPE_FORMAT_COMP_S)) {
      templ.tex_usage = PIPE_TEXTURE_USAGE_DEPTH_STENCIL;
   } else {
      templ.tex_usage = (PIPE_TEXTURE_USAGE_DISPLAY_TARGET |
                         PIPE_TEXTURE_USAGE_RENDER_TARGET |
                         PIPE_TEXTURE_USAGE_SAMPLER);
   }

   return pipe->screen->texture_create(pipe->screen, &templ);
}

/**
 * Allocate a renderbuffer for a an on-screen window (not a user-created
 * renderbuffer).  The window system code determines the format.
 */
static struct st_renderbuffer *
st_new_renderbuffer_fb(enum pipe_format format)
{
   struct st_renderbuffer *strb;

   strb = CALLOC_STRUCT(st_renderbuffer);
   if (!strb) {
      /*_vega_error(NULL, VG_OUT_OF_MEMORY, "creating renderbuffer");*/
      return NULL;
   }

   strb->format = format;

   return strb;
}


/**
 * This is called to allocate the original drawing surface, and
 * during window resize.
 */
static VGboolean
st_renderbuffer_alloc_storage(struct vg_context * ctx,
                              struct st_renderbuffer *strb,
                              VGuint width, VGuint height)
{
   struct pipe_context *pipe = ctx->pipe;
   unsigned surface_usage;

   /* Free the old surface and texture
    */
   pipe_surface_reference(&strb->surface, NULL);
   pipe_texture_reference(&strb->texture, NULL);


   /* Probably need dedicated flags for surface usage too:
    */
   surface_usage = (PIPE_BUFFER_USAGE_GPU_READ  |
                    PIPE_BUFFER_USAGE_GPU_WRITE);

   strb->texture = create_texture(pipe, strb->format,
                                  width, height);

   if (!strb->texture)
      return FALSE;

   strb->surface = pipe->screen->get_tex_surface(pipe->screen,
                                                 strb->texture,
                                                 0, 0, 0,
                                                 surface_usage);
   strb->width = width;
   strb->height = height;

   assert(strb->surface->width == width);
   assert(strb->surface->height == height);

   return strb->surface != NULL;
}

struct vg_context * st_create_context(struct pipe_context *pipe,
                                      const void *visual,
                                      struct vg_context *share)
{
   struct vg_context *ctx = vg_create_context(pipe, visual, share);
   /*debug_printf("--------- CREATE CONTEXT %p\n", ctx);*/
   return ctx;
}

void st_destroy_context(struct vg_context *st)
{
   /*debug_printf("--------- DESTROY CONTEXT %p\n", st);*/
   vg_destroy_context(st);
}

void st_copy_context_state(struct vg_context *dst, struct vg_context *src,
                           uint mask)
{
   fprintf(stderr, "FIXME: %s\n", __FUNCTION__);
}

void st_get_framebuffer_dimensions(struct st_framebuffer *stfb,
				   uint *width,
				   uint *height)
{
   *width = stfb->strb->width;
   *height = stfb->strb->height;
}

struct st_framebuffer * st_create_framebuffer(const void *visual,
                                              enum pipe_format colorFormat,
                                              enum pipe_format depthFormat,
                                              enum pipe_format stencilFormat,
                                              uint width, uint height,
                                              void *privateData)
{
   struct st_framebuffer *stfb = CALLOC_STRUCT(st_framebuffer);
   if (stfb) {
      struct st_renderbuffer *rb =
         st_new_renderbuffer_fb(colorFormat);
      stfb->strb = rb;
#if 0
      if (doubleBuffer) {
         struct st_renderbuffer *rb =
            st_new_renderbuffer_fb(colorFormat);
      }
#endif

      /* we want to combine the depth/stencil */
      if (stencilFormat == depthFormat)
         stfb->dsrb = st_new_renderbuffer_fb(stencilFormat);
      else
         stfb->dsrb = st_new_renderbuffer_fb(PIPE_FORMAT_S8Z24_UNORM);

      /*### currently we always allocate it but it's possible it's
        not necessary if EGL_ALPHA_MASK_SIZE was 0
      */
      stfb->alpha_mask = 0;

      stfb->init_width = width;
      stfb->init_height = height;
      stfb->privateData = privateData;
   }

   return stfb;
}

static void setup_new_alpha_mask(struct vg_context *ctx,
                                 struct st_framebuffer *stfb,
                                 uint width, uint height)
{
   struct pipe_context *pipe = ctx->pipe;
   struct pipe_texture *old_texture = stfb->alpha_mask;

   /*
     we use PIPE_FORMAT_A8R8G8B8_UNORM because we want to render to
     this texture and use it as a sampler, so while this wastes some
     space it makes both of those a lot simpler
   */
   stfb->alpha_mask =
      create_texture(pipe, PIPE_FORMAT_A8R8G8B8_UNORM, width, height);

   if (!stfb->alpha_mask) {
      if (old_texture)
         pipe_texture_reference(&old_texture, NULL);
      return;
   }

   vg_validate_state(ctx);

   /* alpha mask starts with 1.f alpha */
   mask_fill(0, 0, width, height, 1.f);

   /* if we had an old surface copy it over */
   if (old_texture) {
      struct pipe_surface *surface = pipe->screen->get_tex_surface(
         pipe->screen,
         stfb->alpha_mask,
         0, 0, 0,
         PIPE_BUFFER_USAGE_GPU_WRITE);
      struct pipe_surface *old_surface = pipe->screen->get_tex_surface(
         pipe->screen,
         old_texture,
         0, 0, 0,
         PIPE_BUFFER_USAGE_GPU_READ);
      if (pipe->surface_copy) {
         pipe->surface_copy(pipe,
                            surface,
                            0, 0,
                            old_surface,
                            0, 0,
                            MIN2(old_surface->width, width),
                            MIN2(old_surface->height, height));
      } else {
         util_surface_copy(pipe, FALSE,
                           surface,
                           0, 0,
                           old_surface,
                           0, 0,
                           MIN2(old_surface->width, width),
                           MIN2(old_surface->height, height));
      }
      if (surface)
         pipe_surface_reference(&surface, NULL);
      if (old_surface)
         pipe_surface_reference(&old_surface, NULL);
   }

   /* Free the old texture
    */
   if (old_texture)
      pipe_texture_reference(&old_texture, NULL);
}

void st_resize_framebuffer(struct st_framebuffer *stfb,
                           uint width, uint height)
{
   struct vg_context *ctx = vg_current_context();
   struct st_renderbuffer *strb = stfb->strb;
   struct pipe_framebuffer_state *state;

   if (!ctx)
      return;

   state = &ctx->state.g3d.fb;

   /* If this is a noop, exit early and don't do the clear, etc below.
    */
   if (strb->width == width &&
       strb->height == height &&
       state->zsbuf)
      return;

   if (strb->width != width || strb->height != height)
      st_renderbuffer_alloc_storage(ctx, strb,
                                 width, height);

   if (stfb->dsrb->width != width || stfb->dsrb->height != height)
      st_renderbuffer_alloc_storage(ctx, stfb->dsrb,
                                 width, height);

   {
      VGuint i;

      memset(state, 0, sizeof(struct pipe_framebuffer_state));

      state->width  = width;
      state->height = height;

      state->nr_cbufs = 1;
      state->cbufs[0] = strb->surface;
      for (i = 1; i < PIPE_MAX_COLOR_BUFS; ++i)
         state->cbufs[i] = 0;

      state->zsbuf = stfb->dsrb->surface;

      cso_set_framebuffer(ctx->cso_context, state);
   }

   ctx->state.dirty |= VIEWPORT_DIRTY;
   ctx->state.dirty |= DEPTH_STENCIL_DIRTY;/*to reset the scissors*/

   ctx->pipe->clear(ctx->pipe, PIPE_CLEAR_DEPTHSTENCIL,
                    NULL, 0.0, 0);

   /* we need all the other state already set */

   setup_new_alpha_mask(ctx, stfb, width, height);

   pipe_texture_reference( &stfb->blend_texture, NULL );
   stfb->blend_texture = create_texture(ctx->pipe, PIPE_FORMAT_A8R8G8B8_UNORM,
                                        width, height);
}

void st_set_framebuffer_surface(struct st_framebuffer *stfb,
                                uint surfIndex, struct pipe_surface *surf)
{
   struct st_renderbuffer *rb = stfb->strb;

   /* unreference existing surfaces */
   pipe_surface_reference( &rb->surface, NULL );
   pipe_texture_reference( &rb->texture, NULL );

   /* reference new ones */
   pipe_surface_reference( &rb->surface, surf );
   pipe_texture_reference( &rb->texture, surf->texture );

   rb->width  = surf->width;
   rb->height = surf->height;
}

int st_get_framebuffer_surface(struct st_framebuffer *stfb,
                               uint surfIndex, struct pipe_surface **surf)
{
   struct st_renderbuffer *rb = stfb->strb;
   *surf = rb->surface;
   return VG_TRUE;
}

int st_get_framebuffer_texture(struct st_framebuffer *stfb,
                               uint surfIndex, struct pipe_texture **tex)
{
   struct st_renderbuffer *rb = stfb->strb;
   *tex = rb->texture;
   return VG_TRUE;
}

void * st_framebuffer_private(struct st_framebuffer *stfb)
{
   return stfb->privateData;
}

void st_unreference_framebuffer(struct st_framebuffer *stfb)
{
   /* FIXME */
}

void st_make_current(struct vg_context *st,
                     struct st_framebuffer *draw,
                     struct st_framebuffer *read)
{
   vg_set_current_context(st);
   if (st) {
      st->draw_buffer = draw;
   }
}

struct vg_context *st_get_current(void)
{
   return vg_current_context();
}

void st_flush(struct vg_context *st, uint pipeFlushFlags,
              struct pipe_fence_handle **fence)
{
   st->pipe->flush(st->pipe, pipeFlushFlags, fence);
}

void st_finish(struct vg_context *st)
{
   struct pipe_fence_handle *fence = NULL;

   st_flush(st, PIPE_FLUSH_RENDER_CACHE, &fence);

   st->pipe->screen->fence_finish(st->pipe->screen, fence, 0);
   st->pipe->screen->fence_reference(st->pipe->screen, &fence, NULL);
}

void st_notify_swapbuffers(struct st_framebuffer *stfb)
{
   struct vg_context *ctx = vg_current_context();
   if (ctx && ctx->draw_buffer == stfb) {
      st_flush(ctx,
	       PIPE_FLUSH_RENDER_CACHE | 
	       PIPE_FLUSH_SWAPBUFFERS |
	       PIPE_FLUSH_FRAME,
               NULL);
   }
}

void st_notify_swapbuffers_complete(struct st_framebuffer *stfb)
{
}

int st_bind_texture_surface(struct pipe_surface *ps, int target, int level,
                            enum pipe_format format)
{
   return 0;
}

int st_unbind_texture_surface(struct pipe_surface *ps, int target, int level)
{
   return 0;
}
