#include <glib.h>
#include <glib-object.h>

#include <gladeui/glade-app.h>

typedef void (* AssertParentedFunc) (GObject *parent, GObject *child);

typedef struct {
  GType parent_type;
  GType child_type;
  AssertParentedFunc func;
} TestData;

/* Avoid warnings from GVFS-RemoteVolumeMonitor */
static gboolean
ignore_gvfs_warning (const gchar *log_domain,
		     GLogLevelFlags log_level,
		     const gchar *message,
		     gpointer user_data)
{
  if (g_strcmp0 (log_domain, "GVFS-RemoteVolumeMonitor") == 0)
    return FALSE;

  return TRUE;
}

static gboolean
main_loop_quit_cb (gpointer data)
{
  gtk_main_quit ();

  return FALSE;
}

static void
check_finalized (gpointer data,
		 GObject *where_the_object_was)
{
  gboolean *did_finalize = (gboolean *)data;

  *did_finalize = TRUE;
}

static void
test_add_child (gconstpointer data)
{
  TestData *test = (TestData *)data;
  GladeWidgetAdaptor *parent_adaptor;
  GladeWidgetAdaptor *child_adaptor;
  GladeWidget        *gparent, *gchild;
  GObject            *parent, *child;
  gboolean            parent_finalized = FALSE;
  gboolean            child_finalized = FALSE;
  gboolean            gparent_finalized = FALSE;
  gboolean            gchild_finalized = FALSE;

  g_test_log_set_fatal_handler (ignore_gvfs_warning, NULL);

  parent_adaptor = glade_widget_adaptor_get_by_type (test->parent_type);
  child_adaptor  = glade_widget_adaptor_get_by_type (test->child_type);

  gparent = glade_widget_adaptor_create_widget (parent_adaptor, FALSE, NULL);
  gchild  = glade_widget_adaptor_create_widget (child_adaptor, FALSE, NULL);

  parent = glade_widget_get_object (gparent);
  child  = glade_widget_get_object (gchild);

  glade_widget_add_child (gparent, gchild, FALSE);

  /* Pass ownership to the parent */
  g_object_unref (gchild);

  g_assert (glade_widget_get_parent (gchild) == gparent);

  if (test->func)
    test->func (parent, child);

  /* filechoosers hold a reference until an async operation is complete */
  if (GTK_IS_FILE_CHOOSER (parent) || GTK_IS_FILE_CHOOSER (child))
    {
      g_timeout_add (2000, main_loop_quit_cb, NULL);
      gtk_main();
    }
  /* Our plugin code adds an idle when cell renderers are created */
  else if (GTK_IS_CELL_RENDERER (child))
    {
      g_timeout_add (50, main_loop_quit_cb, NULL);
      gtk_main();
    }

  /* Unreffing the parent should finalize the parent and child runtime objects */
  g_object_weak_ref (G_OBJECT (gparent), check_finalized, &gparent_finalized);
  g_object_weak_ref (G_OBJECT (gchild),  check_finalized, &gchild_finalized);
  g_object_weak_ref (G_OBJECT (parent), check_finalized, &parent_finalized);
  g_object_weak_ref (G_OBJECT (child),  check_finalized, &child_finalized);

  g_object_unref (gparent);

  g_assert (gparent_finalized);
  g_assert (gchild_finalized);
  g_assert (parent_finalized);
  g_assert (child_finalized);
}

static void
add_test (GType parent_type,
	  GType child_type,
	  AssertParentedFunc func)
{
  gchar *test_path;
  TestData *data = g_new (TestData, 1);

  test_path = g_strdup_printf ("/AddChild/%s/%s",
			       g_type_name (parent_type),
			       g_type_name (child_type));

  data->parent_type = parent_type;
  data->child_type  = child_type;
  data->func        = func;

  g_test_add_data_func_full (test_path, data, test_add_child, g_free);
  g_free (test_path);
}

static void
assert_widget_parented (GObject *parent,
			GObject *child)
{
  g_assert (gtk_widget_get_parent (GTK_WIDGET (child)) == GTK_WIDGET (parent));
}

