/* GTK - The GIMP Toolkit
 * Copyright (C) 2015 Benjamin Otte <otte@gnome.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include "gtkcssnodestylecacheprivate.h"

#include "gtkdebug.h"
#include "gtkcssstaticstyleprivate.h"

struct _GtkCssNodeStyleCache {
  guint        ref_count;
  GtkCssStyle *style;
  GHashTable  *children;
};

#define UNPACK_DECLARATION(packed) ((GtkCssNodeDeclaration *) (GPOINTER_TO_SIZE (packed) & ~0x3))
#define UNPACK_FLAGS(packed) (GPOINTER_TO_SIZE (packed) & 0x3)
#define PACK(decl, first_child, last_child) GSIZE_TO_POINTER (GPOINTER_TO_SIZE (decl) | ((first_child) ? 0x2 : 0) | ((last_child) ? 0x1 : 0))

GtkCssNodeStyleCache *
gtk_css_node_style_cache_new (GtkCssStyle *style)
{
  GtkCssNodeStyleCache *result;

  result = g_slice_new0 (GtkCssNodeStyleCache);

  result->ref_count = 1;
  result->style = g_object_ref (style);

  return result;
}

GtkCssNodeStyleCache *
gtk_css_node_style_cache_ref (GtkCssNodeStyleCache *cache)
{
  cache->ref_count++; 

  return cache;
}

void
gtk_css_node_style_cache_unref (GtkCssNodeStyleCache *cache)
{
  cache->ref_count--; 

  if (cache->ref_count > 0)
    return;

  g_object_unref (cache->style);
  if (cache->children)
    g_hash_table_unref (cache->children);

  g_slice_free (GtkCssNodeStyleCache, cache);
}

GtkCssStyle *
gtk_css_node_style_cache_get_style (GtkCssNodeStyleCache *cache)
{
  return cache->style;
}

static gboolean
may_be_stored_in_cache (GtkCssStyle *style)
{
  GtkCssChange change;

  /* If you run your application with
   *   GTK_DEBUG=no-css-cache
   * no caching will happen. This is slow (in particular
   * when animating), but useful for figuring out bugs.
   *
   * We achieve that by disallowing any inserts into caches here.
   */
#ifdef G_ENABLE_DEBUG
  if (GTK_DEBUG_CHECK (NO_CSS_CACHE))
    return FALSE;
#endif

  if (!GTK_IS_CSS_STATIC_STYLE (style))
    return FALSE;

  change = gtk_css_static_style_get_change (GTK_CSS_STATIC_STYLE (style));

  /* The cache is shared between all children of the parent, so if a
   * style depends on a sibling it is not independant of the child.
   */
  if (change & GTK_CSS_CHANGE_ANY_SIBLING)
    return FALSE;

  /* Again, the cache is shared between all children of the parent.
   * If the position is relevant, no child has the same style.
   */
  if (change & (GTK_CSS_CHANGE_NTH_CHILD | GTK_CSS_CHANGE_NTH_LAST_CHILD))
    return FALSE;

  return TRUE;
}

static guint
gtk_css_node_style_cache_decl_hash (gconstpointer item)
{
  return gtk_css_node_declaration_hash (UNPACK_DECLARATION (item)) << 2
    | UNPACK_FLAGS (item);
}

static gboolean
gtk_css_node_style_cache_decl_equal (gconstpointer item1,
                                     gconstpointer item2)
{
  if (UNPACK_FLAGS (item1) != UNPACK_FLAGS (item2))
    return FALSE;

  return gtk_css_node_declaration_equal (UNPACK_DECLARATION (item1),
                                         UNPACK_DECLARATION (item2));
}

static void
gtk_css_node_style_cache_decl_free (gpointer item)
{
  gtk_css_node_declaration_unref (UNPACK_DECLARATION (item));
}

GtkCssNodeStyleCache *
gtk_css_node_style_cache_insert (GtkCssNodeStyleCache   *parent,
                                 GtkCssNodeDeclaration  *decl,
                                 gboolean                is_first,
                                 gboolean                is_last,
                                 GtkCssStyle            *style)
{
  GtkCssNodeStyleCache *result;

  if (!may_be_stored_in_cache (style))
    return NULL;

  if (parent->children == NULL)
    parent->children = g_hash_table_new_full (gtk_css_node_style_cache_decl_hash,
                                              gtk_css_node_style_cache_decl_equal,
                                              gtk_css_node_style_cache_decl_free,
                                              (GDestroyNotify) gtk_css_node_style_cache_unref);

  result = gtk_css_node_style_cache_new (style);

  g_hash_table_insert (parent->children,
                       PACK (gtk_css_node_declaration_ref (decl), is_first, is_last),
                       gtk_css_node_style_cache_ref (result));

  return result;
}

GtkCssNodeStyleCache *
gtk_css_node_style_cache_lookup (GtkCssNodeStyleCache        *parent,
                                 const GtkCssNodeDeclaration *decl,
                                 gboolean                     is_first,
                                 gboolean                     is_last)
{
  GtkCssNodeStyleCache *result;

  if (parent->children == NULL)
    return NULL;

  result = g_hash_table_lookup (parent->children, PACK (decl, is_first, is_last));
  if (result == NULL)
    return NULL;

  return gtk_css_node_style_cache_ref (result);
}