/* GTK - The GIMP Toolkit
 * Copyright (C) 2014 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 "gtkcssnodeprivate.h"

#include "gtkcssanimatedstyleprivate.h"
#include "gtkdebug.h"
#include "gtkintl.h"
#include "gtkmarshalers.h"
#include "gtksettingsprivate.h"
#include "gtktypebuiltins.h"

/* When these change we do a full restyling. Otherwise we try to figure out
 * if we need to change things. */
#define GTK_CSS_RADICAL_CHANGE (GTK_CSS_CHANGE_ID | GTK_CSS_CHANGE_NAME | GTK_CSS_CHANGE_CLASS | GTK_CSS_CHANGE_SOURCE | GTK_CSS_CHANGE_PARENT_STYLE)

G_DEFINE_TYPE (GtkCssNode, gtk_css_node, G_TYPE_OBJECT)

enum {
  NODE_ADDED,
  NODE_REMOVED,
  STYLE_CHANGED,
  LAST_SIGNAL
};

enum {
  PROP_0,
  PROP_CLASSES,
  PROP_ID,
  PROP_NAME,
  PROP_STATE,
  PROP_VISIBLE,
  PROP_WIDGET_TYPE,
  NUM_PROPERTIES
};

static guint cssnode_signals[LAST_SIGNAL] = { 0 };
static GParamSpec *cssnode_properties[NUM_PROPERTIES];

static GQuark quark_global_cache;

static GtkStyleProviderPrivate *
gtk_css_node_get_style_provider_or_null (GtkCssNode *cssnode)
{
  return GTK_CSS_NODE_GET_CLASS (cssnode)->get_style_provider (cssnode);
}

static void
gtk_css_node_set_invalid (GtkCssNode *node,
                          gboolean    invalid)
{
  if (node->invalid == invalid)
    return;

  node->invalid = invalid;

  if (node->visible)
    {
      if (node->parent)
        {
          if (invalid)
            gtk_css_node_set_invalid (node->parent, TRUE);
        }
      else
        {
          if (invalid)
            GTK_CSS_NODE_GET_CLASS (node)->queue_validate (node);
          else
            GTK_CSS_NODE_GET_CLASS (node)->dequeue_validate (node);
        }
    }
}

