#include <gtk/gtk.h>

static const char *css =
"test>button {"
"  all: unset; "
"  background-color: white;"
"  border: 30px solid teal;"
"  margin: 40px;"
"  padding: 40px;"
"}"
"test>button:hover {"
"  background-color: blue;"
"}"
"test image {"
"  background-color: purple;"
"}"
;

/* Just so we can avoid a signal */
GtkWidget *transform_tester;
GtkWidget *test_widget;
GtkWidget *test_child;
float scale = 1;
gboolean do_picking = TRUE;

static const GdkRGBA RED   = {1, 0, 0, 0.4};
static const GdkRGBA GREEN = {0, 1, 0, 0.7};
static const GdkRGBA BLUE  = {0, 0, 1, 0.4};
static const GdkRGBA BLACK = {0, 0, 0, 1  };



/* ######################################################################### */
/* ############################## MatrixChooser ############################ */
/* ######################################################################### */


#define GTK_TYPE_MATRIX_CHOOSER (gtk_matrix_chooser_get_type ())
G_DECLARE_FINAL_TYPE (GtkMatrixChooser, gtk_matrix_chooser, GTK, MATRIX_CHOOSER, GtkWidget)

struct _GtkMatrixChooser
{
  GtkWidget parent_instance;
};

G_DEFINE_TYPE (GtkMatrixChooser, gtk_matrix_chooser, GTK_TYPE_WIDGET)

static void
gtk_matrix_chooser_init (GtkMatrixChooser *self)
{
}

static void
gtk_matrix_chooser_class_init (GtkMatrixChooserClass *klass)
{

}


/* ######################################################################### */
/* ############################# TransformTester ########################### */
/* ######################################################################### */

#define TEST_WIDGET_MIN_SIZE 100

#define GTK_TYPE_TRANSFORM_TESTER (gtk_transform_tester_get_type ())
G_DECLARE_FINAL_TYPE (GtkTransformTester, gtk_transform_tester, GTK, TRANSFORM_TESTER, GtkWidget);

struct _GtkTransformTester
{
  GtkWidget parent_instance;

  GtkWidget *test_widget;
  int pick_increase;
};

G_DEFINE_TYPE (GtkTransformTester, gtk_transform_tester, GTK_TYPE_WIDGET);

static void
gtk_transform_tester_measure (GtkWidget      *widget,
                              GtkOrientation  orientation,
                              int             for_size,
                              int            *minimum,
                              int            *natural,
                              int            *minimum_baseline,
                              int            *natural_baseline)
{
  GtkTransformTester *self = (GtkTransformTester *)widget;

  if (self->test_widget)
    {
      gtk_widget_measure (self->test_widget, orientation, for_size,
                          minimum, natural, NULL, NULL);
    }
}

static void
gtk_transform_tester_size_allocate (GtkWidget  *widget,
                                    int         width,
                                    int         height,
                                    int         baseline)
{
  GtkTransformTester *self = (GtkTransformTester *)widget;
  GskTransform *global_transform;
  int w, h;

  if (!self->test_widget)
    return;

  scale += 2.5f;

  gtk_widget_measure (self->test_widget, GTK_ORIENTATION_HORIZONTAL, -1,
                      &w, NULL, NULL, NULL);
  gtk_widget_measure (self->test_widget, GTK_ORIENTATION_VERTICAL, w,
                      &h, NULL, NULL, NULL);

  g_message ("%s: %d, %d", __FUNCTION__, w, h);

  global_transform = NULL;

  global_transform = gsk_transform_translate (global_transform, &GRAPHENE_POINT_INIT (width / 2.0f, height / 2.0f));
  global_transform = gsk_transform_rotate (global_transform, scale);
  global_transform = gsk_transform_translate (global_transform, &GRAPHENE_POINT_INIT (-w / 2.0f, -h / 2.0f));

  gtk_widget_allocate (self->test_widget,
                       w, h,
                       -1,
                       global_transform);
}

