#include <gtk/gtk.h>

static const char *format_name[] = {
  "BGRAp", "ARGBp", "RGBAp",
  "BGRA", "ARGB", "RGBA", "ABGR",
  "RGB", "BGR",
};

static const char *
format_to_string (GdkMemoryFormat format)
{
  if (format < GDK_MEMORY_N_FORMATS)
    return format_name[format];
  else
    return "ERROR";
}

/* Copied from gdkmemorytexture.c */

static void
convert_memcpy (guchar       *dest_data,
                gsize         dest_stride,
                const guchar *src_data,
                gsize         src_stride,
                gsize         width,
                gsize         height)
{
  gsize y;

  for (y = 0; y < height; y++)
    memcpy (dest_data + y * dest_stride, src_data + y * src_stride, 4 * width);
}

static void
convert_memcpy3 (guchar       *dest_data,
                 gsize         dest_stride,
                 const guchar *src_data,
                 gsize         src_stride,
                 gsize         width,
                 gsize         height)
{
  gsize y;

  for (y = 0; y < height; y++)
    memcpy (dest_data + y * dest_stride, src_data + y * src_stride, 3 * width);
}

#define SWIZZLE3(R,G,B) \
static void \
convert_swizzle ## R ## G ## B (guchar       *dest_data, \
                                gsize         dest_stride, \
                                const guchar *src_data, \
                                gsize         src_stride, \
                                gsize         width, \
                                gsize         height) \
{ \
  gsize x, y; \
\
  for (y = 0; y < height; y++) \
    { \
      for (x = 0; x < width; x++) \
        { \
          dest_data[3 * x + R] = src_data[3 * x + 0]; \
          dest_data[3 * x + G] = src_data[3 * x + 1]; \
          dest_data[3 * x + B] = src_data[3 * x + 2]; \
        } \
\
      dest_data += dest_stride; \
      src_data += src_stride; \
    } \
}

SWIZZLE3(2,1,0)

#define SWIZZLE(A,R,G,B) \
static void \
convert_swizzle ## A ## R ## G ## B (guchar       *dest_data, \
                                     gsize         dest_stride, \
                                     const guchar *src_data, \
                                     gsize         src_stride, \
                                     gsize         width, \
                                     gsize         height) \
{ \
  gsize x, y; \
\
  for (y = 0; y < height; y++) \
    { \
      for (x = 0; x < width; x++) \
        { \
          dest_data[4 * x + A] = src_data[4 * x + 0]; \
          dest_data[4 * x + R] = src_data[4 * x + 1]; \
          dest_data[4 * x + G] = src_data[4 * x + 2]; \
          dest_data[4 * x + B] = src_data[4 * x + 3]; \
        } \
\
      dest_data += dest_stride; \
      src_data += src_stride; \
    } \
}

SWIZZLE(3,2,1,0)
SWIZZLE(2,1,0,3)
SWIZZLE(3,0,1,2)
SWIZZLE(1,2,3,0)

#define SWIZZLE_OPAQUE(A,R,G,B) \
static void \
convert_swizzle_opaque_## A ## R ## G ## B (guchar       *dest_data, \
                                            gsize         dest_stride, \
                                            const guchar *src_data, \
                                            gsize         src_stride, \
                                            gsize         width, \
                                            gsize         height) \
{ \
  gsize x, y; \
\
  for (y = 0; y < height; y++) \
    { \
      for (x = 0; x < width; x++) \
        { \
          dest_data[4 * x + A] = 0xFF; \
          dest_data[4 * x + R] = src_data[3 * x + 0]; \
          dest_data[4 * x + G] = src_data[3 * x + 1]; \
          dest_data[4 * x + B] = src_data[3 * x + 2]; \
        } \
\
      dest_data += dest_stride; \
      src_data += src_stride; \
    } \
}

