#include <stdlib.h>
#include <gtk/gtk.h>
#include <cairo.h>

#include <linux/input.h>

/*
 * compile with
 * gcc -Wall -g `pkg-config --libs --cflags gtk+-2.0` -o cairo-mt cairo-mt.c
 */

typedef struct _blobinfo BlobInfo;
struct _blobinfo {
  gint id;
  gint px;
  gint py;
  gint ori;
  gint tmaj;
  gint tmin;
};

static GArray *drawblobs = NULL;
static GtkWidget *drawarea = NULL;

gdouble colors[] = {
  0.0, 0.0, 0.0,
  1.0, 0.0, 0.0,
  0.0, 1.0, 0.0,
  0.0, 0.0, 1.0,
  1.0, 1.0, 0.0,
  0.0, 1.0, 1.0,
  1.0, 0.0, 1.0,
  1.0, 1.0, 1.0,
  0.0, 0.0, 0.0,
  0.5, 0.0, 0.0,
  0.0, 0.5, 0.0,
  0.0, 0.0, 0.5,
  0.5, 0.5, 0.0,
  0.0, 0.5, 0.5,
  0.5, 0.0, 0.5,
  0.5, 0.5, 0.5,
};

void
draw (GtkWidget *darea)
{
  GtkAllocation *alloc;
  gdouble        scaling;
  cairo_t       *cr;
  gdouble dx = 1.0, dy = 1.0;
  BlobInfo      *cur_blob;
  int i;

  cr = gdk_cairo_create (darea->window);

  alloc = &darea->allocation;
  scaling = MIN (alloc->width, alloc->height) / 2.2;

  cairo_translate (cr, alloc->width / 2, alloc->height / 2);
  cairo_scale (cr, scaling, -scaling);

  cairo_device_to_user_distance (cr, &dx, &dy);
  cairo_set_line_width (cr, MAX (dx, dy));
  cairo_set_source_rgba (cr, 1.0, 0.0, 0.0, 1.0);

  /* this is a unit-box around the center of the window */
  cairo_set_source_rgba (cr, 0.0, 0.0, 1.0, 1.0);
  cairo_new_path (cr);
  cairo_rectangle (cr, -1, -1, 2, 2);
  cairo_stroke (cr);

  if (drawblobs)
    {
      cairo_scale (cr, 1./3096, -1./3096);
      for (i = 0; i < drawblobs->len; i++)
        {
          cur_blob = &g_array_index (drawblobs, BlobInfo, i);
          cairo_save (cr);
          cairo_translate (cr, cur_blob->px, cur_blob->py);
          cairo_rotate (cr, -G_PI / 2.0 / 32 * cur_blob->ori);
          cairo_scale (cr, 0.1 * cur_blob->tmin, 0.1 * cur_blob->tmaj);
          cairo_arc (cr, 0, 0, 5, 0, 2*G_PI);
          cairo_set_source_rgba (cr,
                                 colors[3 * cur_blob->id + 0],
                                 colors[3 * cur_blob->id + 1],
                                 colors[3 * cur_blob->id + 2],
                                 0.7);
          cairo_fill (cr);
          cairo_restore (cr);
        }
    }
  cairo_destroy (cr);
}


gboolean
expose_event (GtkWidget      *widget,
              GdkEventExpose *event,
              gpointer       *data)
{
  GtkWidget *darea = GTK_WIDGET (data);

  draw (darea);

  return FALSE;
}


gboolean
evdev_callback (GIOChannel *source,
                GIOCondition condition,
                gpointer data)
{
  static GArray *blobs = NULL;
  static BlobInfo cur_blob = { 0, };
  struct input_event event_buffer[256];
  gsize bytes_read;
  GError *error = NULL;
  GIOStatus status;
  int i;

  if (!blobs)
    blobs = g_array_new (FALSE, FALSE, sizeof (BlobInfo));

  status = g_io_channel_read_chars (source, (gchar *) event_buffer,
                                    sizeof (event_buffer), &bytes_read, &error);

  for (i = 0; i < bytes_read / sizeof (event_buffer[0]); i++)
    {
      if (event_buffer[i].type == EV_ABS)
        {
          switch (event_buffer[i].code)
            {
              case ABS_MT_TRACKING_ID:
                  cur_blob.id = event_buffer[i].value;
                  break;
              case ABS_MT_POSITION_X:
                  cur_blob.px = event_buffer[i].value;
                  break;
              case ABS_MT_POSITION_Y:
                  cur_blob.py = event_buffer[i].value;
                  break;
              case ABS_MT_ORIENTATION:
                  cur_blob.ori = event_buffer[i].value;
                  break;
              case ABS_MT_TOUCH_MAJOR:
                  cur_blob.tmaj = event_buffer[i].value;
                  break;
              case ABS_MT_TOUCH_MINOR:
                  cur_blob.tmin = event_buffer[i].value;
                  break;
              case ABS_X:
              case ABS_Y:
                  break;
              default:
                  g_printerr ("unknown ABS event code %d\n",
                              event_buffer[i].code);
            }
        }
      else if (event_buffer[i].type == EV_SYN)
        {
          switch (event_buffer[i].code)
            {
              case SYN_MT_REPORT:
                  g_array_append_val (blobs, cur_blob);
                  break;
              case SYN_REPORT:
                  if (drawblobs)
                    g_array_free (drawblobs, TRUE);
                  drawblobs = blobs;
                  blobs = g_array_new (FALSE, FALSE, sizeof (BlobInfo));
                  gtk_widget_queue_draw (drawarea);
                  break;
              default:
                  g_printerr ("unknown SYN event code %d\n",
                              event_buffer[i].code);
            }
        }

    }

  return TRUE;
}


void
open_input (char *filename)
{

  GIOChannel *evdev;
  GError *error = NULL;

  evdev = g_io_channel_new_file (filename, "r", &error);
  g_io_channel_set_encoding (evdev, NULL, NULL);
  g_io_channel_set_flags (evdev, G_IO_FLAG_NONBLOCK, NULL);

  g_io_add_watch (evdev, G_IO_IN, evdev_callback, NULL);
}


void
create_window (void)
{
  GtkWidget *window, *darea;

  window = g_object_new (GTK_TYPE_WINDOW,
                         "title", "Test",
                         "default-width", 800,
                         "default-height", 600,
                         "visible", TRUE,
                         NULL);

  g_signal_connect (window,
                    "destroy", G_CALLBACK (gtk_main_quit), NULL);

  darea = g_object_new (GTK_TYPE_DRAWING_AREA,
                        "width-request", 100,
                        "height-request", 100,
                        "visible", TRUE,
                        "parent", window,
                        NULL);
  drawarea = darea;
  g_object_connect (G_OBJECT (darea),
                    "signal::expose-event",
                    G_CALLBACK (expose_event), darea,
                    NULL);
}


gint
main (gint argc, char *argv[])
{
  gtk_init (&argc, &argv);

  create_window ();
  
  open_input (argc > 1 ? argv[1] : "/dev/input/event9");

  gtk_main ();

  return 0;
}