#pragma once

#include "gdkcolorstate.h"

#include "gdkcicpparamsprivate.h"
#include "gdkdebugprivate.h"
#include "gdkmemoryformatprivate.h"
#include "gdkrgba.h"

typedef enum
{
  GDK_COLOR_STATE_ID_SRGB,
  GDK_COLOR_STATE_ID_SRGB_LINEAR,
  GDK_COLOR_STATE_ID_REC2100_PQ,
  GDK_COLOR_STATE_ID_REC2100_LINEAR,

  GDK_COLOR_STATE_N_IDS
} GdkColorStateId;

typedef struct _GdkColorStateClass GdkColorStateClass;

struct _GdkColorState
{
  const GdkColorStateClass *klass;
  gatomicrefcount ref_count;

  GdkMemoryDepth depth;
  GdkColorState *rendering_color_state;
  GdkColorState *rendering_color_state_linear;
};

/* Note: self may be the source or the target colorstate */
typedef void            (* GdkFloatColorConvert)(GdkColorState  *self,
                                                 float         (*values)[4],
                                                 gsize           n_values);

struct _GdkColorStateClass
{
  void                  (* free)                (GdkColorState  *self);
  gboolean              (* equal)               (GdkColorState  *self,
                                                 GdkColorState  *other);
  const char *          (* get_name)            (GdkColorState  *self);
  GdkColorState *       (* get_no_srgb_tf)      (GdkColorState  *self);
  GdkFloatColorConvert  (* get_convert_to)      (GdkColorState  *self,
                                                 GdkColorState  *target);
  GdkFloatColorConvert  (* get_convert_from)    (GdkColorState  *self,
                                                 GdkColorState  *source);
  const GdkCicp *       (* get_cicp)            (GdkColorState  *self);
  void                  (* clamp)               (GdkColorState  *self,
                                                 const float     src[4],
                                                 float           dest[4]);
};

typedef struct _GdkDefaultColorState GdkDefaultColorState;

struct _GdkDefaultColorState
{
  GdkColorState parent;

  const char *name;
  GdkColorState *no_srgb;
  GdkFloatColorConvert convert_to[GDK_COLOR_STATE_N_IDS];
  void (* clamp) (GdkColorState  *self,
                  const float     src[4],
                  float           dest[4]);

  GdkCicp cicp;
};

extern GdkDefaultColorState gdk_default_color_states[GDK_COLOR_STATE_N_IDS];

#define GDK_COLOR_STATE_SRGB           ((GdkColorState *) &gdk_default_color_states[GDK_COLOR_STATE_ID_SRGB])
#define GDK_COLOR_STATE_SRGB_LINEAR    ((GdkColorState *) &gdk_default_color_states[GDK_COLOR_STATE_ID_SRGB_LINEAR])
#define GDK_COLOR_STATE_REC2100_PQ     ((GdkColorState *) &gdk_default_color_states[GDK_COLOR_STATE_ID_REC2100_PQ])
#define GDK_COLOR_STATE_REC2100_LINEAR ((GdkColorState *) &gdk_default_color_states[GDK_COLOR_STATE_ID_REC2100_LINEAR])

#define GDK_IS_DEFAULT_COLOR_STATE(c) ((GdkDefaultColorState *) (c) >= &gdk_default_color_states[0] && \
                                       (GdkDefaultColorState *) (c) < &gdk_default_color_states[GDK_COLOR_STATE_N_IDS])
#define GDK_DEFAULT_COLOR_STATE_ID(c) ((GdkColorStateId) (((GdkDefaultColorState *) c) - gdk_default_color_states))

const char *    gdk_color_state_get_name                (GdkColorState          *color_state);
GdkColorState * gdk_color_state_get_no_srgb_tf          (GdkColorState          *self);

GdkColorState * gdk_color_state_new_for_cicp            (const GdkCicp          *cicp,
                                                         GError                **error);

void            gdk_color_state_clamp                   (GdkColorState          *self,
                                                         const float             src[4],
                                                         float                   dest[4]);