SWIZZLE_OPAQUE(3,2,1,0)
SWIZZLE_OPAQUE(3,0,1,2)
SWIZZLE_OPAQUE(0,1,2,3)
SWIZZLE_OPAQUE(0,3,2,1)

#define PREMULTIPLY(d,c,a) G_STMT_START { guint t = c * a + 0x80; d = ((t >> 8) + t) >> 8; } G_STMT_END
#define SWIZZLE_PREMULTIPLY(A,R,G,B, A2,R2,G2,B2) \
static void \
convert_swizzle_premultiply_ ## A ## R ## G ## B ## _ ## A2 ## R2 ## G2 ## B2 \
                                    (guchar       *dest_data, \
                                     gsize         dest_stride, \
                                     const guchar *src_data, \
                                     gsize         src_stride, \
                                     gsize         width, \
                                     gsize         height) \
{ \
  gsize x, y; \
\
  for (y = 0; y < height; y++) \
    { \
      for (x = 0; x < width; x++) \
        { \
          dest_data[4 * x + A] = src_data[4 * x + A2]; \
          PREMULTIPLY(dest_data[4 * x + R], src_data[4 * x + R2], src_data[4 * x + A2]); \
          PREMULTIPLY(dest_data[4 * x + G], src_data[4 * x + G2], src_data[4 * x + A2]); \
          PREMULTIPLY(dest_data[4 * x + B], src_data[4 * x + B2], src_data[4 * x + A2]); \
        } \
\
      dest_data += dest_stride; \
      src_data += src_stride; \
    } \
}

SWIZZLE_PREMULTIPLY (3,2,1,0, 3,2,1,0)
SWIZZLE_PREMULTIPLY (0,1,2,3, 3,2,1,0)
SWIZZLE_PREMULTIPLY (3,2,1,0, 0,1,2,3)
SWIZZLE_PREMULTIPLY (0,1,2,3, 0,1,2,3)
SWIZZLE_PREMULTIPLY (3,2,1,0, 3,0,1,2)
SWIZZLE_PREMULTIPLY (0,1,2,3, 3,0,1,2)
SWIZZLE_PREMULTIPLY (3,2,1,0, 0,3,2,1)
SWIZZLE_PREMULTIPLY (0,1,2,3, 0,3,2,1)
SWIZZLE_PREMULTIPLY (3,0,1,2, 3,2,1,0)
SWIZZLE_PREMULTIPLY (3,0,1,2, 0,1,2,3)
SWIZZLE_PREMULTIPLY (3,0,1,2, 3,0,1,2)
SWIZZLE_PREMULTIPLY (3,0,1,2, 0,3,2,1)

typedef void (* ConversionFunc) (guchar       *dest_data,
                                 gsize         dest_stride,
                                 const guchar *src_data,
                                 gsize         src_stride,
                                 gsize         width,
                                 gsize         height);

static ConversionFunc converters[GDK_MEMORY_N_FORMATS][GDK_MEMORY_N_FORMATS] =
{
  { convert_memcpy, convert_swizzle3210, convert_swizzle2103, NULL, NULL, NULL, NULL, NULL, NULL },
  { convert_swizzle3210, convert_memcpy, convert_swizzle3012, NULL, NULL, NULL, NULL, NULL, NULL },
  { convert_swizzle2103, convert_swizzle1230, convert_memcpy, NULL, NULL, NULL, NULL, NULL, NULL },
  { convert_swizzle_premultiply_3210_3210, convert_swizzle_premultiply_0123_3210, convert_swizzle_premultiply_3012_3210, convert_memcpy, NULL, NULL, NULL, NULL, NULL },
  { convert_swizzle_premultiply_3210_0123, convert_swizzle_premultiply_0123_0123, convert_swizzle_premultiply_3012_0123, NULL, convert_memcpy, NULL, NULL, NULL, NULL },
  { convert_swizzle_premultiply_3210_3012, convert_swizzle_premultiply_0123_3012, convert_swizzle_premultiply_3012_3012, convert_swizzle2103, convert_swizzle1230, convert_memcpy, convert_swizzle3210, NULL, NULL },
  { convert_swizzle_premultiply_3210_0321, convert_swizzle_premultiply_0123_0321, convert_swizzle_premultiply_3012_0321, NULL, NULL, NULL, convert_memcpy, NULL, NULL },
  { convert_swizzle_opaque_3210, convert_swizzle_opaque_0123, convert_swizzle_opaque_3012, NULL, NULL, NULL, NULL, convert_memcpy3, convert_swizzle210 },
  { convert_swizzle_opaque_3012, convert_swizzle_opaque_0321, convert_swizzle_opaque_3210, NULL, NULL, NULL, NULL, convert_swizzle210, convert_memcpy3 },
};

