#include <stdlib.h>
#include <gio/gio.h>

typedef struct
{
  gint event_type;
  gchar *file;
  gchar *other_file;
  gint step;
} RecordedEvent;

static void
free_recorded_event (RecordedEvent *event)
{
  g_free (event->file);
  g_free (event->other_file);
  g_free (event);
}

typedef struct
{
  GFile *file;
  GFileMonitor *monitor;
  GMainLoop *loop;
  gint step;
  GList *events;
  GString *output;
} TestData;

#if 0
static void
output_event (RecordedEvent *event)
{
  if (event->step >= 0)
    g_print (">>>> step %d\n", event->step);
  else
    {
      GTypeClass *class;

      class = g_type_class_ref (g_type_from_name ("GFileMonitorEvent"));
      g_print ("%s file=%s other_file=%s\n",
               g_enum_get_value (G_ENUM_CLASS (class), event->event_type)->value_nick,
               event->file,
               event->other_file);
      g_type_class_unref (class);
    }
}

static void
output_events (GList *list)
{
  GList *l;

  g_print (">>>output events\n");
  for (l = list; l; l = l->next)
    output_event ((RecordedEvent *)l->data);
}
#endif

/* a placeholder for temp file names we don't want to compare */
static const gchar DONT_CARE[] = "";

static void
check_expected_event (gint           i,
                      RecordedEvent *e1,
                      RecordedEvent *e2)
{
  g_assert_cmpint (e1->step, ==, e2->step);
  if (e1->step < 0)
    return;

  g_assert_cmpint (e1->event_type, ==, e2->event_type);

  if (e1->file != DONT_CARE)
    g_assert_cmpstr (e1->file, ==, e2->file);

  if (e1->other_file != DONT_CARE)
    g_assert_cmpstr (e1->other_file, ==, e2->other_file);
}

static void
check_expected_events (RecordedEvent *expected,
                       gsize          n_expected,
                       GList         *recorded)
{
  gint i;
  GList *l;

  g_assert_cmpint (n_expected, ==, g_list_length (recorded));

  for (i = 0, l = recorded; i < n_expected; i++, l = l->next)
    {
      RecordedEvent *e1 = &expected[i];
      RecordedEvent *e2 = (RecordedEvent *)l->data;

      check_expected_event (i, e1, e2);
    }
}

static void
record_event (TestData    *data,
              gint         event_type,
              const gchar *file,
              const gchar *other_file,
              gint         step)
{
  RecordedEvent *event;

  event = g_new0 (RecordedEvent, 1);
  event->event_type = event_type;
  event->file = g_strdup (file);
  event->other_file = g_strdup (other_file);
  event->step = step;

  data->events = g_list_append (data->events, event);
}

static void
monitor_changed (GFileMonitor      *monitor,
                 GFile             *file,
                 GFile             *other_file,
                 GFileMonitorEvent  event_type,
                 gpointer           user_data)
{
  TestData *data = user_data;
  gchar *basename, *other_base;

  basename = g_file_get_basename (file);
  if (other_file)
    other_base = g_file_get_basename (other_file);
  else
    other_base = NULL;

  record_event (data, event_type, basename, other_base, -1);

  g_free (basename);
  g_free (other_base);
}

static gboolean
atomic_replace_step (gpointer user_data)
{
  TestData *data = user_data;
  GError *error = NULL;

  switch (data->step)
    {
    case 0:
      record_event (data, -1, NULL, NULL, 0);
      g_file_replace_contents (data->file, "step 0", 6, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error);
      g_assert_no_error (error);
      break;
    case 1:
      record_event (data, -1, NULL, NULL, 1);
      g_file_replace_contents (data->file, "step 1", 6, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error);
      g_assert_no_error (error);
      break;
    case 2:
      record_event (data, -1, NULL, NULL, 2);
      g_main_loop_quit (data->loop);
      return G_SOURCE_REMOVE;
    }

  data->step++;

  return G_SOURCE_CONTINUE;
}

/* this is the output we expect from the above steps */
static RecordedEvent atomic_replace_output[] = {
  { -1, NULL, NULL, 0 },
  { G_FILE_MONITOR_EVENT_CREATED, "atomic_replace_file", NULL, -1 },
  { G_FILE_MONITOR_EVENT_CHANGED, "atomic_replace_file", NULL, -1 },
  { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "atomic_replace_file", NULL, -1 },
  { -1, NULL, NULL, 1 },
  { G_FILE_MONITOR_EVENT_RENAMED, (gchar*)DONT_CARE, "atomic_replace_file", -1 },
  { -1, NULL, NULL, 2 },
};

static void
test_atomic_replace (void)
{
  GError *error = NULL;
  TestData data;

  data.output = g_string_new ("");
  data.step = 0;
  data.events = NULL;

  data.file = g_file_new_for_path ("atomic_replace_file");
  g_file_delete (data.file, NULL, NULL);

  data.monitor = g_file_monitor_file (data.file, G_FILE_MONITOR_WATCH_MOVES, NULL, &error);
  g_assert_no_error (error);

  g_file_monitor_set_rate_limit (data.monitor, 200);
  g_signal_connect (data.monitor, "changed", G_CALLBACK (monitor_changed), &data);

  data.loop = g_main_loop_new (NULL, TRUE);

  g_timeout_add (1000, atomic_replace_step, &data);

  g_main_loop_run (data.loop);

  /*output_events (data.events);*/
  check_expected_events (atomic_replace_output, G_N_ELEMENTS (atomic_replace_output), data.events);

  /* clean up */
  g_file_delete (data.file, NULL, NULL);

  g_list_free_full (data.events, (GDestroyNotify)free_recorded_event);
  g_main_loop_unref (data.loop);
  g_object_unref (data.monitor);
  g_object_unref (data.file);
  g_string_free (data.output, TRUE);
}

int
main (int argc, char *argv[])
{
  g_test_init (&argc, &argv, NULL);

  g_test_add_func ("/monitor/atomic-replace", test_atomic_replace);

  return g_test_run ();
}