/* Testcase for bug 659022, written by Kristian Rietveld.
 *
 * This testcase is a "model" of EmpathyIndividualStore and
 * EmpathyIndividualView.  Copyright belongs to the Empathy contributors.
 */

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

/* NOTES:
 *   - Yes, the code leaks memory.
 *   - We only deal with the show_groups == TRUE case.
 *
 * What the model does not do:
 *   - put in parts of the DnD code
 *   - support contacts stored in multiple groups
 *   - save group states
 *   - adding/removing groups
 *   - adding/removing contacts
 */

static gboolean is_searching = FALSE;
static char search_char;

enum
{
  COL_IS_CONTACT,
  COL_IS_ONLINE,
  COL_IS_SEPARATOR,
  COL_NAME,
  N_COL
};

static struct Contact
{
  const char *name;
  const char *group;
} contacts[] =
{
  /* Contact names courtesy of some random keyboard smashing */
  { "foo", "B" },
  { "asdfo", "C" },
  { "asf", "B" },
  { "hgf", "A" },
  { "pou", "A" },

  { "bkjs", "C" },
  { "goi", "B" },
  { "opt", "A" },
  { "lnkz", "B" },
  { "piou3", "B" },

  { "jkls", "C" },
  { "lkjs", "C" },
  { "ub", "A" },
  { "jb7f", "B" },
  { "yvb", "A" },

  { "xcuy", "B" },
  { "iuvf", "C" },
  { "xouyw", "B" },
  { "psd", "A" },
  { "lkzs", "B" },
};

#define N_CONTACTS 20

static struct Group
{
  const char *name;
  GtkTreeIter *iter_group;
} groups[] =
{
  { "A", NULL },
  { "B", NULL },
  { "C", NULL }
};

#define N_GROUPS 3

static struct Group *
get_group (const char *name)
{
  int i;

  for (i = 0; i < N_GROUPS; i++)
    {
      if (!strcmp (name, groups[i].name))
        return &groups[i];
    }

  return NULL;
}

static void
add_indiv (GtkTreeStore *store, const char *name, const char *group)
{
  struct Group *g;
  GtkTreeIter iter;

  g = get_group (group);
  if (!g->iter_group)
    {
      gtk_tree_store_append (store, &iter, NULL);
      gtk_tree_store_set (store, &iter,
                          COL_NAME, group,
                          COL_IS_CONTACT, FALSE,
                          -1);
      g->iter_group = gtk_tree_iter_copy (&iter);

      gtk_tree_store_insert_with_values (store, &iter, g->iter_group, 0,
                                         COL_IS_SEPARATOR, TRUE,
                                         COL_IS_CONTACT, FALSE,
                                         -1);
    }

  gtk_tree_store_insert_with_values (store, &iter, g->iter_group, 1,
                                     COL_NAME, name,
                                     COL_IS_CONTACT, TRUE,
                                     COL_IS_ONLINE, FALSE,
                                     COL_IS_SEPARATOR, FALSE,
                                     -1);
}

static void
remove_indiv (GtkTreeStore *store, const char *name, const char *group)
{
  gboolean valid;
  struct Group *g;
  GtkTreeIter iter;

  g = get_group (group);
  if (!g->iter_group)
    return;

  /* Look for contact */
  for (valid = gtk_tree_model_iter_children (GTK_TREE_MODEL (store), &iter, g->iter_group);
       valid; valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter))
    {
      gchar *iter_name;

      gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_NAME, &iter_name, -1);
      if (!iter_name)
        continue;

      if (!strcmp (iter_name, name))
        {
          g_free (iter_name);
          break;
        }

      g_free (iter_name);
    }

  if (!valid)
    /* Contact not found */
    return;

  if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), g->iter_group) <= 2)
    {
      gtk_tree_store_remove (store, g->iter_group);
      g->iter_group = NULL;
    }
  else
    {
      gtk_tree_store_remove (store, &iter);
    }
}

static gboolean
contact_is_visible (GtkTreeModel *model,
                    GtkTreeIter  *iter)
{
  gboolean visible = FALSE;
  gchar *name = NULL;
  gboolean is_online;

  gtk_tree_model_get (model, iter,
                      COL_NAME, &name,
                      COL_IS_ONLINE, &is_online,
                      -1);

  if (is_searching)
    {
      int i;
      int len = strlen (name);

      for (i = 0; i < len; i++)
        {
          if (name[i] == search_char)
            {
              visible = TRUE;
              break;
            }
        }
    }
  else
    {
      visible = is_online;
    }

  g_free (name);

  if (visible)
    {
      GtkTreeIter parent;
      GtkTreePath *path;

      gtk_tree_model_iter_parent (model, &parent, iter);
      path = gtk_tree_model_get_path (model, &parent);
#if 0
      gtk_tree_model_row_changed (model, path, &parent);
#endif
      gtk_tree_path_free (path);
    }

  return visible;
}

