/* Buzztrax
 * Copyright (C) 2014 Buzztrax team <buzztrax-devel@buzztrax.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 */
/**
 * SECTION:btpresetlistmodel
 * @short_description: data model class for widgets showing presets of a machine
 *
 * A generic model representing the presets of a machine, suitable for
 * combo-boxes and treeview widgets.
 *
 * One needs to manually tell the model of new, removed and changed presets.
 */

#define BT_EDIT
#define BT_PRESET_LIST_MODEL_C

#include "bt-edit.h"

//-- defines
#define N_COLUMNS __BT_PRESET_LIST_MODEL_N_COLUMNS

//-- globals

static GQuark singleton_quark;

//-- structs

struct _BtPresetListModelPrivate
{
  gint stamp;

  GstElement *machine;
  GType param_types[N_COLUMNS];
  GSequence *seq;
};

//-- the class

static void bt_preset_list_model_tree_model_init (gpointer const g_iface,
    gpointer const iface_data);

G_DEFINE_TYPE_WITH_CODE (BtPresetListModel, bt_preset_list_model,
    G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
        bt_preset_list_model_tree_model_init));

//-- helper

// we are comparing by name
static gint
model_item_cmp (gconstpointer a, gconstpointer b, gpointer data)
{
  return strcmp (a, b);
}


static void
list_model_add (BtPresetListModel * model, gchar * preset)
{
  GSequence *seq = model->priv->seq;
  GtkTreePath *path;
  GtkTreeIter iter;
  gint position;

  GST_INFO ("add preset %s to model", preset);

  // insert new entry
  iter.stamp = model->priv->stamp;
  iter.user_data = g_sequence_insert_sorted (seq, preset, model_item_cmp, NULL);
  position = g_sequence_iter_get_position (iter.user_data);

  // signal to the view/app
  path = gtk_tree_path_new ();
  gtk_tree_path_append_index (path, position);
  gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
  gtk_tree_path_free (path);
}

//-- constructor methods

/**
 * bt_preset_list_model_new:
 * @machine: the gstreamer element of the machine
 *
 * Creates a list model of presets for the @machine. The model is automatically
 * updated when presets are added, removed or renamed.
 *
 * Returns: the preset model.
 */
BtPresetListModel *
bt_preset_list_model_new (GstElement * machine)
{
  BtPresetListModel *self;
  GType type;

  type = G_OBJECT_TYPE (machine);

  // Make this a singleton per machine, this way we can keep the lists in sync
  // in the app. A file-system watcher would be even nicer though.
  if ((self = g_type_get_qdata (type, singleton_quark))) {
    self = g_object_ref (self);
  } else {
    gchar **presets, **preset;

    self = g_object_new (BT_TYPE_PRESET_LIST_MODEL, NULL);

    self->priv->param_types[BT_PRESET_LIST_MODEL_LABEL] = G_TYPE_STRING;
    self->priv->param_types[BT_PRESET_LIST_MODEL_COMMENT] = G_TYPE_STRING;

    self->priv->machine = g_object_ref (machine);
    presets = gst_preset_get_preset_names (GST_PRESET (machine));
    if (presets) {
      for (preset = presets; *preset; preset++) {
        list_model_add (self, g_strdup (*preset));
      }
      g_strfreev (presets);
    }
    // add an extra ref, this way we leak the reference though
    g_type_set_qdata (type, singleton_quark, g_object_ref (self));
  }
  return self;
}

//-- methods

/**
 * bt_preset_list_model_add:
 * @model: the model
 * @preset: the new preset
 *
 * Notifies the model that a new preset was added.
 */
void
bt_preset_list_model_add (BtPresetListModel * model, gchar * preset)
{
  list_model_add (model, preset);
}

/**
 * bt_preset_list_model_remove:
 * @model: the model
 * @preset: the removed preset
 *
 * Notifies the model that a preset was removed.
 */
void
bt_preset_list_model_remove (BtPresetListModel * model, gchar * preset)
{
  GSequence *seq = model->priv->seq;
  GtkTreePath *path;
  GSequenceIter *iter;
  gint position;

  GST_INFO ("removing preset %s from model", preset);

  // remove entry
  iter = g_sequence_lookup (seq, preset, model_item_cmp, NULL);
  position = g_sequence_iter_get_position (iter);
  g_sequence_remove (iter);

  // signal to the view/app
  path = gtk_tree_path_new ();
  gtk_tree_path_append_index (path, position);
  gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
  gtk_tree_path_free (path);

  GST_INFO ("removed preset %s from model at pos %d ", preset, position);
}

/**
 * bt_preset_list_model_rename:
 * @model: the model
 * @o_preset: the previous preset name
 * @n_preset: the new preset name
 *
 * Notifies the model that a preset was renamed.
 */