static void
gtk_css_node_get_property (GObject    *object,
                           guint       property_id,
                           GValue     *value,
                           GParamSpec *pspec)
{
  GtkCssNode *cssnode = GTK_CSS_NODE (object);

  switch (property_id)
    {
    case PROP_CLASSES:
      g_value_take_boxed (value, gtk_css_node_get_classes (cssnode));
      break;

    case PROP_ID:
      g_value_set_string (value, gtk_css_node_get_id (cssnode));
      break;

    case PROP_NAME:
      g_value_set_string (value, gtk_css_node_get_name (cssnode));
      break;

    case PROP_STATE:
      g_value_set_flags (value, gtk_css_node_get_state (cssnode));
      break;

    case PROP_VISIBLE:
      g_value_set_boolean (value, gtk_css_node_get_visible (cssnode));
      break;

    case PROP_WIDGET_TYPE:
      g_value_set_gtype (value, gtk_css_node_get_widget_type (cssnode));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
gtk_css_node_set_property (GObject      *object,
                           guint         property_id,
                           const GValue *value,
                           GParamSpec   *pspec)
{
  GtkCssNode *cssnode = GTK_CSS_NODE (object);

  switch (property_id)
    {
    case PROP_CLASSES:
      gtk_css_node_set_classes (cssnode, g_value_get_boxed (value));
      break;

    case PROP_ID:
      gtk_css_node_set_id (cssnode, g_value_get_string (value));
      break;

    case PROP_NAME:
      gtk_css_node_set_name (cssnode, g_value_get_string (value));
      break;

    case PROP_STATE:
      gtk_css_node_set_state (cssnode, g_value_get_flags (value));
      break;

    case PROP_VISIBLE:
      gtk_css_node_set_visible (cssnode, g_value_get_boolean (value));
      break;

    case PROP_WIDGET_TYPE:
      gtk_css_node_set_widget_type (cssnode, g_value_get_gtype (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
gtk_css_node_dispose (GObject *object)
{
  GtkCssNode *cssnode = GTK_CSS_NODE (object);

  while (cssnode->first_child)
    {
      gtk_css_node_set_parent (cssnode->first_child, NULL);
    }

  gtk_css_node_set_invalid (cssnode, FALSE);

  G_OBJECT_CLASS (gtk_css_node_parent_class)->dispose (object);
}

static void
gtk_css_node_finalize (GObject *object)
{
  GtkCssNode *cssnode = GTK_CSS_NODE (object);

  if (cssnode->style)
    g_object_unref (cssnode->style);
  gtk_css_node_declaration_unref (cssnode->decl);

  G_OBJECT_CLASS (gtk_css_node_parent_class)->finalize (object);
}

#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))

static gboolean
may_use_global_parent_cache (GtkCssNode *node)
{
  GtkStyleProviderPrivate *provider;
  GtkCssNode *parent;
  
  parent = gtk_css_node_get_parent (node);
  if (parent == NULL)
    return FALSE;

  provider = gtk_css_node_get_style_provider_or_null (node);
  if (provider != NULL && provider != gtk_css_node_get_style_provider (parent))
    return FALSE;

  return TRUE;
}

static GtkCssStyle *
lookup_in_global_parent_cache (GtkCssNode                  *node,
                               GtkCssStyle                 *parent,
                               const GtkCssNodeDeclaration *decl)
{
  GHashTable *cache;
  GtkCssStyle *style;

  if (parent == NULL ||
      !may_use_global_parent_cache (node))
    return NULL;

  cache = g_object_get_qdata (G_OBJECT (parent), quark_global_cache);
  if (cache == NULL)
    return NULL;

  style = g_hash_table_lookup (cache,
                               PACK (decl,
                                     gtk_css_node_get_previous_sibling (node) == NULL,
                                     gtk_css_node_get_next_sibling (node) == NULL));

  return style;
}

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

  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_global_parent_cache_hash (gconstpointer item)
{
  return gtk_css_node_declaration_hash (UNPACK_DECLARATION (item)) << 2
    | UNPACK_FLAGS (item);
}

static gboolean
gtk_global_parent_cache_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_global_parent_cache_free (gpointer item)
{
  gtk_css_node_declaration_unref (UNPACK_DECLARATION (item));
}

static void
store_in_global_parent_cache (GtkCssNode                  *node,
                              GtkCssStyle                 *parent,
                              const GtkCssNodeDeclaration *decl,
                              GtkCssStyle                 *style)
{
  GHashTable *cache;

  g_assert (GTK_IS_CSS_STATIC_STYLE (style));

  if (parent == NULL ||
      !may_use_global_parent_cache (node))
    return;

  if (!may_be_stored_in_parent_cache (style))
    return;

  cache = g_object_get_qdata (G_OBJECT (parent), quark_global_cache);
  if (cache == NULL)
    {
      cache = g_hash_table_new_full (gtk_global_parent_cache_hash,
                                     gtk_global_parent_cache_equal,
                                     gtk_global_parent_cache_free,
                                     g_object_unref);
      g_object_set_qdata_full (G_OBJECT (parent),
                               quark_global_cache,
                               cache,
                               (GDestroyNotify) g_hash_table_destroy);
    }

  g_hash_table_insert (cache,
                       PACK (gtk_css_node_declaration_ref ((GtkCssNodeDeclaration *) decl),
                             gtk_css_node_get_previous_sibling (node) == NULL,
                             gtk_css_node_get_next_sibling (node) == NULL),
                       g_object_ref (style));
}

static GtkCssStyle *
gtk_css_node_create_style (GtkCssNode *cssnode)
{
  const GtkCssNodeDeclaration *decl;
  GtkCssMatcher matcher;
  GtkCssStyle *parent;
  GtkCssStyle *style;

  decl = gtk_css_node_get_declaration (cssnode);
  parent = cssnode->parent ? cssnode->parent->style : NULL;

  style = lookup_in_global_parent_cache (cssnode, parent, decl);
  if (style)
    return g_object_ref (style);

  if (gtk_css_node_init_matcher (cssnode, &matcher))
    style = gtk_css_static_style_new_compute (gtk_css_node_get_style_provider (cssnode),
                                              &matcher,
                                              parent);
  else
    style = gtk_css_static_style_new_compute (gtk_css_node_get_style_provider (cssnode),
                                              NULL,
                                              parent);

  store_in_global_parent_cache (cssnode, parent, decl, style);

  return style;
}

static gboolean
should_create_transitions (GtkCssChange change)
{
  return (change & GTK_CSS_CHANGE_ANIMATIONS) == 0;
}

static gboolean
gtk_css_style_needs_recreation (GtkCssStyle  *style,
                                GtkCssChange  change)
{
  /* Try to avoid invalidating if we can */
  if (change & GTK_CSS_RADICAL_CHANGE)
    return TRUE;

  if (GTK_IS_CSS_ANIMATED_STYLE (style))
    style = GTK_CSS_ANIMATED_STYLE (style)->style;

  if (gtk_css_static_style_get_change (GTK_CSS_STATIC_STYLE (style)) & change)
    return TRUE;
  else
    return FALSE;
}

static GtkCssStyle *
gtk_css_node_real_update_style (GtkCssNode   *cssnode,
                                GtkCssChange  change,
                                gint64        timestamp,
                                GtkCssStyle  *style)
{
  GtkCssStyle *static_style, *new_static_style, *new_style;

  if (GTK_IS_CSS_ANIMATED_STYLE (style))
    {
      static_style = GTK_CSS_ANIMATED_STYLE (style)->style;
    }
  else
    {
      static_style = style;
    }

  if (gtk_css_style_needs_recreation (static_style, change))
    new_static_style = gtk_css_node_create_style (cssnode);
  else
    new_static_style = g_object_ref (static_style);

  if (new_static_style != static_style || (change & GTK_CSS_CHANGE_ANIMATIONS))
    {
      GtkCssNode *parent = gtk_css_node_get_parent (cssnode);
      new_style = gtk_css_animated_style_new (new_static_style,
                                              parent ? gtk_css_node_get_style (parent) : NULL,
                                              timestamp,
                                              gtk_css_node_get_style_provider (cssnode),
                                              should_create_transitions (change) ? style : NULL);
    }
  else if (static_style != style && (change & GTK_CSS_CHANGE_TIMESTAMP))
    {
      new_style = gtk_css_animated_style_new_advance (GTK_CSS_ANIMATED_STYLE (style),
                                                      static_style,
                                                      timestamp);
    }
  else
    {
      new_style = g_object_ref (style);
    }

  if (!gtk_css_style_is_static (new_style))
    gtk_css_node_set_invalid (cssnode, TRUE);

  g_object_unref (new_static_style);

  return new_style;
}

static void
gtk_css_node_real_invalidate (GtkCssNode *node)
{
}

static void
gtk_css_node_real_queue_validate (GtkCssNode *node)
{
}

static void
gtk_css_node_real_dequeue_validate (GtkCssNode *node)
{
}

static void
gtk_css_node_real_validate (GtkCssNode *node)
{
}

gboolean
gtk_css_node_real_init_matcher (GtkCssNode     *cssnode,
                                GtkCssMatcher  *matcher)
{
  _gtk_css_matcher_node_init (matcher, cssnode);

  return TRUE;
}

static GtkWidgetPath *
gtk_css_node_real_create_widget_path (GtkCssNode *cssnode)
{
  return gtk_widget_path_new ();
}

static const GtkWidgetPath *
gtk_css_node_real_get_widget_path (GtkCssNode *cssnode)
{
  return NULL;
}

static GtkStyleProviderPrivate *
gtk_css_node_real_get_style_provider (GtkCssNode *cssnode)
{
  return NULL;
}

static GdkFrameClock *
gtk_css_node_real_get_frame_clock (GtkCssNode *cssnode)
{
  return NULL;
}

static void
gtk_css_node_real_node_removed (GtkCssNode *parent,
                                GtkCssNode *node,
                                GtkCssNode *previous)
{
  if (node->previous_sibling)
    node->previous_sibling->next_sibling = node->next_sibling;
  else
    node->parent->first_child = node->next_sibling;

  if (node->next_sibling)
    node->next_sibling->previous_sibling = node->previous_sibling;
  else
    node->parent->last_child = node->previous_sibling;

  node->previous_sibling = NULL;
  node->next_sibling = NULL;
  node->parent = NULL;
}

static void
gtk_css_node_real_node_added (GtkCssNode *parent,
                              GtkCssNode *node,
                              GtkCssNode *new_previous)
{
  if (new_previous)
    {
      node->previous_sibling = new_previous;
      node->next_sibling = new_previous->next_sibling;
      new_previous->next_sibling = node;
    }
  else
    {
      node->next_sibling = parent->first_child;
      parent->first_child = node;
    }

  if (node->next_sibling)
    node->next_sibling->previous_sibling = node;
  else
    parent->last_child = node;

  node->parent = parent;
}

static void
gtk_css_node_real_style_changed (GtkCssNode  *cssnode,
                                 GtkCssStyle *old_style,
                                 GtkCssStyle *new_style)
{
  g_object_ref (new_style);
  g_object_unref (old_style);

  cssnode->style = new_style;
}

static void
gtk_css_node_class_init (GtkCssNodeClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  quark_global_cache = g_quark_from_static_string ("gtk-global-cache");

  object_class->get_property = gtk_css_node_get_property;
  object_class->set_property = gtk_css_node_set_property;
  object_class->dispose = gtk_css_node_dispose;
  object_class->finalize = gtk_css_node_finalize;

  klass->update_style = gtk_css_node_real_update_style;
  klass->invalidate = gtk_css_node_real_invalidate;
  klass->validate = gtk_css_node_real_validate;
  klass->queue_validate = gtk_css_node_real_queue_validate;
  klass->dequeue_validate = gtk_css_node_real_dequeue_validate;
  klass->init_matcher = gtk_css_node_real_init_matcher;
  klass->create_widget_path = gtk_css_node_real_create_widget_path;
  klass->get_widget_path = gtk_css_node_real_get_widget_path;
  klass->get_style_provider = gtk_css_node_real_get_style_provider;
  klass->get_frame_clock = gtk_css_node_real_get_frame_clock;

  klass->node_added = gtk_css_node_real_node_added;
  klass->node_removed = gtk_css_node_real_node_removed;
  klass->style_changed = gtk_css_node_real_style_changed;

  cssnode_signals[NODE_ADDED] =
    g_signal_new (I_("node-added"),
		  G_TYPE_FROM_CLASS (object_class),
		  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkCssNodeClass, node_added),
		  NULL, NULL,
		  _gtk_marshal_VOID__OBJECT_OBJECT,
		  G_TYPE_NONE, 2,
		  GTK_TYPE_CSS_NODE, GTK_TYPE_CSS_NODE);
  cssnode_signals[NODE_REMOVED] =
    g_signal_new (I_("node-removed"),
		  G_TYPE_FROM_CLASS (object_class),
		  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkCssNodeClass, node_removed),
		  NULL, NULL,
		  _gtk_marshal_VOID__OBJECT_OBJECT,
		  G_TYPE_NONE, 2,
		  GTK_TYPE_CSS_NODE, GTK_TYPE_CSS_NODE);
  cssnode_signals[STYLE_CHANGED] =
    g_signal_new (I_("style-changed"),
		  G_TYPE_FROM_CLASS (object_class),
		  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkCssNodeClass, style_changed),
		  NULL, NULL,
		  _gtk_marshal_VOID__OBJECT_OBJECT,
		  G_TYPE_NONE, 2,
		  GTK_TYPE_CSS_STYLE, GTK_TYPE_CSS_STYLE);

  cssnode_properties[PROP_CLASSES] =
    g_param_spec_boxed ("classes", "Classes",
                         "List of classes",
                         G_TYPE_STRV,
                         G_PARAM_READWRITE
                         | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
  cssnode_properties[PROP_ID] =
    g_param_spec_string ("id", "ID",
                         "Unique ID",
                         NULL,
                         G_PARAM_READWRITE
                         | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
  cssnode_properties[PROP_NAME] =
    g_param_spec_string ("name", "Name",
                         "Name identifying the type of node",
                         NULL,
                         G_PARAM_READWRITE
                         | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
  cssnode_properties[PROP_STATE] =
    g_param_spec_flags ("state", "State",
                        "State flags",
                        GTK_TYPE_STATE_FLAGS,
                        0,
                        G_PARAM_READWRITE
                        | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
  cssnode_properties[PROP_VISIBLE] =
    g_param_spec_boolean ("visible", "Visible",
                          "If other nodes can see this node",
                          TRUE,
                          G_PARAM_READWRITE
                          | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
  cssnode_properties[PROP_WIDGET_TYPE] =
    g_param_spec_gtype ("widget-type", "Widget type",
                        "GType of the widget",
                        G_TYPE_NONE,
                        G_PARAM_READWRITE
                        | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  g_object_class_install_properties (object_class, NUM_PROPERTIES, cssnode_properties);
}

static void
gtk_css_node_init (GtkCssNode *cssnode)
{
  cssnode->decl = gtk_css_node_declaration_new ();

  cssnode->style = g_object_ref (gtk_css_static_style_get_default ());

  cssnode->visible = TRUE;
}

GtkCssNode *
gtk_css_node_new (void)
{
  return g_object_new (GTK_TYPE_CSS_NODE, NULL);
}

static GdkFrameClock *
gtk_css_node_get_frame_clock_or_null (GtkCssNode *cssnode)
{
  while (cssnode->parent)
    cssnode = cssnode->parent;

  return GTK_CSS_NODE_GET_CLASS (cssnode)->get_frame_clock (cssnode);
}

static gint64
gtk_css_node_get_timestamp (GtkCssNode *cssnode)
{
  GdkFrameClock *frameclock;

  frameclock = gtk_css_node_get_frame_clock_or_null (cssnode);
  if (frameclock == NULL)
    return 0;

  return gdk_frame_clock_get_frame_time (frameclock);
}

static void
gtk_css_node_parent_was_unset (GtkCssNode *node)
{
  if (node->invalid)
    GTK_CSS_NODE_GET_CLASS (node)->queue_validate (node);
}

static void
gtk_css_node_parent_will_be_set (GtkCssNode *node)
{
  if (node->invalid)
    GTK_CSS_NODE_GET_CLASS (node)->dequeue_validate (node);
}

static void
gtk_css_node_invalidate_style (GtkCssNode *cssnode)
{
  if (cssnode->style_is_invalid)
    return;

  cssnode->style_is_invalid = TRUE;
  gtk_css_node_set_invalid (cssnode, TRUE);
  
  if (cssnode->first_child)
    gtk_css_node_invalidate_style (cssnode->first_child);
  
  if (cssnode->next_sibling)
    gtk_css_node_invalidate_style (cssnode->next_sibling);
}

static void
gtk_css_node_reposition (GtkCssNode *node,
                         GtkCssNode *new_parent,
                         GtkCssNode *previous)
{
  GtkCssNode *old_parent;

  g_assert (! (new_parent == NULL && previous != NULL));

  old_parent = node->parent;
  /* Take a reference here so the whole function has a reference */
  g_object_ref (node);

  if (node->visible)
    {
      if (node->next_sibling)
        gtk_css_node_invalidate (node->next_sibling,
                                 GTK_CSS_CHANGE_ANY_SIBLING
                                 | GTK_CSS_CHANGE_NTH_CHILD
                                 | (node->previous_sibling ? 0 : GTK_CSS_CHANGE_FIRST_CHILD));
      else if (node->previous_sibling)
        gtk_css_node_invalidate (node->previous_sibling, GTK_CSS_CHANGE_LAST_CHILD);
    }

  if (old_parent != NULL)
    {
      g_signal_emit (old_parent, cssnode_signals[NODE_REMOVED], 0, node, node->previous_sibling);
      if (old_parent->first_child && node->visible)
        gtk_css_node_invalidate (old_parent->first_child, GTK_CSS_CHANGE_NTH_LAST_CHILD);
    }

  if (old_parent != new_parent)
    {
      if (old_parent == NULL)
        {
          gtk_css_node_parent_will_be_set (node);
        }
      else
        {
          g_object_unref (node);
        }

      if (gtk_css_node_get_style_provider_or_null (node) == NULL)
        gtk_css_node_invalidate_style_provider (node);
      gtk_css_node_invalidate (node, GTK_CSS_CHANGE_TIMESTAMP | GTK_CSS_CHANGE_ANIMATIONS);

      if (new_parent)
        {
          g_object_ref (node);

          if (node->pending_changes)
            new_parent->needs_propagation = TRUE;
          if (node->invalid && node->visible)
            gtk_css_node_set_invalid (new_parent, TRUE);
        }
      else
        {
          gtk_css_node_parent_was_unset (node);
        }
    }

  if (new_parent)
    {
      g_signal_emit (new_parent, cssnode_signals[NODE_ADDED], 0, node, previous);
      if (node->visible)
        gtk_css_node_invalidate (new_parent->first_child, GTK_CSS_CHANGE_NTH_LAST_CHILD);
    }

  if (node->visible)
    {
      if (node->next_sibling)
        {
          if (node->previous_sibling == NULL)
            gtk_css_node_invalidate (node->next_sibling, GTK_CSS_CHANGE_FIRST_CHILD);
          else
            gtk_css_node_invalidate_style (node->next_sibling);
        }
      else if (node->previous_sibling)
        {
          gtk_css_node_invalidate (node->previous_sibling, GTK_CSS_CHANGE_LAST_CHILD);
        }
    }
  else
    {
      if (node->next_sibling)
        gtk_css_node_invalidate_style (node->next_sibling);
    }

  gtk_css_node_invalidate (node, GTK_CSS_CHANGE_ANY_PARENT
                                 | GTK_CSS_CHANGE_ANY_SIBLING
                                 | GTK_CSS_CHANGE_NTH_CHILD
                                 | (node->previous_sibling ? 0 : GTK_CSS_CHANGE_FIRST_CHILD)
                                 | (node->next_sibling ? 0 : GTK_CSS_CHANGE_LAST_CHILD));

  g_object_unref (node);
}

void
gtk_css_node_set_parent (GtkCssNode *node,
                         GtkCssNode *parent)
{
  if (node->parent == parent)
    return;

  gtk_css_node_reposition (node, parent, parent ? parent->last_child : NULL);
}

void
gtk_css_node_insert_after (GtkCssNode *parent,
                           GtkCssNode *cssnode,
                           GtkCssNode *previous_sibling)
{
  g_return_if_fail (previous_sibling == NULL || previous_sibling->parent == parent);

  if (cssnode->previous_sibling == previous_sibling &&
      cssnode->parent == parent)
    return;

  gtk_css_node_reposition (cssnode,
                           parent,
                           previous_sibling);
}

void
gtk_css_node_insert_before (GtkCssNode *parent,
                            GtkCssNode *cssnode,
                            GtkCssNode *next_sibling)
{
  g_return_if_fail (next_sibling == NULL || next_sibling->parent == parent);

  if (cssnode->next_sibling == next_sibling && 
      cssnode->parent == parent)
    return;

  gtk_css_node_reposition (cssnode,
                           parent,
                           next_sibling ? next_sibling->previous_sibling : parent->last_child);
}

void
gtk_css_node_reverse_children (GtkCssNode *cssnode)
{
  GtkCssNode *end;

  end = cssnode->last_child;
  while (cssnode->first_child != end)
    {
      gtk_css_node_reposition (cssnode->first_child,
                               cssnode,
                               end);
    }

}

GtkCssNode *
gtk_css_node_get_parent (GtkCssNode *cssnode)
{
  return cssnode->parent;
}

GtkCssNode *
gtk_css_node_get_first_child (GtkCssNode *cssnode)
{
  return cssnode->first_child;
}

GtkCssNode *
gtk_css_node_get_last_child (GtkCssNode *cssnode)
{
  return cssnode->last_child;
}

GtkCssNode *
gtk_css_node_get_previous_sibling (GtkCssNode *cssnode)
{
  return cssnode->previous_sibling;
}

GtkCssNode *
gtk_css_node_get_next_sibling (GtkCssNode *cssnode)
{
  return cssnode->next_sibling;
}

static gboolean
gtk_css_node_set_style (GtkCssNode  *cssnode,
                        GtkCssStyle *style)
{
  if (cssnode->style == style)
    return FALSE;

  g_signal_emit (cssnode, cssnode_signals[STYLE_CHANGED], 0, cssnode->style, style);
  return TRUE;
}

static void
gtk_css_node_propagate_pending_changes (GtkCssNode *cssnode,
                                        gboolean    style_changed)
{
  GtkCssChange change, child_change;
  GtkCssNode *child;

  change = _gtk_css_change_for_child (cssnode->pending_changes);
  if (style_changed)
    change |= GTK_CSS_CHANGE_PARENT_STYLE;

  if (!cssnode->needs_propagation && change == 0)
    return;

  for (child = gtk_css_node_get_first_child (cssnode);
       child;
       child = gtk_css_node_get_next_sibling (child))
    {
      child_change = child->pending_changes;
      gtk_css_node_invalidate (child, change);
      if (child->visible)
        change |= _gtk_css_change_for_sibling (child_change);
    }

  cssnode->needs_propagation = FALSE;
}

static gboolean
gtk_css_node_needs_new_style (GtkCssNode *cssnode)
{
  return cssnode->style_is_invalid || cssnode->needs_propagation;
}

static void
gtk_css_node_ensure_style (GtkCssNode *cssnode,
                           gint64      current_time)
{
  gboolean style_changed;

  if (!gtk_css_node_needs_new_style (cssnode))
    return;

  if (cssnode->parent)
    gtk_css_node_ensure_style (cssnode->parent, current_time);

  if (cssnode->style_is_invalid)
    {
      GtkCssStyle *new_style;

      if (cssnode->previous_sibling)
        gtk_css_node_ensure_style (cssnode->previous_sibling, current_time);

      new_style = GTK_CSS_NODE_GET_CLASS (cssnode)->update_style (cssnode,
                                                                  cssnode->pending_changes,
                                                                  current_time,
                                                                  cssnode->style);

      style_changed = gtk_css_node_set_style (cssnode, new_style);
      g_object_unref (new_style);
    }
  else
    {
      style_changed = FALSE;
    }

  gtk_css_node_propagate_pending_changes (cssnode, style_changed);

  cssnode->pending_changes = 0;
  cssnode->style_is_invalid = FALSE;
}

GtkCssStyle *
gtk_css_node_get_style (GtkCssNode *cssnode)
{
  if (gtk_css_node_needs_new_style (cssnode))
    {
      gint64 timestamp = gtk_css_node_get_timestamp (cssnode);

      gtk_css_node_ensure_style (cssnode, timestamp);
    }

  return cssnode->style;
}

void
gtk_css_node_set_visible (GtkCssNode *cssnode,
                          gboolean    visible)
{
  if (cssnode->visible == visible)
    return;

  cssnode->visible = visible;
  g_object_notify_by_pspec (G_OBJECT (cssnode), cssnode_properties[PROP_VISIBLE]);

  if (cssnode->invalid)
    {
      if (cssnode->visible)
        {
          if (cssnode->parent)
            gtk_css_node_set_invalid (cssnode->parent, TRUE);
          else
            GTK_CSS_NODE_GET_CLASS (cssnode)->queue_validate (cssnode);
        }
      else
        {
          if (cssnode->parent == NULL)
            GTK_CSS_NODE_GET_CLASS (cssnode)->dequeue_validate (cssnode);
        }
    }

  if (cssnode->next_sibling)
    gtk_css_node_invalidate (cssnode->next_sibling, GTK_CSS_CHANGE_ANY_SIBLING
                                                    | GTK_CSS_CHANGE_NTH_CHILD
                                                    | (cssnode->previous_sibling ? 0 : GTK_CSS_CHANGE_FIRST_CHILD));

  if (cssnode->previous_sibling)
    gtk_css_node_invalidate (cssnode->previous_sibling, GTK_CSS_CHANGE_NTH_LAST_CHILD
                                                        | (cssnode->next_sibling ? 0 : GTK_CSS_CHANGE_LAST_CHILD));
}

gboolean
gtk_css_node_get_visible (GtkCssNode *cssnode)
{
  return cssnode->visible;
}

void
gtk_css_node_set_name (GtkCssNode              *cssnode,
                       /*interned*/ const char *name)
{
  if (gtk_css_node_declaration_set_name (&cssnode->decl, name))
    {
      gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_NAME);
      g_object_notify_by_pspec (G_OBJECT (cssnode), cssnode_properties[PROP_NAME]);
      g_object_notify_by_pspec (G_OBJECT (cssnode), cssnode_properties[PROP_WIDGET_TYPE]);
    }
}

/* interned */ const char *
gtk_css_node_get_name (GtkCssNode *cssnode)
{
  return gtk_css_node_declaration_get_name (cssnode->decl);
}

void
gtk_css_node_set_widget_type (GtkCssNode *cssnode,
                              GType       widget_type)
{
  if (gtk_css_node_declaration_set_type (&cssnode->decl, widget_type))
    {
      gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_NAME);
      g_object_notify_by_pspec (G_OBJECT (cssnode), cssnode_properties[PROP_NAME]);
      g_object_notify_by_pspec (G_OBJECT (cssnode), cssnode_properties[PROP_WIDGET_TYPE]);
    }
}

GType
gtk_css_node_get_widget_type (GtkCssNode *cssnode)
{
  return gtk_css_node_declaration_get_type (cssnode->decl);
}

void
gtk_css_node_set_id (GtkCssNode                *cssnode,
                     /* interned */ const char *id)
{
  if (gtk_css_node_declaration_set_id (&cssnode->decl, id))
    {
      gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_ID);
      g_object_notify_by_pspec (G_OBJECT (cssnode), cssnode_properties[PROP_ID]);
    }
}

/* interned */ const char *
gtk_css_node_get_id (GtkCssNode *cssnode)
{
  return gtk_css_node_declaration_get_id (cssnode->decl);
}

void
gtk_css_node_set_state (GtkCssNode    *cssnode,
                        GtkStateFlags  state_flags)
{
  if (gtk_css_node_declaration_set_state (&cssnode->decl, state_flags))
    {
      gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_STATE);
      g_object_notify_by_pspec (G_OBJECT (cssnode), cssnode_properties[PROP_STATE]);
    }
}

GtkStateFlags
gtk_css_node_get_state (GtkCssNode *cssnode)
{
  return gtk_css_node_declaration_get_state (cssnode->decl);
}

void
gtk_css_node_set_junction_sides (GtkCssNode       *cssnode,
                                 GtkJunctionSides  junction_sides)
{
  gtk_css_node_declaration_set_junction_sides (&cssnode->decl, junction_sides);
}

GtkJunctionSides
gtk_css_node_get_junction_sides (GtkCssNode *cssnode)
{
  return gtk_css_node_declaration_get_junction_sides (cssnode->decl);
}

static void
gtk_css_node_clear_classes (GtkCssNode *cssnode)
{
  if (gtk_css_node_declaration_clear_classes (&cssnode->decl))
    {
      gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_CLASS);
      g_object_notify_by_pspec (G_OBJECT (cssnode), cssnode_properties[PROP_CLASSES]);
    }
}

void
gtk_css_node_set_classes (GtkCssNode  *cssnode,
                          const char **classes)
{
  guint i;

  g_object_freeze_notify (G_OBJECT (cssnode));

  gtk_css_node_clear_classes (cssnode);

  if (classes)
    {
      for (i = 0; classes[i] != NULL; i++)
        {
          gtk_css_node_add_class (cssnode, g_quark_from_string (classes[i]));
        }
    }

  g_object_thaw_notify (G_OBJECT (cssnode));
}

char **
gtk_css_node_get_classes (GtkCssNode *cssnode)
{
  const GQuark *classes;
  char **result;
  guint n_classes, i, j;

  classes = gtk_css_node_declaration_get_classes (cssnode->decl, &n_classes);
  result = g_new (char *, n_classes + 1);

  for (i = n_classes, j = 0; i-- > 0; ++j)
    {
      result[j] = g_strdup (g_quark_to_string (classes[i]));
    }

  result[n_classes] = NULL;
  return result;
}

void
gtk_css_node_add_class (GtkCssNode *cssnode,
                        GQuark      style_class)
{
  if (gtk_css_node_declaration_add_class (&cssnode->decl, style_class))
    {
      gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_CLASS);
      g_object_notify_by_pspec (G_OBJECT (cssnode), cssnode_properties[PROP_CLASSES]);
    }
}

void
gtk_css_node_remove_class (GtkCssNode *cssnode,
                           GQuark      style_class)
{
  if (gtk_css_node_declaration_remove_class (&cssnode->decl, style_class))
    {
      gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_CLASS);
      g_object_notify_by_pspec (G_OBJECT (cssnode), cssnode_properties[PROP_CLASSES]);
    }
}

gboolean
gtk_css_node_has_class (GtkCssNode *cssnode,
                        GQuark      style_class)
{
  return gtk_css_node_declaration_has_class (cssnode->decl, style_class);
}

const GQuark *
gtk_css_node_list_classes (GtkCssNode *cssnode,
                           guint      *n_classes)
{
  return gtk_css_node_declaration_get_classes (cssnode->decl, n_classes);
}

void
gtk_css_node_add_region (GtkCssNode     *cssnode,
                         GQuark          region,
                         GtkRegionFlags  flags)
{
  gtk_css_node_declaration_add_region (&cssnode->decl, region, flags);
}

void
gtk_css_node_remove_region (GtkCssNode *cssnode,
                            GQuark      region)
{
  gtk_css_node_declaration_remove_region (&cssnode->decl, region);
}

gboolean
gtk_css_node_has_region (GtkCssNode     *cssnode,
                         GQuark          region,
                         GtkRegionFlags *out_flags)
{
  return gtk_css_node_declaration_has_region (cssnode->decl, region, out_flags);
}

GList *
gtk_css_node_list_regions (GtkCssNode *cssnode)
{
  return gtk_css_node_declaration_list_regions (cssnode->decl);
}


const GtkCssNodeDeclaration *
gtk_css_node_get_declaration (GtkCssNode *cssnode)
{
  return cssnode->decl;
}

void
gtk_css_node_invalidate_style_provider (GtkCssNode *cssnode)
{
  GtkCssNode *child;

  gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_SOURCE);

  for (child = cssnode->first_child;
       child;
       child = child->next_sibling)
    {
      if (gtk_css_node_get_style_provider_or_null (child) == NULL)
        gtk_css_node_invalidate_style_provider (child);
    }
}

static void
gtk_css_node_invalidate_timestamp (GtkCssNode *cssnode)
{
  GtkCssNode *child;

  if (!cssnode->invalid)
    return;

  if (!gtk_css_style_is_static (cssnode->style))
    gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_TIMESTAMP);

  for (child = cssnode->first_child; child; child = child->next_sibling)
    {
      gtk_css_node_invalidate_timestamp (child);
    }
}

