
#include "gskglshadowcacheprivate.h"

#define MAX_UNUSED_FRAMES (16 * 5) /* 5 seconds? */

typedef struct
{
  GskRoundedRect outline;
  float blur_radius;
} CacheKey;

typedef struct
{
  GskRoundedRect outline;
  float blur_radius;

  int texture_id;
  int unused_frames;
} CacheItem;

static gboolean
key_equal (const void *x,
           const void *y)
{
  const CacheKey *a = x;
  const CacheKey *b = y;

  return graphene_size_equal (&a->outline.corner[0], &b->outline.corner[0]) &&
         graphene_size_equal (&a->outline.corner[1], &b->outline.corner[1]) &&
         graphene_size_equal (&a->outline.corner[2], &b->outline.corner[2]) &&
         graphene_size_equal (&a->outline.corner[3], &b->outline.corner[3]) &&
         graphene_rect_equal (&a->outline.bounds, &b->outline.bounds) &&
         a->blur_radius == b->blur_radius;
}

void
gsk_gl_shadow_cache_init (GskGLShadowCache *self)
{
  self->textures = g_array_new (FALSE, TRUE, sizeof (CacheItem));
}

void
gsk_gl_shadow_cache_free (GskGLShadowCache *self,
                          GskGLDriver      *gl_driver)
{
  guint i, p;

  for (i = 0, p = self->textures->len; i < p; i ++)
    {
      const CacheItem *item = &g_array_index (self->textures, CacheItem, i);

      gsk_gl_driver_destroy_texture (gl_driver, item->texture_id);
    }

  g_array_free (self->textures, TRUE);
  self->textures = NULL;
}

void
gsk_gl_shadow_cache_begin_frame (GskGLShadowCache *self,
                                 GskGLDriver      *gl_driver)
{
  guint i, p;

  /* We remove all textures with used = FALSE since those have not been used in the
   * last frame. For all others, we reset the `used` value to FALSE instead and see
   * if they end up with TRUE in the next call to begin_frame. */
  for (i = 0, p = self->textures->len; i < p; i ++)
    {
      CacheItem *item = &g_array_index (self->textures, CacheItem, i);

      if (item->unused_frames > MAX_UNUSED_FRAMES)
        {
          gsk_gl_driver_destroy_texture (gl_driver, item->texture_id);
          g_array_remove_index_fast (self->textures, i);
          p --;
          i --;
        }
      else
        {
          item->unused_frames ++;
        }
    }
}

/* XXX
 * The offset origin should always be at 0/0, or the blur radius should just go
 * away since it defines the origin position anyway?
 */
int
gsk_gl_shadow_cache_get_texture_id (GskGLShadowCache     *self,
                                    GskGLDriver          *gl_driver,
                                    const GskRoundedRect *shadow_rect,
                                    float                 blur_radius)
{
  CacheItem *item= NULL;
  guint i;

  g_assert (self != NULL);
  g_assert (gl_driver != NULL);
  g_assert (shadow_rect != NULL);

  for (i = 0; i < self->textures->len; i ++)
    {
      CacheItem *k = &g_array_index (self->textures, CacheItem, i);

      if (key_equal (&(CacheKey){*shadow_rect, blur_radius},
                     &(CacheKey){k->outline, k->blur_radius}))
        {
          item = k;
          break;
        }
    }

  if (item == NULL)
    return 0;

  item->unused_frames = 0;

  g_assert (item->texture_id != 0);

  return item->texture_id;
}

void
gsk_gl_shadow_cache_commit (GskGLShadowCache     *self,
                            const GskRoundedRect *shadow_rect,
                            float                 blur_radius,
                            int                   texture_id)
{
  CacheItem *item;

  g_assert (self != NULL);
  g_assert (shadow_rect != NULL);
  g_assert (texture_id > 0);

  g_array_set_size (self->textures, self->textures->len + 1);
  item = &g_array_index (self->textures, CacheItem, self->textures->len - 1);

  item->outline = *shadow_rect;
  item->blur_radius = blur_radius;
  item->unused_frames = 0;
  item->texture_id = texture_id;
}