static void
gdk_memory_convert (guchar          *dest_data,
                    gsize            dest_stride,
                    GdkMemoryFormat  dest_format,
                    const guchar    *src_data,
                    gsize            src_stride,
                    GdkMemoryFormat  src_format,
                    gsize            width,
                    gsize            height)
{
  g_assert (dest_format < GDK_MEMORY_N_FORMATS);
  g_assert (src_format < GDK_MEMORY_N_FORMATS);

  if (converters[src_format][dest_format] == NULL)
    g_error ("Conversion from %s to %s not supported", format_to_string (src_format), format_to_string (dest_format));

  converters[src_format][dest_format] (dest_data, dest_stride, src_data, src_stride, width, height);
}

/* End of copied code */

static GdkTexture *
make_texture (GdkMemoryFormat  format,
              int              padding,
              int             *out_stride,
              int             *out_bpp)
{
  GdkPixbuf *source;
  GdkMemoryFormat source_format;
  int width, height, stride, bpp;
  guchar *data;
  GBytes *bytes;
  GdkTexture *texture;
  GError *error = NULL;

  width = height = 200;

  source = gdk_pixbuf_new_from_file_at_scale ("tests/portland-rose.jpg",
                                              width, height, TRUE,
                                              &error);
  if (!source)
    g_error ("%s", error->message);

  source_format = GDK_MEMORY_R8G8B8;
  bpp = 3;

  if (format != GDK_MEMORY_R8G8B8 && format != GDK_MEMORY_B8G8R8)
    {
      bpp = 4;

      /* add an alpha channel with 50% alpha */
      GdkPixbuf *pb = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, width, height);
      gdk_pixbuf_composite (source, pb, 0, 0, width, height, 0, 0, 1, 1, GDK_INTERP_BILINEAR, 128);
      g_object_unref (source);
      source = pb;

      source_format = GDK_MEMORY_R8G8B8A8;
    }

  stride = bpp * width + padding;
  data = g_new0 (guchar, stride * height);

  gdk_memory_convert (data, stride, format,
                      gdk_pixbuf_get_pixels (source),
                      gdk_pixbuf_get_rowstride (source),
                      source_format,
                      width, height);

  g_object_unref (source);

  bytes = g_bytes_new_take (data, stride * height);
  texture = gdk_memory_texture_new (width, height, format, bytes, stride);
  g_bytes_unref (bytes);

  *out_stride = stride;
  *out_bpp = bpp;

  return texture;
}

static void
update_picture (GtkWidget *picture)
{
  GdkMemoryFormat format;
  int padding;
  GdkTexture *texture;
  GtkLabel *label;
  char *text;
  int stride;
  int bpp;

  format = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (picture), "format"));
  padding = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (picture), "padding"));

  texture = make_texture (format, padding, &stride, &bpp);
  gtk_picture_set_paintable (GTK_PICTURE (picture), GDK_PAINTABLE (texture));

  label = GTK_LABEL (g_object_get_data (G_OBJECT (picture), "size_label"));
  text = g_strdup_printf ("%d x %d @ %d",
                          gdk_texture_get_width (texture),
                          gdk_texture_get_height (texture),
                          bpp);
  gtk_label_set_label (label, text);
  g_free (text);

  label = GTK_LABEL (g_object_get_data (G_OBJECT (picture), "stride_label"));
  text = g_strdup_printf ("%d", stride);
  gtk_label_set_label (label, text);
  g_free (text);

  g_object_unref (texture);
}