void
bt_preset_list_model_rename (BtPresetListModel * model, gchar * o_preset,
    gchar * n_preset)
{
  GSequence *seq = model->priv->seq;
  GtkTreePath *path;
  GtkTreeIter iter;
  gint pos1, pos2;

  iter.stamp = model->priv->stamp;
  iter.user_data = g_sequence_lookup (seq, o_preset, model_item_cmp, NULL);
  pos1 = g_sequence_iter_get_position (iter.user_data);

  g_sequence_set (iter.user_data, n_preset);
  g_sequence_sort_changed (iter.user_data, model_item_cmp, NULL);
  pos2 = g_sequence_iter_get_position (iter.user_data);

  GST_DEBUG ("pos %d -> %d", pos1, pos2);

  // signal updates
  if (pos1 != pos2) {
    path = gtk_tree_path_new ();
    gtk_tree_path_append_index (path, pos1);
    gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
    gtk_tree_path_free (path);
    path = gtk_tree_path_new ();
    gtk_tree_path_append_index (path, pos2);
    gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
    gtk_tree_path_free (path);
  } else {
    path = gtk_tree_path_new ();
    gtk_tree_path_append_index (path, pos2);
    gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
    gtk_tree_path_free (path);
  }
}

/**
 * bt_preset_list_model_find_iter:
 * @model: the model
 * @preset: the selected preset
 * @iter: the tree iter for the selection
 *
 * Find the preset and return the iter to the row.
 *
 * Returns: TRUE of the preset has been found and @iter is set.
 */
gboolean
bt_preset_list_model_find_iter (BtPresetListModel * model, gchar * preset,
    GtkTreeIter * iter)
{
  GSequence *seq = model->priv->seq;

  iter->stamp = model->priv->stamp;
  iter->user_data = g_sequence_lookup (seq, preset, model_item_cmp, NULL);
  return iter->user_data != NULL;
}

//-- tree model interface

static GtkTreeModelFlags
bt_preset_list_model_tree_model_get_flags (GtkTreeModel * tree_model)
{
  return (GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY);
}

static gint
bt_preset_list_model_tree_model_get_n_columns (GtkTreeModel * tree_model)
{
  return (N_COLUMNS);
}

static GType
bt_preset_list_model_tree_model_get_column_type (GtkTreeModel * tree_model,
    gint index)
{
  BtPresetListModel *model = BT_PRESET_LIST_MODEL (tree_model);

  g_return_val_if_fail (index < N_COLUMNS, G_TYPE_INVALID);

  return (model->priv->param_types[index]);
}

static gboolean
bt_preset_list_model_tree_model_get_iter (GtkTreeModel * tree_model,
    GtkTreeIter * iter, GtkTreePath * path)
{
  BtPresetListModel *model = BT_PRESET_LIST_MODEL (tree_model);
  GSequence *seq = model->priv->seq;
  gint i = gtk_tree_path_get_indices (path)[0];

  if (i >= g_sequence_get_length (seq))
    return (FALSE);

  iter->stamp = model->priv->stamp;
  iter->user_data = g_sequence_get_iter_at_pos (seq, i);

  return (TRUE);
}

static GtkTreePath *
bt_preset_list_model_tree_model_get_path (GtkTreeModel * tree_model,
    GtkTreeIter * iter)
{
  BtPresetListModel *model = BT_PRESET_LIST_MODEL (tree_model);
  GtkTreePath *path;

  g_return_val_if_fail (iter->stamp == model->priv->stamp, NULL);

  if (g_sequence_iter_is_end (iter->user_data))
    return (NULL);

  path = gtk_tree_path_new ();
  gtk_tree_path_append_index (path,
      g_sequence_iter_get_position (iter->user_data));

  return (path);
}

static void
bt_preset_list_model_tree_model_get_value (GtkTreeModel * tree_model,
    GtkTreeIter * iter, gint column, GValue * value)
{
  BtPresetListModel *model = BT_PRESET_LIST_MODEL (tree_model);
  gchar *preset, *comment;

  g_return_if_fail (column < N_COLUMNS);

  g_value_init (value, model->priv->param_types[column]);
  if ((preset = g_sequence_get (iter->user_data))) {
    switch (column) {
      case BT_PRESET_LIST_MODEL_LABEL:
        g_value_set_string (value, preset);
        break;
      case BT_PRESET_LIST_MODEL_COMMENT:
        gst_preset_get_meta (GST_PRESET (model->priv->machine), preset,
            "comment", &comment);
        g_value_take_string (value, comment);
        break;
    }
  }
}