static void
gtk_transform_tester_snapshot (GtkWidget   *widget,
                               GtkSnapshot *snapshot)
{
  GtkTransformTester *self = (GtkTransformTester *)widget;
  const int width = gtk_widget_get_width (widget);
  const int height = gtk_widget_get_height (widget);
  const int inc = self->pick_increase;
  graphene_rect_t child_bounds;
  graphene_rect_t self_bounds;
  int x, y;

  GTK_WIDGET_CLASS (gtk_transform_tester_parent_class)->snapshot (widget, snapshot);

  if (!do_picking ||
      !gtk_widget_compute_bounds (self->test_widget, widget, &child_bounds) ||
      !gtk_widget_compute_bounds (self->test_widget, self->test_widget, &self_bounds))
    return;

  {
    const struct {
      graphene_point_t coords;
      GdkRGBA color;
    } points[4] = {
      { self_bounds.origin, {1, 0, 0, 1} },
      { GRAPHENE_POINT_INIT (self_bounds.origin.x + self_bounds.size.width, self_bounds.origin.y), {0, 1, 0, 1} },
      { GRAPHENE_POINT_INIT (self_bounds.origin.x + self_bounds.size.width, self_bounds.origin.y + self_bounds.size.height), {0, 0, 1, 1} },
      { GRAPHENE_POINT_INIT (self_bounds.origin.x, self_bounds.origin.y + self_bounds.size.height), {1, 0, 1, 1} }
    };

    for (x = 0; x < G_N_ELEMENTS (points); x ++)
      {
        int px, py;

        gtk_widget_translate_coordinates (self->test_widget, widget,
                                          points[x].coords.x, points[x].coords.y,
                                          &px, &py);

        gtk_snapshot_append_color (snapshot, &points[x].color,
                                   &GRAPHENE_RECT_INIT (px, py,
                                                        4,
                                                        4));
      }
  }

  /* Now add custom drawing */
  for (x = 0; x < width; x += inc)
    {
      for (y = 0; y < height; y += inc)
        {
          const float px = x;
          const float py = y;
          GtkWidget *picked;
#if 1
          picked = gtk_widget_pick (widget, px, py, GTK_PICK_DEFAULT);
#else
          {
            int dx, dy;
            gtk_widget_translate_coordinates (widget, self->test_widget, px, py, &dx, &dy);
            picked = gtk_widget_pick (self->test_widget, dx, dy, GTK_PICK_DEFAULT);
          }
#endif

          if (picked == self->test_widget)
            gtk_snapshot_append_color (snapshot, &GREEN,
                                       &GRAPHENE_RECT_INIT (px - (inc / 2), py - (inc / 2), inc, inc));
          else if (picked == test_child)
            gtk_snapshot_append_color (snapshot, &BLUE,
                                       &GRAPHENE_RECT_INIT (px - (inc / 2), py - (inc / 2), inc, inc));

          else
            gtk_snapshot_append_color (snapshot, &RED,
                                       &GRAPHENE_RECT_INIT (px - (inc / 2), py - (inc / 2), inc, inc));
        }
    }

  gtk_snapshot_append_color (snapshot, &BLACK,
                             &GRAPHENE_RECT_INIT (child_bounds.origin.x,
                                                  child_bounds.origin.y,
                                                  child_bounds.size.width,
                                                  1));

  gtk_snapshot_append_color (snapshot, &BLACK,
                             &GRAPHENE_RECT_INIT (child_bounds.origin.x + child_bounds.size.width,
                                                  child_bounds.origin.y,
                                                  1,
                                                  child_bounds.size.height));

  gtk_snapshot_append_color (snapshot, &BLACK,
                             &GRAPHENE_RECT_INIT (child_bounds.origin.x,
                                                  child_bounds.origin.y + child_bounds.size.height,
                                                  child_bounds.size.width,
                                                  1));

  gtk_snapshot_append_color (snapshot, &BLACK,
                             &GRAPHENE_RECT_INIT (child_bounds.origin.x,
                                                  child_bounds.origin.y,
                                                  1,
                                                  child_bounds.size.height));
}

static void
gtk_transform_tester_init (GtkTransformTester *self)
{
  self->pick_increase = 4;
}