static void
assert_submenu (GObject *parent,
		GObject *child)
{
  if (GTK_IS_MENU_ITEM (parent))
    g_assert (gtk_menu_item_get_submenu (GTK_MENU_ITEM (parent)) == GTK_WIDGET (child));
  else if (GTK_IS_MENU_TOOL_BUTTON (parent))
    g_assert (gtk_menu_tool_button_get_menu (GTK_MENU_TOOL_BUTTON (parent)) == GTK_WIDGET (child));
  else
    g_assert_not_reached ();
}

static void
assert_cell_parented (GObject *parent,
		      GObject *child)
{
  GList *cells;

  cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (parent));
  g_assert (g_list_find (cells, child) != NULL);
  g_list_free (cells);
}

static void
assert_column_parented (GObject *parent,
			GObject *child)
{
  g_assert (gtk_tree_view_get_column (GTK_TREE_VIEW (parent), 0) == GTK_TREE_VIEW_COLUMN (child));
}

/* Ignore deprecated classes, we test them regardless */
G_GNUC_BEGIN_IGNORE_DEPRECATIONS

static void
add_child_widgets (GType parent_type)
{
  add_test (parent_type, GTK_TYPE_IMAGE,               assert_widget_parented);
  add_test (parent_type, GTK_TYPE_LABEL,               assert_widget_parented);
  add_test (parent_type, GTK_TYPE_ACCEL_LABEL,         assert_widget_parented);
  add_test (parent_type, GTK_TYPE_ENTRY,               assert_widget_parented);
  add_test (parent_type, GTK_TYPE_SEARCH_ENTRY,        assert_widget_parented);
  add_test (parent_type, GTK_TYPE_SPIN_BUTTON,         assert_widget_parented);
  add_test (parent_type, GTK_TYPE_SWITCH,              assert_widget_parented);
  add_test (parent_type, GTK_TYPE_SEPARATOR,           assert_widget_parented);
  add_test (parent_type, GTK_TYPE_ARROW,               assert_widget_parented);
  add_test (parent_type, GTK_TYPE_DRAWING_AREA,        assert_widget_parented);
  add_test (parent_type, GTK_TYPE_SPINNER,             assert_widget_parented);
  add_test (parent_type, GTK_TYPE_LEVEL_BAR,           assert_widget_parented);
  add_test (parent_type, GTK_TYPE_PROGRESS_BAR,        assert_widget_parented);
  add_test (parent_type, GTK_TYPE_SCALE,               assert_widget_parented);
  add_test (parent_type, GTK_TYPE_SCROLLBAR,           assert_widget_parented);
  add_test (parent_type, GTK_TYPE_BUTTON,              assert_widget_parented);
  add_test (parent_type, GTK_TYPE_TOGGLE_BUTTON,       assert_widget_parented);
  add_test (parent_type, GTK_TYPE_CHECK_BUTTON,        assert_widget_parented);
  add_test (parent_type, GTK_TYPE_RADIO_BUTTON,        assert_widget_parented);
  add_test (parent_type, GTK_TYPE_LINK_BUTTON,         assert_widget_parented);
  add_test (parent_type, GTK_TYPE_MENU_BUTTON,         assert_widget_parented);

  add_test (parent_type, GTK_TYPE_COMBO_BOX,           assert_widget_parented);
  add_test (parent_type, GTK_TYPE_COMBO_BOX_TEXT,      assert_widget_parented);
  add_test (parent_type, GTK_TYPE_SCALE_BUTTON,        assert_widget_parented);
  add_test (parent_type, GTK_TYPE_VOLUME_BUTTON,       assert_widget_parented);
  add_test (parent_type, GTK_TYPE_FONT_BUTTON,         assert_widget_parented);
  add_test (parent_type, GTK_TYPE_COLOR_BUTTON,        assert_widget_parented);

  /* FIXME: FileChooserButton leaks a GTask which will crash in the following test */
  /* add_test (parent_type, GTK_TYPE_FILE_CHOOSER_BUTTON, assert_widget_parented); */
  add_test (parent_type, GTK_TYPE_APP_CHOOSER_BUTTON,  assert_widget_parented);
  add_test (parent_type, GTK_TYPE_TEXT_VIEW,           assert_widget_parented);
  add_test (parent_type, GTK_TYPE_TREE_VIEW,           assert_widget_parented);
  add_test (parent_type, GTK_TYPE_ICON_VIEW,           assert_widget_parented);
  add_test (parent_type, GTK_TYPE_CALENDAR,            assert_widget_parented);
  add_test (parent_type, GTK_TYPE_BOX,                 assert_widget_parented);
  add_test (parent_type, GTK_TYPE_NOTEBOOK,            assert_widget_parented);
  add_test (parent_type, GTK_TYPE_FRAME,               assert_widget_parented);
  add_test (parent_type, GTK_TYPE_ASPECT_FRAME,        assert_widget_parented);
  add_test (parent_type, GTK_TYPE_OVERLAY,             assert_widget_parented);
  add_test (parent_type, GTK_TYPE_MENU_BAR,            assert_widget_parented);
  add_test (parent_type, GTK_TYPE_TOOLBAR,             assert_widget_parented);
  add_test (parent_type, GTK_TYPE_TOOL_PALETTE,        assert_widget_parented);
  add_test (parent_type, GTK_TYPE_PANED,               assert_widget_parented);
  add_test (parent_type, GTK_TYPE_BUTTON_BOX,          assert_widget_parented);
  add_test (parent_type, GTK_TYPE_LAYOUT,              assert_widget_parented);
  add_test (parent_type, GTK_TYPE_FIXED,               assert_widget_parented);
  add_test (parent_type, GTK_TYPE_EVENT_BOX,           assert_widget_parented);
  add_test (parent_type, GTK_TYPE_EXPANDER,            assert_widget_parented);
  add_test (parent_type, GTK_TYPE_VIEWPORT,            assert_widget_parented);
  add_test (parent_type, GTK_TYPE_ALIGNMENT,           assert_widget_parented);
  add_test (parent_type, GTK_TYPE_GRID,                assert_widget_parented);
  add_test (parent_type, GTK_TYPE_SCROLLED_WINDOW,     assert_widget_parented);
  add_test (parent_type, GTK_TYPE_INFO_BAR,            assert_widget_parented);
  add_test (parent_type, GTK_TYPE_STATUSBAR,           assert_widget_parented);
}

