/* dzl-list-store-adapter.c
 *
 * Copyright (C) 2017 Christian Hergert <chergert@redhat.com>
 *
 * This file is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This file 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 Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#define G_LOG_DOMAIN "dzl-list-store-adapter"

#include "bindings/dzl-signal-group.h"
#include "tree/dzl-list-store-adapter.h"

typedef struct
{
  DzlSignalGroup *signals;
  GListModel *model;
  gint length;
  GType type;
} DzlListStoreAdapterPrivate;

enum {
  PROP_0,
  PROP_MODEL,
  N_PROPS
};

static void tree_model_iface_init (GtkTreeModelIface *iface);

G_DEFINE_TYPE_WITH_CODE (DzlListStoreAdapter, dzl_list_store_adapter, G_TYPE_OBJECT,
                         G_ADD_PRIVATE (DzlListStoreAdapter)
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, tree_model_iface_init))

static GParamSpec *properties [N_PROPS];

static GtkTreeModelFlags
dzl_list_store_adapter_get_flags (GtkTreeModel *model)
{
  return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY;
}

static gint
dzl_list_store_adapter_get_n_columns (GtkTreeModel *model)
{
  return 1;
}

static GType
dzl_list_store_adapter_get_column_type (GtkTreeModel *model,
                                        gint          column)
{
  DzlListStoreAdapter *self = DZL_LIST_STORE_ADAPTER (model);
  DzlListStoreAdapterPrivate *priv = dzl_list_store_adapter_get_instance_private (self);

  if (column == 0)
    return priv->type;

  return G_TYPE_INVALID;
}

static gboolean
dzl_list_store_adapter_get_iter (GtkTreeModel *model,
                                 GtkTreeIter  *iter,
                                 GtkTreePath  *path)
{
  DzlListStoreAdapter *self = (DzlListStoreAdapter *)model;
  DzlListStoreAdapterPrivate *priv = dzl_list_store_adapter_get_instance_private (self);
  gint pos;

  g_assert (DZL_IS_LIST_STORE_ADAPTER (self));
  g_assert (iter != NULL);
  g_assert (path != NULL);

  if (gtk_tree_path_get_depth (path) != 1)
    return FALSE;

  pos = gtk_tree_path_get_indices (path) [0];

  if (pos >= priv->length)
    return FALSE;

  iter->user_data = GINT_TO_POINTER (pos);

  return TRUE;
}

static GtkTreePath *
dzl_list_store_adapter_get_path (GtkTreeModel *model,
                                 GtkTreeIter  *iter)
{
  return gtk_tree_path_new_from_indices (GPOINTER_TO_INT (iter->user_data), -1);
}

static void
dzl_list_store_adapter_get_value (GtkTreeModel *model,
                                  GtkTreeIter  *iter,
                                  gint          column,
                                  GValue       *value)
{
  DzlListStoreAdapter *self = DZL_LIST_STORE_ADAPTER (model);
  DzlListStoreAdapterPrivate *priv = dzl_list_store_adapter_get_instance_private (self);

  g_value_init (value, priv->type);
  g_value_take_object (value,
                       g_list_model_get_item (priv->model,
                                              GPOINTER_TO_INT (iter->user_data)));
}

static gboolean
dzl_list_store_adapter_iter_next (GtkTreeModel *model,
                                  GtkTreeIter  *iter)
{
  DzlListStoreAdapter *self = DZL_LIST_STORE_ADAPTER (model);
  DzlListStoreAdapterPrivate *priv = dzl_list_store_adapter_get_instance_private (self);
  gint pos = GPOINTER_TO_INT (iter->user_data) + 1;

  iter->user_data = GINT_TO_POINTER (pos);

  return pos < priv->length;
}

static gboolean
dzl_list_store_adapter_iter_previous (GtkTreeModel *model,
                                      GtkTreeIter  *iter)
{
  gint pos = GPOINTER_TO_INT (iter->user_data) - 1;

  iter->user_data = GINT_TO_POINTER (pos);

  return pos >= 0;
}

static gboolean
dzl_list_store_adapter_iter_children (GtkTreeModel *model,
                                      GtkTreeIter  *iter,
                                      GtkTreeIter  *parent)
{
  if (parent != NULL)
    return FALSE;

  iter->user_data = NULL;

  return TRUE;
}

static gboolean
dzl_list_store_adapter_iter_has_child (GtkTreeModel *model,
                                       GtkTreeIter  *iter)
{
  return iter == NULL;
}

static gint
dzl_list_store_adapter_iter_n_children (GtkTreeModel *model,
                                        GtkTreeIter  *iter)
{
  DzlListStoreAdapter *self = DZL_LIST_STORE_ADAPTER (model);
  DzlListStoreAdapterPrivate *priv = dzl_list_store_adapter_get_instance_private (self);

  if (iter == NULL)
    return priv->length;

  return 0;
}

static gboolean
dzl_list_store_adapter_iter_parent (GtkTreeModel *model,
                                    GtkTreeIter  *iter,
                                    GtkTreeIter  *child)
{
  return FALSE;
}

static gboolean
dzl_list_store_adapter_iter_nth_child (GtkTreeModel *model,
                                       GtkTreeIter  *iter,
                                       GtkTreeIter  *parent,
                                       gint          nth)
{
  DzlListStoreAdapter *self = DZL_LIST_STORE_ADAPTER (model);
  DzlListStoreAdapterPrivate *priv = dzl_list_store_adapter_get_instance_private (self);

  if (parent == NULL && nth < priv->length)
    {
      iter->user_data = GINT_TO_POINTER (nth);
      return TRUE;
    }

  return FALSE;
}

static void
tree_model_iface_init (GtkTreeModelIface *iface)
{
  iface->get_flags = dzl_list_store_adapter_get_flags;
  iface->get_n_columns = dzl_list_store_adapter_get_n_columns;
  iface->get_column_type = dzl_list_store_adapter_get_column_type;
  iface->get_iter = dzl_list_store_adapter_get_iter;
  iface->get_path = dzl_list_store_adapter_get_path;
  iface->get_value = dzl_list_store_adapter_get_value;
  iface->iter_next = dzl_list_store_adapter_iter_next;
  iface->iter_previous = dzl_list_store_adapter_iter_previous;
  iface->iter_children = dzl_list_store_adapter_iter_children;
  iface->iter_has_child = dzl_list_store_adapter_iter_has_child;
  iface->iter_n_children = dzl_list_store_adapter_iter_n_children;
  iface->iter_nth_child = dzl_list_store_adapter_iter_nth_child;
  iface->iter_parent = dzl_list_store_adapter_iter_parent;
}

static void
dzl_list_store_adapter_items_changed (DzlListStoreAdapter *self,
                                      guint                position,
                                      guint                removed,
                                      guint                added,
                                      GListModel          *model)
{
  DzlListStoreAdapterPrivate *priv = dzl_list_store_adapter_get_instance_private (self);
  GtkTreePath *path;
  GtkTreeIter iter = { 0 };

  g_assert (DZL_IS_LIST_STORE_ADAPTER (self));
  g_assert (G_IS_LIST_MODEL (model));

  priv->length -= removed;
  priv->length += added;

  path = gtk_tree_path_new_from_indices (position, -1);

  for (guint i = 0; i < removed; i++)
    gtk_tree_model_row_deleted (GTK_TREE_MODEL (self), path);

  for (guint i = 0; i < added; i++)
    {
      iter.user_data = GINT_TO_POINTER (position + i);
      gtk_tree_model_row_inserted (GTK_TREE_MODEL (self), path, &iter);
      gtk_tree_path_next (path);
    }

  gtk_tree_path_free (path);
}

static void
dzl_list_store_adapter_bind (DzlListStoreAdapter *self,
                             GListModel          *model,
                             DzlSignalGroup      *signals)
{
  DzlListStoreAdapterPrivate *priv = dzl_list_store_adapter_get_instance_private (self);

  g_assert (DZL_IS_LIST_STORE_ADAPTER (self));
  g_assert (G_IS_LIST_MODEL (model));
  g_assert (DZL_IS_SIGNAL_GROUP (signals));

  priv->model = model;
  priv->type = g_list_model_get_item_type (model);
  priv->length = g_list_model_get_n_items (model);
}

static void
dzl_list_store_adapter_unbind (DzlListStoreAdapter *self,
                               DzlSignalGroup      *signals)
{
  DzlListStoreAdapterPrivate *priv = dzl_list_store_adapter_get_instance_private (self);

  g_assert (DZL_IS_LIST_STORE_ADAPTER (self));
  g_assert (DZL_IS_SIGNAL_GROUP (signals));

  priv->model = NULL;
  priv->length = 0;
  priv->type = G_TYPE_OBJECT;
}

static void
dzl_list_store_adapter_finalize (GObject *object)
{
  DzlListStoreAdapter *self = (DzlListStoreAdapter *)object;
  DzlListStoreAdapterPrivate *priv = dzl_list_store_adapter_get_instance_private (self);

  g_clear_object (&priv->signals);

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

static void
dzl_list_store_adapter_get_property (GObject    *object,
                                     guint       prop_id,
                                     GValue     *value,
                                     GParamSpec *pspec)
{
  DzlListStoreAdapter *self = DZL_LIST_STORE_ADAPTER (object);

  switch (prop_id)
    {
    case PROP_MODEL:
      g_value_set_object (value, dzl_list_store_adapter_get_model (self));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
dzl_list_store_adapter_set_property (GObject      *object,
                                     guint         prop_id,
                                     const GValue *value,
                                     GParamSpec   *pspec)
{
  DzlListStoreAdapter *self = DZL_LIST_STORE_ADAPTER (object);

  switch (prop_id)
    {
    case PROP_MODEL:
      dzl_list_store_adapter_set_model (self, g_value_get_object (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
dzl_list_store_adapter_class_init (DzlListStoreAdapterClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->finalize = dzl_list_store_adapter_finalize;
  object_class->get_property = dzl_list_store_adapter_get_property;
  object_class->set_property = dzl_list_store_adapter_set_property;

  properties [PROP_MODEL] =
    g_param_spec_object ("model",
                         "Model",
                         "The model to be adapted",
                         G_TYPE_LIST_MODEL,
                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_properties (object_class, N_PROPS, properties);
}

static void
dzl_list_store_adapter_init (DzlListStoreAdapter *self)
{
  DzlListStoreAdapterPrivate *priv = dzl_list_store_adapter_get_instance_private (self);

  priv->type = G_TYPE_OBJECT;
  priv->signals = dzl_signal_group_new (G_TYPE_LIST_MODEL);

  dzl_signal_group_connect_swapped (priv->signals,
                                    "items-changed",
                                    G_CALLBACK (dzl_list_store_adapter_items_changed),
                                    self);

  g_signal_connect_swapped (priv->signals,
                            "bind",
                            G_CALLBACK (dzl_list_store_adapter_bind),
                            self);

  g_signal_connect_swapped (priv->signals,
                            "unbind",
                            G_CALLBACK (dzl_list_store_adapter_unbind),
                            self);
}

DzlListStoreAdapter *
dzl_list_store_adapter_new (GListModel *model)
{
  return g_object_new (DZL_TYPE_LIST_STORE_ADAPTER,
                       "model", model,
                       NULL);
}

/**
 * dzl_list_store_adapter_get_model:
 * @self: A #DzlListStoreAdapter
 *
 * Gets the model being adapted.
 *
 * Returns: (transfer none): A #GListModel
 *
 * Since: 3.26
 */
GListModel *
dzl_list_store_adapter_get_model (DzlListStoreAdapter *self)
{
  DzlListStoreAdapterPrivate *priv = dzl_list_store_adapter_get_instance_private (self);

  g_return_val_if_fail (DZL_IS_LIST_STORE_ADAPTER (self), NULL);

  return dzl_signal_group_get_target (priv->signals);
}

void
dzl_list_store_adapter_set_model (DzlListStoreAdapter *self,
                                  GListModel          *model)
{
  DzlListStoreAdapterPrivate *priv = dzl_list_store_adapter_get_instance_private (self);

  g_return_if_fail (DZL_IS_LIST_STORE_ADAPTER (self));
  g_return_if_fail (!model || G_IS_LIST_MODEL (model));

  dzl_signal_group_set_target (priv->signals, model);
  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MODEL]);
}
