#pragma once

#include "gskrendernode.h"
#include <cairo.h>

#include "gdk/gdkmemoryformatprivate.h"
#include "gdk/gdkcolorprivate.h"
#include "gskgradientprivate.h"
#include "gpu/gskgputypesprivate.h"

G_BEGIN_DECLS

typedef enum {
  /* No pixels are copied */
  GSK_COPY_NONE = 0,
  /* Pixels that are copied will be pasted to the same pixel */
  GSK_COPY_SAME_PIXEL,
  /* Pixels are copied and and will be pasted whereever */
  GSK_COPY_ANY
} GskCopyMode;

#define GSK_COPY_MODE_BITS 2

typedef struct _GskRenderNodeClass GskRenderNodeClass;

#define GSK_IS_RENDER_NODE_TYPE(node,type) \
  (G_TYPE_INSTANCE_GET_CLASS ((node), GSK_TYPE_RENDER_NODE, GskRenderNodeClass)->node_type == (type))

#define GSK_RENDER_NODE_TYPE(node) \
  (G_TYPE_INSTANCE_GET_CLASS ((node), GSK_TYPE_RENDER_NODE, GskRenderNodeClass)->node_type)

struct _GskRenderNode
{
  GTypeInstance parent_instance;

  gatomicrefcount ref_count;

  graphene_rect_t bounds;

  guint preferred_depth : GDK_MEMORY_DEPTH_BITS;
  guint copy_mode : GSK_COPY_MODE_BITS;
  guint fully_opaque : 1; /* TRUE if the node is opaque in its bounds */
  guint bilevel_opacity : 1; /* TRUE if the node is either opaque or fully transparent at every coord */
  guint is_hdr : 1;
  guint isolates_background : 1; /* TRUE if children are drawn to an empty background */
  guint clears_background : 1; /* mostly relevant for tracking opacity */
  guint contains_subsurface_node : 1; /* contains a subsurface node */
  guint contains_paste_node : 1; /* contains a paste node that has no matching copy node */
  guint needs_blending : 1; /* This node cannot be drawn with glDisable (GL_BLEND) */
};

typedef struct
{
  cairo_region_t *region;
  GSList *copies;
  GdkSurface *surface;
} GskDiffData;

typedef struct
{
  graphene_rect_t opaque;
  GSList *copies;
} GskOpacityData;

#define GSK_OPACITY_DATA_INIT_EMPTY(copies) { GRAPHENE_RECT_INIT(0, 0, 0, 0), (copies) }
#define GSK_OPACITY_DATA_INIT_COPY(data) (*(data))

typedef struct
{
  GdkColorState *ccs;
} GskCairoData;

struct _GskRenderNodeClass
{
  GTypeClass parent_class;

  GskRenderNodeType node_type;

  void          (* finalize)                            (GskRenderNode               *node);
  void          (* draw)                                (GskRenderNode               *node,
                                                         cairo_t                     *cr,
                                                         GskCairoData                *data);
  gboolean      (* can_diff)                            (const GskRenderNode         *node1,
                                                         const GskRenderNode         *node2);
  void          (* diff)                                (GskRenderNode               *node1,
                                                         GskRenderNode               *node2,
                                                         GskDiffData                 *data);
  GskRenderNode**(* get_children)                       (GskRenderNode               *node,
                                                         gsize                       *n_children);
  GskRenderNode*(* replay)                              (GskRenderNode               *node,
                                                         GskRenderReplay             *replay);
  void          (* render_opacity)                      (GskRenderNode               *node,
                                                         GskOpacityData              *data);

  /* GPU renderer */
  GskGpuRenderPass *
                (* occlusion)                           (GskRenderNode               *node,
                                                         GskGpuOcclusion             *occlusion);
};

void            gsk_render_node_init_types              (void);

#define GSK_DEFINE_RENDER_NODE_TYPE(TypeName, type_name) \
GType \
type_name##_get_type (void) \
{ \
  static _g_type_once_init_type static_g_define_type_id = 0; \
  if (_g_type_once_init_enter (&static_g_define_type_id)) \
    { \
      GType g_define_type_id = gsk_render_node_type_register_static (g_intern_static_string (#TypeName), \
                                                                     sizeof (TypeName), \
                                                                     type_name ## _class_init); \
      _g_type_once_init_leave (&static_g_define_type_id, g_define_type_id); \
    } \
  return static_g_define_type_id; \
}

GType           gsk_render_node_type_register_static    (const char                  *node_name,
                                                         gsize                        instance_size,
                                                         GClassInitFunc               class_init);

gpointer        gsk_render_node_alloc                   (GType                        node_type);

void            _gsk_render_node_unref                  (GskRenderNode               *node);

gboolean        gsk_render_node_can_diff                (const GskRenderNode         *node1,
                                                         const GskRenderNode         *node2) G_GNUC_PURE;
void            gsk_render_node_diff                    (GskRenderNode               *node1,
                                                         GskRenderNode               *node2,
                                                         GskDiffData                 *data);
void            gsk_render_node_diff_impossible         (GskRenderNode               *node1,
                                                         GskRenderNode               *node2,
                                                         GskDiffData                 *data);
void            gsk_render_node_draw_full               (GskRenderNode               *node,
                                                         cairo_t                     *cr,
                                                         GskCairoData                *data);
void            gsk_render_node_draw_with_color_state   (GskRenderNode               *node,
                                                         cairo_t                     *cr,
                                                         GdkColorState               *color_state);
void            gsk_render_node_draw_fallback           (GskRenderNode               *node,
                                                         cairo_t                     *cr);
void            gsk_render_node_render_opacity          (GskRenderNode               *self,
                                                         GskOpacityData              *data);

GdkMemoryDepth  gsk_render_node_get_preferred_depth     (const GskRenderNode         *node) G_GNUC_PURE;
gboolean        gsk_render_node_is_hdr                  (const GskRenderNode         *node) G_GNUC_PURE;
gboolean        gsk_render_node_is_fully_opaque         (const GskRenderNode         *node) G_GNUC_PURE;
gboolean        gsk_render_node_is_bilevel_opacity      (const GskRenderNode         *node) G_GNUC_PURE;
gboolean        gsk_render_node_isolates_background     (const GskRenderNode         *node) G_GNUC_PURE;
gboolean        gsk_render_node_clears_background       (const GskRenderNode         *node) G_GNUC_PURE;
GskCopyMode     gsk_render_node_get_copy_mode           (const GskRenderNode         *node) G_GNUC_PURE;
gboolean        gsk_render_node_contains_subsurface_node(const GskRenderNode         *node) G_GNUC_PURE;
gboolean        gsk_render_node_contains_paste_node     (const GskRenderNode         *node) G_GNUC_PURE;
gboolean        gsk_render_node_needs_blending          (const GskRenderNode         *node) G_GNUC_PURE;

#define gsk_render_node_ref(node)   _gsk_render_node_ref(node)
#define gsk_render_node_unref(node) _gsk_render_node_unref(node)

static inline GskRenderNode *
_gsk_render_node_ref (GskRenderNode *node)
{
  g_atomic_ref_count_inc (&node->ref_count);
  return node;
}

#define GSK_RENDER_NODE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_RENDER_NODE, GskRenderNodeClass))

#define gsk_render_node_get_node_type(node) _gsk_render_node_get_node_type (node)

G_GNUC_PURE static inline
GskRenderNodeType
_gsk_render_node_get_node_type (const GskRenderNode *node)
{
  return GSK_RENDER_NODE_GET_CLASS (node)->node_type;
}

const GskGradient * gsk_gradient_node_get_gradient      (const GskRenderNode *node);

G_END_DECLS