static void
update_format (GtkDropDown *dropdown,
               GParamSpec  *pspec,
               GtkWidget   *picture)
{
  g_object_set_data (G_OBJECT (picture), "format", GINT_TO_POINTER (gtk_drop_down_get_selected (dropdown)));
  update_picture (picture);
}

static void
update_padding (GtkSpinButton *spinbutton,
                GParamSpec    *pspec,
                GtkWidget     *picture)
{
  g_object_set_data (G_OBJECT (picture), "padding", GINT_TO_POINTER (gtk_spin_button_get_value_as_int (spinbutton)));
  update_picture (picture);
}

static void
add_to_grid (GtkWidget      *grid,
             int             left,
             int             top,
             GdkMemoryFormat format,
             int             padding)
{
  GtkWidget *dropdown, *spin, *picture, *label;

  picture = gtk_picture_new ();
  gtk_grid_attach (GTK_GRID (grid), picture, left + 2, top + 0, 1, 4);

  g_object_set_data (G_OBJECT (picture), "format", GINT_TO_POINTER (format));
  g_object_set_data (G_OBJECT (picture), "padding", GINT_TO_POINTER (padding));

  dropdown = gtk_drop_down_new_from_strings (format_name);
  gtk_widget_set_valign (dropdown, GTK_ALIGN_CENTER);
  gtk_drop_down_set_selected (GTK_DROP_DOWN (dropdown), format);
  g_signal_connect (dropdown, "notify::selected", G_CALLBACK (update_format), picture);

  gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Format"), left, top, 1, 1);
  gtk_grid_attach (GTK_GRID (grid), dropdown, left + 1, top, 1, 1);

  spin = gtk_spin_button_new_with_range (0, 10, 1);
  gtk_widget_set_valign (spin, GTK_ALIGN_CENTER);
  gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), padding);
  g_signal_connect (spin, "notify::value", G_CALLBACK (update_padding), picture);

  gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Padding"), left, top + 1, 1, 1);
  gtk_grid_attach (GTK_GRID (grid), spin, left + 1, top + 1, 1, 1);

  label = gtk_label_new ("");
  gtk_label_set_xalign (GTK_LABEL (label), 0);
  g_object_set_data (G_OBJECT (picture), "size_label", label);
  gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Size"), left, top + 2, 1, 1);
  gtk_grid_attach (GTK_GRID (grid), label, left + 1, top + 2, 1, 1);
  label = gtk_label_new ("");
  gtk_label_set_xalign (GTK_LABEL (label), 0);
  g_object_set_data (G_OBJECT (picture), "stride_label", label);
  gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Stride"), left, top + 3, 1, 1);
  gtk_grid_attach (GTK_GRID (grid), label, left + 1, top + 3, 1, 1);

  update_picture (picture);
}

int
main (int argc, char *argv[])
{
  GtkWidget *window, *grid;

  gtk_init ();

  window = gtk_window_new ();
  grid = gtk_grid_new ();
  gtk_widget_set_margin_top (grid, 10);
  gtk_widget_set_margin_bottom (grid, 10);
  gtk_widget_set_margin_start (grid, 10);
  gtk_widget_set_margin_end (grid, 10);
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_window_set_child (GTK_WINDOW (window), grid);

  add_to_grid (grid, 0, 0, GDK_MEMORY_R8G8B8, 0);

  gtk_window_present (GTK_WINDOW (window));

  while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0)
    g_main_context_iteration (NULL, TRUE);

  return 0;
}