void
gtk_css_node_invalidate_frame_clock (GtkCssNode *cssnode,
                                     gboolean    just_timestamp)
{
  /* frame clock is handled by the top level */
  if (cssnode->parent)
    return;

  gtk_css_node_invalidate_timestamp (cssnode);

  if (!just_timestamp)
    gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_ANIMATIONS);
}

void
gtk_css_node_invalidate (GtkCssNode   *cssnode,
                         GtkCssChange  change)
{
  if (!cssnode->invalid)
    change &= ~GTK_CSS_CHANGE_TIMESTAMP;

  if (change == 0)
    return;

  cssnode->pending_changes |= change;

  GTK_CSS_NODE_GET_CLASS (cssnode)->invalidate (cssnode);

  if (cssnode->parent)
    cssnode->parent->needs_propagation = TRUE;
  gtk_css_node_invalidate_style (cssnode);
}

void
gtk_css_node_validate_internal (GtkCssNode *cssnode,
                                gint64      timestamp)
{
  GtkCssNode *child;

  /* If you run your application with
   *   GTK_DEBUG=no-css-cache
   * every invalidation will purge the cache and completely query
   * everything anew form the cache. This is slow (in particular
   * when animating), but useful for figuring out bugs.
   *
   * We achieve that by pretending that everything that could have
   * changed has and so we of course totally need to redo everything.
   *
   * Note that this also completely revalidates child widgets all
   * the time.
   */
#ifdef G_ENABLE_DEBUG
  if (GTK_DEBUG_CHECK (NO_CSS_CACHE))
    cssnode->pending_changes |= GTK_CSS_CHANGE_ANY;
#endif

  if (!cssnode->invalid)
    return;

  gtk_css_node_ensure_style (cssnode, timestamp);

  /* need to set to FALSE then to TRUE here to make it chain up */
  gtk_css_node_set_invalid (cssnode, FALSE);
  if (!gtk_css_style_is_static (cssnode->style))
    gtk_css_node_set_invalid (cssnode, TRUE);

  GTK_CSS_NODE_GET_CLASS (cssnode)->validate (cssnode);

  for (child = gtk_css_node_get_first_child (cssnode);
       child;
       child = gtk_css_node_get_next_sibling (child))
    {
      if (child->visible)
        gtk_css_node_validate_internal (child, timestamp);
    }
}