static void
add_child_cells (GType parent_type)
{
  add_test (parent_type, GTK_TYPE_CELL_RENDERER_TEXT, assert_cell_parented);
  add_test (parent_type, GTK_TYPE_CELL_RENDERER_ACCEL, assert_cell_parented);
  add_test (parent_type, GTK_TYPE_CELL_RENDERER_TOGGLE, assert_cell_parented);
  add_test (parent_type, GTK_TYPE_CELL_RENDERER_COMBO, assert_cell_parented);
  add_test (parent_type, GTK_TYPE_CELL_RENDERER_SPIN, assert_cell_parented);
  add_test (parent_type, GTK_TYPE_CELL_RENDERER_PIXBUF, assert_cell_parented);
  add_test (parent_type, GTK_TYPE_CELL_RENDERER_PROGRESS, assert_cell_parented);
  add_test (parent_type, GTK_TYPE_CELL_RENDERER_SPINNER, assert_cell_parented);
}

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

  glade_init ();
  glade_app_get ();

  /* Normal GtkContainer / GtkWidget parenting */
  add_child_widgets (GTK_TYPE_WINDOW);
  add_child_widgets (GTK_TYPE_BOX);
  add_child_widgets (GTK_TYPE_GRID);
  add_child_widgets (GTK_TYPE_NOTEBOOK);
  add_child_widgets (GTK_TYPE_OVERLAY);
  add_child_widgets (GTK_TYPE_PANED);
  add_child_widgets (GTK_TYPE_BUTTON_BOX);
  add_child_widgets (GTK_TYPE_LAYOUT);
  add_child_widgets (GTK_TYPE_FIXED);
  add_child_widgets (GTK_TYPE_EVENT_BOX);
  add_child_widgets (GTK_TYPE_EXPANDER);
  add_child_widgets (GTK_TYPE_VIEWPORT);
  add_child_widgets (GTK_TYPE_ALIGNMENT);

  /* Actions */
  add_test (GTK_TYPE_ACTION_GROUP, GTK_TYPE_ACTION, NULL);
  add_test (GTK_TYPE_ACTION_GROUP, GTK_TYPE_TOGGLE_ACTION, NULL);
  add_test (GTK_TYPE_ACTION_GROUP, GTK_TYPE_RADIO_ACTION, NULL);
  add_test (GTK_TYPE_ACTION_GROUP, GTK_TYPE_RECENT_ACTION, NULL);

  /* Menus */
  add_test (GTK_TYPE_MENU_BAR, GTK_TYPE_MENU_ITEM, assert_widget_parented);
  add_test (GTK_TYPE_MENU_BAR, GTK_TYPE_IMAGE_MENU_ITEM, assert_widget_parented);
  add_test (GTK_TYPE_MENU_BAR, GTK_TYPE_CHECK_MENU_ITEM, assert_widget_parented);
  add_test (GTK_TYPE_MENU_BAR, GTK_TYPE_RADIO_MENU_ITEM, assert_widget_parented);
  add_test (GTK_TYPE_MENU_BAR, GTK_TYPE_SEPARATOR_MENU_ITEM, assert_widget_parented);
  add_test (GTK_TYPE_MENU, GTK_TYPE_MENU_ITEM, assert_widget_parented);
  add_test (GTK_TYPE_MENU, GTK_TYPE_IMAGE_MENU_ITEM, assert_widget_parented);
  add_test (GTK_TYPE_MENU, GTK_TYPE_CHECK_MENU_ITEM, assert_widget_parented);
  add_test (GTK_TYPE_MENU, GTK_TYPE_RADIO_MENU_ITEM, assert_widget_parented);
  add_test (GTK_TYPE_MENU, GTK_TYPE_SEPARATOR_MENU_ITEM, assert_widget_parented);
  add_test (GTK_TYPE_MENU_ITEM, GTK_TYPE_MENU, assert_submenu);
  add_test (GTK_TYPE_IMAGE_MENU_ITEM, GTK_TYPE_MENU, assert_submenu);
  add_test (GTK_TYPE_CHECK_MENU_ITEM, GTK_TYPE_MENU, assert_submenu);
  add_test (GTK_TYPE_RADIO_MENU_ITEM, GTK_TYPE_MENU, assert_submenu);

  /* Toolbars / ToolPalette */
  add_test (GTK_TYPE_TOOLBAR, GTK_TYPE_TOOL_BUTTON, assert_widget_parented);
  add_test (GTK_TYPE_TOOLBAR, GTK_TYPE_TOGGLE_TOOL_BUTTON, assert_widget_parented);
  add_test (GTK_TYPE_TOOLBAR, GTK_TYPE_RADIO_TOOL_BUTTON, assert_widget_parented);
  add_test (GTK_TYPE_TOOLBAR, GTK_TYPE_MENU_TOOL_BUTTON, assert_widget_parented);
  add_test (GTK_TYPE_TOOL_ITEM_GROUP, GTK_TYPE_TOOL_BUTTON, assert_widget_parented);
  add_test (GTK_TYPE_TOOL_ITEM_GROUP, GTK_TYPE_TOGGLE_TOOL_BUTTON, assert_widget_parented);
  add_test (GTK_TYPE_TOOL_ITEM_GROUP, GTK_TYPE_RADIO_TOOL_BUTTON, assert_widget_parented);
  add_test (GTK_TYPE_TOOL_ITEM_GROUP, GTK_TYPE_MENU_TOOL_BUTTON, assert_widget_parented);
  add_test (GTK_TYPE_TOOL_PALETTE, GTK_TYPE_TOOL_ITEM_GROUP, assert_widget_parented);
  add_test (GTK_TYPE_MENU_TOOL_BUTTON, GTK_TYPE_MENU, assert_submenu);

  /* Cell layouts */
  /* FIXME: GtkTreeViewColumn gives a critical when creating it */
  /* add_test (GTK_TYPE_TREE_VIEW, GTK_TYPE_TREE_VIEW_COLUMN, assert_column_parented); */
  add_child_cells (GTK_TYPE_TREE_VIEW_COLUMN);
  add_child_cells (GTK_TYPE_ICON_VIEW);
  add_child_cells (GTK_TYPE_COMBO_BOX);

  /* TextTag */
  add_test (GTK_TYPE_TEXT_TAG_TABLE, GTK_TYPE_TEXT_TAG, NULL);

  return g_test_run ();
}