static gboolean
individual_view_visible_func (GtkTreeModel *model,
                              GtkTreeIter  *iter,
                              gpointer      data)
{
  gboolean valid;
  gboolean is_online, is_separator, is_contact;
  GtkTreeIter child;

  gtk_tree_model_get (model, iter,
                      COL_IS_ONLINE, &is_online,
                      COL_IS_SEPARATOR, &is_separator,
                      COL_IS_CONTACT, &is_contact,
                      -1);

  if (is_separator)
    return TRUE;

  if (is_contact)
    return contact_is_visible (model, iter);

  /* Otherwise, this is a group */
  for (valid = gtk_tree_model_iter_children (model, &child, iter);
       valid; valid = gtk_tree_model_iter_next (model, &child))
    {
      gtk_tree_model_get (model, &child,
                          COL_IS_CONTACT, &is_contact,
                          COL_IS_ONLINE, &is_online,
                          -1);

      if (!is_contact)
        continue;

      /* Group is visible if at least 1 contact is */
      if (contact_is_visible (model, &child))
        return TRUE;
    }

  return FALSE;
}

static void
go_online (GtkTreeStore *store)
{
  int i;

  for (i = 0; i < N_CONTACTS; i++)
    add_indiv (store, contacts[i].name, contacts[i].group);
}

static void
go_offline (GtkTreeStore *store)
{
  int i;

  for (i = 0; i < N_CONTACTS; i++)
    remove_indiv (store, contacts[i].name, contacts[i].group);
}

static void
toggle_online (GtkTreeStore *store, struct Contact *c)
{
  gboolean valid;
  struct Group *g;
  GtkTreeIter iter;
  gboolean is_online;

  g = get_group (c->group);
  if (!g->iter_group)
    return;

  /* Look for contact */
  for (valid = gtk_tree_model_iter_children (GTK_TREE_MODEL (store), &iter, g->iter_group);
       valid; valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter))
    {
      gchar *iter_name;

      gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_NAME, &iter_name, -1);
      if (!iter_name)
        continue;

      if (!strcmp (iter_name, c->name))
        {
          g_free (iter_name);
          break;
        }

      g_free (iter_name);
    }

  if (!valid)
    /* Contact not found */
    return;

  gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
                      COL_IS_ONLINE, &is_online, -1);
  is_online ^= 1;
  gtk_tree_store_set (store, &iter,
                      COL_IS_ONLINE, is_online, -1);
}

static gboolean
expand_all (GtkTreeView *tree_view)
{
  gtk_tree_view_expand_all (tree_view);

  return FALSE;
}

static void
row_has_child_toggled (GtkTreeModel *model,
                       GtkTreePath *path,
                       GtkTreeIter *iter,
                       GtkTreeView *tree_view)
{
  if (gtk_tree_path_get_depth (path) != 1)
    return;

  g_idle_add ((GSourceFunc) expand_all, tree_view);
}

static void
ensure_group_visible (GtkTreeModel *model,
                      GtkTreePath *path)
{
  GtkTreePath *parent_path;
  GtkTreeIter parent_iter;

  if (gtk_tree_path_get_depth (path) < 2)
    return;

  parent_path = gtk_tree_path_copy (path);
  gtk_tree_path_up (parent_path);
  gtk_tree_model_get_iter (model, &parent_iter, parent_path);
  gtk_tree_model_row_changed (model, parent_path, &parent_iter);
  gtk_tree_path_free (parent_path);
}

static void
row_changed (GtkTreeModel *model,
             GtkTreePath *path,
             GtkTreeIter *iter,
             gpointer user_data)
{
  ensure_group_visible (model, path);
}

static void
row_deleted (GtkTreeModel *model,
             GtkTreePath *path,
             gpointer user_data)
{
  ensure_group_visible (model, path);
}


int
main (void)
{
  int i;
  GtkTreeStore *store;
  GtkTreeModel *filter;
  GtkWidget *tree_view;

  gtk_init (NULL, NULL);
/* sranddev(); */

  store = gtk_tree_store_new (N_COL,
                              G_TYPE_BOOLEAN,
                              G_TYPE_BOOLEAN,
                              G_TYPE_BOOLEAN,
                              G_TYPE_STRING);
  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
                                        COL_NAME, GTK_SORT_ASCENDING);
  filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL);
  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
                                          individual_view_visible_func,
                                          NULL,
                                          NULL);

  tree_view = gtk_tree_view_new ();

  /* row-has-child-toggled is connect before tree view gets a chance to */
  g_signal_connect (filter, "row-has-child-toggled",
                    G_CALLBACK (row_has_child_toggled), tree_view);

  gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), GTK_TREE_MODEL (filter));
  g_signal_connect (store, "row-inserted",
                    G_CALLBACK (row_changed), NULL);
  g_signal_connect (store, "row-changed",
                    G_CALLBACK (row_changed), NULL);
  g_signal_connect (store, "row-deleted",
                    G_CALLBACK (row_deleted), NULL);

  go_online (store);

  /* Do random stuff */
  for (i = 0; i < 10000; i++)
    {
      int c = ((float)rand() / RAND_MAX) * (float)N_CONTACTS;
      int op = ((float)rand() / RAND_MAX) * 8.0;

      if (op <= 4)
        toggle_online (store, &contacts[c]);
      else if (op <= 6)
        {
          /* try search */
          int ch = 97.0 + ((float)rand() / RAND_MAX) * 26.0;

          is_searching = TRUE;
          search_char = ch;
          gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter));

          gtk_main_iteration_do (FALSE);

          is_searching = FALSE;
          gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter));

          gtk_main_iteration_do (FALSE);
        }
      else
        {
          /* network disruption? */
          go_offline (store);
          go_online (store);
        }

      gtk_main_iteration_do (FALSE);
    }

  go_offline (store);

  g_assert (gtk_tree_model_iter_n_children (filter, NULL) == 0);

  return 0;
}
