/**************************************************************************
 *
 * 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 "paint.h"
#include "renderer.h"
#include "shaders_cache.h"
#include "shader.h"
#include "vg_manager.h"
#include "api.h"
#include "mask.h"
#include "handle.h"

#include "pipe/p_context.h"
#include "util/u_inlines.h"

#include "cso_cache/cso_context.h"

#include "util/u_memory.h"
#include "util/u_sampler.h"
#include "util/u_surface.h"
#include "util/u_format.h"

struct vg_context *_vg_context = 0;

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

/**
 * A depth/stencil rb will be needed regardless of what the visual says.
 */
static boolean
choose_depth_stencil_format(struct vg_context *ctx)
{
   struct pipe_screen *screen = ctx->pipe->screen;
   enum pipe_format formats[] = {
      PIPE_FORMAT_Z24_UNORM_S8_UINT,
      PIPE_FORMAT_S8_UINT_Z24_UNORM,
      PIPE_FORMAT_NONE
   };
   enum pipe_format *fmt;

   for (fmt = formats; *fmt != PIPE_FORMAT_NONE; fmt++) {
      if (screen->is_format_supported(screen, *fmt,
               PIPE_TEXTURE_2D, 0, PIPE_BIND_DEPTH_STENCIL))
         break;
   }

   ctx->ds_format = *fmt;

   return (ctx->ds_format != PIPE_FORMAT_NONE);
}

void vg_set_current_context(struct vg_context *ctx)
{
   _vg_context = ctx;
   api_make_dispatch_current((ctx) ? ctx->dispatch : NULL);
}

struct vg_context * vg_create_context(struct pipe_context *pipe,
                                      const void *visual,
                                      struct vg_context *share)
{
   struct vg_context *ctx;

   ctx = CALLOC_STRUCT(vg_context);

   ctx->pipe = pipe;
   if (!choose_depth_stencil_format(ctx)) {
      FREE(ctx);
      return NULL;
   }

   ctx->dispatch = api_create_dispatch();

   vg_init_state(&ctx->state.vg);
   ctx->state.dirty = ALL_DIRTY;

   ctx->cso_context = cso_create_context(pipe);

   ctx->default_paint = paint_create(ctx);
   ctx->state.vg.stroke_paint = ctx->default_paint;
   ctx->state.vg.fill_paint = ctx->default_paint;


   ctx->mask.sampler.wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
   ctx->mask.sampler.wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
   ctx->mask.sampler.wrap_r = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
   ctx->mask.sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NONE;
   ctx->mask.sampler.min_img_filter = PIPE_TEX_FILTER_NEAREST;
   ctx->mask.sampler.mag_img_filter = PIPE_TEX_FILTER_NEAREST;
   ctx->mask.sampler.normalized_coords = 0;

   ctx->blend_sampler.wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
   ctx->blend_sampler.wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
   ctx->blend_sampler.wrap_r = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
   ctx->blend_sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NONE;
   ctx->blend_sampler.min_img_filter = PIPE_TEX_FILTER_NEAREST;
   ctx->blend_sampler.mag_img_filter = PIPE_TEX_FILTER_NEAREST;
   ctx->blend_sampler.normalized_coords = 0;

   vg_set_error(ctx, VG_NO_ERROR);

   ctx->owned_objects[VG_OBJECT_PAINT] = cso_hash_create();
   ctx->owned_objects[VG_OBJECT_IMAGE] = cso_hash_create();
   ctx->owned_objects[VG_OBJECT_MASK] = cso_hash_create();
   ctx->owned_objects[VG_OBJECT_FONT] = cso_hash_create();
   ctx->owned_objects[VG_OBJECT_PATH] = cso_hash_create();

   ctx->renderer = renderer_create(ctx);
   ctx->sc = shaders_cache_create(ctx);
   ctx->shader = shader_create(ctx);

   return ctx;
}