void
gtk_css_node_validate (GtkCssNode *cssnode)
{
  gint64 timestamp;

  timestamp = gtk_css_node_get_timestamp (cssnode);

  gtk_css_node_validate_internal (cssnode, timestamp);
}

gboolean
gtk_css_node_init_matcher (GtkCssNode     *cssnode,
                           GtkCssMatcher  *matcher)
{
  return GTK_CSS_NODE_GET_CLASS (cssnode)->init_matcher (cssnode, matcher);
}

GtkWidgetPath *
gtk_css_node_create_widget_path (GtkCssNode *cssnode)
{
  return GTK_CSS_NODE_GET_CLASS (cssnode)->create_widget_path (cssnode);
}

const GtkWidgetPath *
gtk_css_node_get_widget_path (GtkCssNode *cssnode)
{
  return GTK_CSS_NODE_GET_CLASS (cssnode)->get_widget_path (cssnode);
}

GtkStyleProviderPrivate *
gtk_css_node_get_style_provider (GtkCssNode *cssnode)
{
  GtkStyleProviderPrivate *result;
  
  result = gtk_css_node_get_style_provider_or_null (cssnode);
  if (result)
    return result;

  if (cssnode->parent)
    return gtk_css_node_get_style_provider (cssnode->parent);

  return GTK_STYLE_PROVIDER_PRIVATE (_gtk_settings_get_style_cascade (gtk_settings_get_default (), 1));
}