static void
gtk_transform_tester_class_init (GtkTransformTesterClass *klass)
{
  GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;

  widget_class->measure = gtk_transform_tester_measure;
  widget_class->size_allocate = gtk_transform_tester_size_allocate;
  widget_class->snapshot = gtk_transform_tester_snapshot;

  gtk_widget_class_set_css_name (widget_class, "test");
}

static gboolean
tick_cb (GtkWidget     *widget,
         GdkFrameClock *frame_clock,
         gpointer       user_data)
{
  gtk_widget_queue_allocate (widget);

  return G_SOURCE_CONTINUE;
}

static void
gtk_transform_tester_set_test_widget (GtkTransformTester *self,
                                      GtkWidget          *widget)
{
  g_assert (!self->test_widget);

  self->test_widget = widget;
  gtk_widget_set_parent (widget, (GtkWidget *)self);

  gtk_widget_add_tick_callback (GTK_WIDGET (self), tick_cb, NULL, NULL);
}

static void
toggled_cb (GtkToggleButton *source,
            gpointer         user_data)
{
  do_picking = gtk_toggle_button_get_active (source);
}

static void
quit_cb (GtkWidget *widget,
         gpointer   data)
{
  gboolean *done = data;

  *done = TRUE;

  g_main_context_wakeup (NULL);
}

int
main (int argc, char **argv)
{
  GtkWidget *window;
  GtkWidget *matrix_chooser;
  GtkWidget *box;
  GtkWidget *titlebar;
  GtkWidget *toggle_button;
  GtkCssProvider *provider;
  gboolean done = FALSE;

  gtk_init ();

  provider = gtk_css_provider_new ();
  gtk_css_provider_load_from_data (provider, css, -1);
  gtk_style_context_add_provider_for_display (gdk_display_get_default (),
                                              GTK_STYLE_PROVIDER (provider),
                                              GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

  window = gtk_window_new ();
  matrix_chooser = g_object_new (GTK_TYPE_MATRIX_CHOOSER, NULL);
  transform_tester = g_object_new (GTK_TYPE_TRANSFORM_TESTER, NULL);
  box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  titlebar = gtk_header_bar_new ();

  gtk_window_set_titlebar (GTK_WINDOW (window), titlebar);
  gtk_header_bar_set_show_title_buttons (GTK_HEADER_BAR (titlebar), TRUE);

  toggle_button = gtk_toggle_button_new ();
  gtk_button_set_label (GTK_BUTTON (toggle_button), "Picking");
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle_button), do_picking);
  g_signal_connect (toggle_button, "toggled", G_CALLBACK (toggled_cb), NULL);
  gtk_container_add (GTK_CONTAINER (titlebar), toggle_button);

  test_widget = gtk_button_new ();
  gtk_widget_set_size_request (test_widget, TEST_WIDGET_MIN_SIZE, TEST_WIDGET_MIN_SIZE);
  gtk_widget_set_halign (test_widget, GTK_ALIGN_CENTER);
  gtk_widget_set_valign (test_widget, GTK_ALIGN_CENTER);


  test_child = gtk_image_new_from_icon_name ("weather-clear");
  gtk_widget_set_halign (test_child, GTK_ALIGN_CENTER);
  gtk_widget_set_valign (test_child, GTK_ALIGN_CENTER);
  gtk_widget_set_size_request (test_child, TEST_WIDGET_MIN_SIZE / 2, TEST_WIDGET_MIN_SIZE / 2);
  gtk_container_add (GTK_CONTAINER (test_widget), test_child);


  gtk_transform_tester_set_test_widget (GTK_TRANSFORM_TESTER (transform_tester), test_widget);

  gtk_widget_set_vexpand (transform_tester, TRUE);
  gtk_container_add (GTK_CONTAINER (box), transform_tester);
  gtk_container_add (GTK_CONTAINER (box), matrix_chooser);
  gtk_container_add (GTK_CONTAINER (window), box);

  gtk_window_set_default_size ((GtkWindow *)window, 200, 200);
  g_signal_connect (window, "close-request", G_CALLBACK (quit_cb), &done);
  gtk_widget_show (window);

  while (!done)
    g_main_context_iteration (NULL, TRUE);

  return 0;
}