static gboolean
bt_preset_list_model_tree_model_iter_next (GtkTreeModel * tree_model,
    GtkTreeIter * iter)
{
  BtPresetListModel *model = BT_PRESET_LIST_MODEL (tree_model);
  gboolean res;

  g_return_val_if_fail (model->priv->stamp == iter->stamp, FALSE);

  iter->user_data = g_sequence_iter_next (iter->user_data);
  if ((res = g_sequence_iter_is_end (iter->user_data)))
    iter->stamp = 0;

  return !res;
}

static gboolean
bt_preset_list_model_tree_model_iter_children (GtkTreeModel * tree_model,
    GtkTreeIter * iter, GtkTreeIter * parent)
{
  BtPresetListModel *model = BT_PRESET_LIST_MODEL (tree_model);

  /* this is a list, nodes have no children */
  if (!parent) {
    if (g_sequence_get_length (model->priv->seq) > 0) {
      iter->stamp = model->priv->stamp;
      iter->user_data = g_sequence_get_begin_iter (model->priv->seq);
      return (TRUE);
    }
  }
  iter->stamp = 0;
  return (FALSE);
}

static gboolean
bt_preset_list_model_tree_model_iter_has_child (GtkTreeModel * tree_model,
    GtkTreeIter * iter)
{
  return (FALSE);
}

static gint
bt_preset_list_model_tree_model_iter_n_children (GtkTreeModel * tree_model,
    GtkTreeIter * iter)
{
  BtPresetListModel *model = BT_PRESET_LIST_MODEL (tree_model);

  if (iter == NULL)
    return g_sequence_get_length (model->priv->seq);

  g_return_val_if_fail (model->priv->stamp == iter->stamp, -1);
  return (0);
}

static gboolean
bt_preset_list_model_tree_model_iter_nth_child (GtkTreeModel * tree_model,
    GtkTreeIter * iter, GtkTreeIter * parent, gint n)
{
  BtPresetListModel *model = BT_PRESET_LIST_MODEL (tree_model);
  GSequenceIter *child;

  iter->stamp = 0;

  if (parent)
    return (FALSE);

  child = g_sequence_get_iter_at_pos (model->priv->seq, n);

  if (g_sequence_iter_is_end (child))
    return (FALSE);

  iter->stamp = model->priv->stamp;
  iter->user_data = child;

  return (TRUE);
}

static gboolean
bt_preset_list_model_tree_model_iter_parent (GtkTreeModel * tree_model,
    GtkTreeIter * iter, GtkTreeIter * child)
{
  iter->stamp = 0;
  return (FALSE);
}

static void
bt_preset_list_model_tree_model_init (gpointer const g_iface,
    gpointer const iface_data)
{
  GtkTreeModelIface *const iface = g_iface;

  iface->get_flags = bt_preset_list_model_tree_model_get_flags;
  iface->get_n_columns = bt_preset_list_model_tree_model_get_n_columns;
  iface->get_column_type = bt_preset_list_model_tree_model_get_column_type;
  iface->get_iter = bt_preset_list_model_tree_model_get_iter;
  iface->get_path = bt_preset_list_model_tree_model_get_path;
  iface->get_value = bt_preset_list_model_tree_model_get_value;
  iface->iter_next = bt_preset_list_model_tree_model_iter_next;
  iface->iter_children = bt_preset_list_model_tree_model_iter_children;
  iface->iter_has_child = bt_preset_list_model_tree_model_iter_has_child;
  iface->iter_n_children = bt_preset_list_model_tree_model_iter_n_children;
  iface->iter_nth_child = bt_preset_list_model_tree_model_iter_nth_child;
  iface->iter_parent = bt_preset_list_model_tree_model_iter_parent;
}

//-- class internals

static void
bt_preset_list_model_finalize (GObject * object)
{
  BtPresetListModel *self = BT_PRESET_LIST_MODEL (object);

  GST_DEBUG ("!!!! self=%p", self);

  g_object_unref (self->priv->machine);
  g_sequence_free (self->priv->seq);

  G_OBJECT_CLASS (bt_preset_list_model_parent_class)->finalize (object);
}

static void
bt_preset_list_model_init (BtPresetListModel * self)
{
  self->priv =
      G_TYPE_INSTANCE_GET_PRIVATE (self, BT_TYPE_PRESET_LIST_MODEL,
      BtPresetListModelPrivate);

  self->priv->seq = g_sequence_new (NULL);
  // random int to check whether an iter belongs to our model
  self->priv->stamp = g_random_int ();
}

static void
bt_preset_list_model_class_init (BtPresetListModelClass * klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  g_type_class_add_private (klass, sizeof (BtPresetListModelPrivate));

  singleton_quark = g_quark_from_static_string ("BtPresetListModel::singleton");

  gobject_class->finalize = bt_preset_list_model_finalize;
}