static inline GdkColorState *
gdk_color_state_get_rendering_color_state (GdkColorState *self)
{
  if (GDK_DEBUG_CHECK (HDR))
    self = GDK_COLOR_STATE_REC2100_PQ;

  if (!GDK_DEBUG_CHECK (LINEAR))
    return self->rendering_color_state;

  return self->rendering_color_state_linear;
}

static inline GdkMemoryDepth
gdk_color_state_get_depth (GdkColorState *self)
{
  if (!GDK_DEBUG_CHECK (LINEAR) && self->depth == GDK_MEMORY_U8_SRGB)
    return GDK_MEMORY_U8;

  return self->depth;
}

static inline GdkColorState *
gdk_color_state_get_by_id (GdkColorStateId id)
{
  return (GdkColorState *) &gdk_default_color_states[id];
}

#define gdk_color_state_ref(self) _gdk_color_state_ref (self)
static inline GdkColorState *
_gdk_color_state_ref (GdkColorState *self)
{
  if (GDK_IS_DEFAULT_COLOR_STATE (self))
    return self;

  g_atomic_ref_count_inc (&self->ref_count);

  return self;
}

#define gdk_color_state_unref(self) _gdk_color_state_unref (self)
static inline void
_gdk_color_state_unref (GdkColorState *self)
{
  if (GDK_IS_DEFAULT_COLOR_STATE (self))
    return;

  if (g_atomic_ref_count_dec (&self->ref_count))
    self->klass->free (self);
}

#define gdk_color_state_equal(a,b) _gdk_color_state_equal ((a), (b))
static inline gboolean
_gdk_color_state_equal (GdkColorState *self,
                        GdkColorState *other)
{
  if (self == other)
    return TRUE;

  if (self->klass != other->klass)
    return FALSE;

  return self->klass->equal (self, other);
}

/* Note: the functions returned from this expect the source
 * color state to be passed as self
 */
static inline GdkFloatColorConvert
gdk_color_state_get_convert_to (GdkColorState *self,
                                GdkColorState *target)
{
  return self->klass->get_convert_to (self, target);
}

/* Note: the functions returned from this expect the target
 * color state to be passed as self
 */
static inline GdkFloatColorConvert
gdk_color_state_get_convert_from (GdkColorState *self,
                                  GdkColorState *source)
{
  return self->klass->get_convert_from (self, source);
}

static inline const GdkCicp *
gdk_color_state_get_cicp (GdkColorState *self)
{
  return self->klass->get_cicp (self);
}

static inline void
gdk_color_state_convert_color (GdkColorState *src_cs,
                               const float    src[4],
                               GdkColorState *dest_cs,
                               float          dest[4])
{
  GdkFloatColorConvert convert = NULL;
  GdkFloatColorConvert convert2 = NULL;

  dest[0] = src[0];
  dest[1] = src[1];
  dest[2] = src[2];
  dest[3] = src[3];

  if (gdk_color_state_equal (src_cs, dest_cs))
    return;

  convert = gdk_color_state_get_convert_to (src_cs, dest_cs);

  if (!convert)
    convert2 = gdk_color_state_get_convert_from (dest_cs, src_cs);

  if (!convert && !convert2)
    {
      GdkColorState *connection = GDK_COLOR_STATE_REC2100_LINEAR;
      convert = gdk_color_state_get_convert_to (src_cs, connection);
      convert2 = gdk_color_state_get_convert_from (dest_cs, connection);
    }

  if (convert)
    convert (src_cs,  (float(*)[4]) dest, 1);

  if (convert2)
    convert2 (dest_cs, (float(*)[4]) dest, 1);
}

static inline void
gdk_color_state_from_rgba (GdkColorState *self,
                           const GdkRGBA *rgba,
                           float          out_color[4])
{
  gdk_color_state_convert_color (GDK_COLOR_STATE_SRGB,
                                 (const float *) rgba,
                                 self,
                                 out_color);
}