void vg_destroy_context(struct vg_context *ctx)
{
   struct pipe_resource **cbuf = &ctx->mask.cbuf;

   renderer_destroy(ctx->renderer);
   shaders_cache_destroy(ctx->sc);
   shader_destroy(ctx->shader);
   paint_destroy(ctx->default_paint);

   if (*cbuf)
      pipe_resource_reference(cbuf, NULL);

   if (ctx->mask.union_fs)
      vg_shader_destroy(ctx, ctx->mask.union_fs);
   if (ctx->mask.intersect_fs)
      vg_shader_destroy(ctx, ctx->mask.intersect_fs);
   if (ctx->mask.subtract_fs)
      vg_shader_destroy(ctx, ctx->mask.subtract_fs);
   if (ctx->mask.set_fs)
      vg_shader_destroy(ctx, ctx->mask.set_fs);

   cso_release_all(ctx->cso_context);
   cso_destroy_context(ctx->cso_context);

   cso_hash_delete(ctx->owned_objects[VG_OBJECT_PAINT]);
   cso_hash_delete(ctx->owned_objects[VG_OBJECT_IMAGE]);
   cso_hash_delete(ctx->owned_objects[VG_OBJECT_MASK]);
   cso_hash_delete(ctx->owned_objects[VG_OBJECT_FONT]);
   cso_hash_delete(ctx->owned_objects[VG_OBJECT_PATH]);

   api_destroy_dispatch(ctx->dispatch);

   FREE(ctx);
}

void vg_init_object(struct vg_object *obj, struct vg_context *ctx, enum vg_object_type type)
{
   obj->type = type;
   obj->ctx = ctx;
   obj->handle = create_handle(obj);
}

/** free object resources, but not the object itself */
void vg_free_object(struct vg_object *obj)
{
   obj->type = 0;
   obj->ctx = NULL;
   destroy_handle(obj->handle);
}

VGboolean vg_context_is_object_valid(struct vg_context *ctx,
                                enum vg_object_type type,
                                VGHandle handle)
{
    if (ctx) {
       struct cso_hash *hash = ctx->owned_objects[type];
       if (!hash)
          return VG_FALSE;
       return cso_hash_contains(hash, (unsigned) handle);
    }
    return VG_FALSE;
}

void vg_context_add_object(struct vg_context *ctx,
                           struct vg_object *obj)
{
    if (ctx) {
       struct cso_hash *hash = ctx->owned_objects[obj->type];
       if (!hash)
          return;
       cso_hash_insert(hash, (unsigned) obj->handle, obj);
    }
}

void vg_context_remove_object(struct vg_context *ctx,
                              struct vg_object *obj)
{
   if (ctx) {
      struct cso_hash *hash = ctx->owned_objects[obj->type];
      if (!hash)
         return;
      cso_hash_take(hash, (unsigned) obj->handle);
   }
}

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

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

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

   templ.target = PIPE_TEXTURE_2D;
   templ.width0 = width;
   templ.height0 = height;
   templ.depth0 = 1;
   templ.array_size = 1;
   templ.last_level = 0;

   if (util_format_get_component_bits(format, UTIL_FORMAT_COLORSPACE_ZS, 1)) {
      templ.bind = PIPE_BIND_DEPTH_STENCIL;
   } else {
      templ.bind = (PIPE_BIND_DISPLAY_TARGET |
                    PIPE_BIND_RENDER_TARGET |
                    PIPE_BIND_SAMPLER_VIEW);
   }

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

static struct pipe_sampler_view *
create_tex_and_view(struct pipe_context *pipe, enum pipe_format format,
                    VGint width, VGint height)
{
   struct pipe_resource *texture;
   struct pipe_sampler_view view_templ;
   struct pipe_sampler_view *view;

   texture = create_texture(pipe, format, width, height);

   if (!texture)
      return NULL;

   u_sampler_view_default_template(&view_templ, texture, texture->format);
   view = pipe->create_sampler_view(pipe, texture, &view_templ);
   /* want the texture to go away if the view is freed */
   pipe_resource_reference(&texture, NULL);

   return view;
}

static void
vg_context_update_surface_mask_view(struct vg_context *ctx,
                                    uint width, uint height)
{
   struct st_framebuffer *stfb = ctx->draw_buffer;
   struct pipe_sampler_view *old_sampler_view = stfb->surface_mask_view;
   struct pipe_context *pipe = ctx->pipe;

   if (old_sampler_view &&
       old_sampler_view->texture->width0 == width &&
       old_sampler_view->texture->height0 == height)
      return;

   /*
     we use PIPE_FORMAT_B8G8R8A8_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->surface_mask_view = create_tex_and_view(pipe,
         PIPE_FORMAT_B8G8R8A8_UNORM, width, height);

   if (!stfb->surface_mask_view) {
      if (old_sampler_view)
         pipe_sampler_view_reference(&old_sampler_view, NULL);
      return;
   }

   /* XXX could this call be avoided? */
   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_sampler_view) {
      struct pipe_box src_box;
      u_box_origin_2d(MIN2(old_sampler_view->texture->width0,
                           stfb->surface_mask_view->texture->width0),
                      MIN2(old_sampler_view->texture->height0,
                           stfb->surface_mask_view->texture->height0),
                      &src_box);

      pipe->resource_copy_region(pipe,
                                 stfb->surface_mask_view->texture,
                                 0, 0, 0, 0,
                                 old_sampler_view->texture,
                                 0, &src_box);
   }

   /* Free the old texture
    */
   if (old_sampler_view)
      pipe_sampler_view_reference(&old_sampler_view, NULL);
}

static void
vg_context_update_blend_texture_view(struct vg_context *ctx,
                                     uint width, uint height)
{
   struct pipe_context *pipe = ctx->pipe;
   struct st_framebuffer *stfb = ctx->draw_buffer;
   struct pipe_sampler_view *old = stfb->blend_texture_view;

   if (old &&
       old->texture->width0 == width &&
       old->texture->height0 == height)
      return;

   stfb->blend_texture_view = create_tex_and_view(pipe,
         PIPE_FORMAT_B8G8R8A8_UNORM, width, height);

   pipe_sampler_view_reference(&old, NULL);
}

static boolean
vg_context_update_depth_stencil_rb(struct vg_context * ctx,
                                   uint width, uint height)
{
   struct st_renderbuffer *dsrb = ctx->draw_buffer->dsrb;
   struct pipe_context *pipe = ctx->pipe;
   struct pipe_surface surf_tmpl;

   if ((dsrb->width == width && dsrb->height == height) && dsrb->texture)
      return FALSE;

   /* unreference existing ones */
   pipe_surface_reference(&dsrb->surface, NULL);
   pipe_resource_reference(&dsrb->texture, NULL);
   dsrb->width = dsrb->height = 0;

   dsrb->texture = create_texture(pipe, dsrb->format, width, height);
   if (!dsrb->texture)
      return TRUE;

   u_surface_default_template(&surf_tmpl, dsrb->texture);
   dsrb->surface = pipe->create_surface(pipe,
                                        dsrb->texture,
                                        &surf_tmpl);
   if (!dsrb->surface) {
      pipe_resource_reference(&dsrb->texture, NULL);
      return TRUE;
   }

   dsrb->width = width;
   dsrb->height = height;

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

   return TRUE;
}

void vg_validate_state(struct vg_context *ctx)
{
   struct st_framebuffer *stfb = ctx->draw_buffer;

   vg_manager_validate_framebuffer(ctx);

   if (vg_context_update_depth_stencil_rb(ctx, stfb->width, stfb->height))
      ctx->state.dirty |= DEPTH_STENCIL_DIRTY;

   /* blend state depends on fb format and paint color */
   if ((ctx->state.dirty & FRAMEBUFFER_DIRTY) ||
       (ctx->state.dirty & PAINT_DIRTY))
      ctx->state.dirty |= BLEND_DIRTY;

   renderer_validate(ctx->renderer, ctx->state.dirty,
         ctx->draw_buffer, &ctx->state.vg);

   ctx->state.dirty = 0;

   shader_set_masking(ctx->shader, ctx->state.vg.masking);
   shader_set_image_mode(ctx->shader, ctx->state.vg.image_mode);
   shader_set_color_transform(ctx->shader, ctx->state.vg.color_transform);
}

VGboolean vg_object_is_valid(VGHandle object, enum vg_object_type type)
{
   struct vg_object *obj = handle_to_object(object);
   if (obj && is_aligned(obj) && obj->type == type)
      return VG_TRUE;
   else
      return VG_FALSE;
}

void vg_set_error(struct vg_context *ctx,
                  VGErrorCode code)
{
   /*vgGetError returns the oldest error code provided by
    * an API call on the current context since the previous
    * call to vgGetError on that context (or since the creation
    of the context).*/
   if (ctx->_error == VG_NO_ERROR)
      ctx->_error = code;
}

static void vg_prepare_blend_texture(struct vg_context *ctx,
                                     struct pipe_sampler_view *src)
{
   struct st_framebuffer *stfb = ctx->draw_buffer;
   struct pipe_context *pipe = ctx->pipe;
   struct pipe_blit_info info;

   vg_context_update_blend_texture_view(ctx, stfb->width, stfb->height);

   memset(&info, 0, sizeof info);
   info.dst.resource = stfb->blend_texture_view->texture;
   info.dst.level = 0;
   info.dst.box.x = 0;
   info.dst.box.y = 0;
   info.dst.box.z = 0;
   info.dst.box.width = stfb->width;
   info.dst.box.height = stfb->height;
   info.dst.box.depth = 1;
   info.dst.format = stfb->blend_texture_view->format;
   info.src.resource = src->texture;
   info.src.level = src->u.tex.first_level;
   info.src.box.x = 0;
   info.src.box.y = 0;
   info.src.box.z = src->u.tex.first_layer;
   info.src.box.width = stfb->width;
   info.src.box.height = stfb->height;
   info.src.box.depth = 1;
   info.src.format = src->format;
   info.mask = PIPE_MASK_RGBA;
   info.filter = PIPE_TEX_MIPFILTER_NEAREST;
   info.scissor_enable = 0;

   pipe->blit(pipe, &info);
}

struct pipe_sampler_view *vg_prepare_blend_surface(struct vg_context *ctx)
{
   struct pipe_context *pipe = ctx->pipe;
   struct pipe_sampler_view *view;
   struct pipe_sampler_view view_templ;
   struct st_framebuffer *stfb = ctx->draw_buffer;
   struct st_renderbuffer *strb = stfb->strb;

   vg_validate_state(ctx);

   u_sampler_view_default_template(&view_templ, strb->texture, strb->texture->format);
   view = pipe->create_sampler_view(pipe, strb->texture, &view_templ);

   vg_prepare_blend_texture(ctx, view);

   pipe_sampler_view_reference(&view, NULL);

   return stfb->blend_texture_view;
}


struct pipe_sampler_view *vg_prepare_blend_surface_from_mask(struct vg_context *ctx)
{
   struct st_framebuffer *stfb = ctx->draw_buffer;

   vg_validate_state(ctx);

   vg_context_update_surface_mask_view(ctx, stfb->width, stfb->height);
   vg_prepare_blend_texture(ctx, stfb->surface_mask_view);

   return stfb->blend_texture_view;
}

struct pipe_sampler_view *vg_get_surface_mask(struct vg_context *ctx)
{
   struct st_framebuffer *stfb = ctx->draw_buffer;

   vg_context_update_surface_mask_view(ctx, stfb->width, stfb->height);

   return stfb->surface_mask_view;
}

/**
 * A transformation from window coordinates to paint coordinates.
 */
VGboolean vg_get_paint_matrix(struct vg_context *ctx,
                              const struct matrix *paint_to_user,
                              const struct matrix *user_to_surface,
                              struct matrix *mat)
{
   struct matrix tmp;

   /* get user-to-paint matrix */
   memcpy(mat, paint_to_user, sizeof(*paint_to_user));
   if (!matrix_invert(mat))
      return VG_FALSE;

   /* get surface-to-user matrix */
   memcpy(&tmp, user_to_surface, sizeof(*user_to_surface));
   if (!matrix_invert(&tmp))
      return VG_FALSE;

   matrix_mult(mat, &tmp);

   return VG_TRUE;
}
