/*
     This file is part of GNUnet
     Copyright (C) 2012 GNUnet e.V.

     GNUnet is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published
     by the Free Software Foundation; either version 3, or (at your
     option) any later version.

     GNUnet 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
     General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with GNUnet; see the file COPYING.  If not, write to the
     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     Boston, MA 02110-1301, USA.
*/

/**
 * @file src/fs/gnunet-fs-gtk_unindex.c
 * @author Christian Grothoff
 * @brief operations to display and unindex indexed files
 */
#include "gnunet-fs-gtk.h"
#include "gnunet-fs-gtk_common.h"
#include "gnunet-fs-gtk_event-handler.h"
#include "gnunet-fs-gtk_unindex.h"


/**
 * Columns in the unindex model.
 */
enum UNINDEX_ModelColumns
{
  /**
     * A gchararray.
     */
  UNINDEX_MC_FILENAME = 0,

  /**
     * A guint64.
     */
  UNINDEX_MC_FILESIZE = 1,

  /**
     * A gchararray representing a color.
     */
  UNINDEX_MC_BACKGROUND_COLOR = 2,

  /**
     * A 'struct UnindexEntry' (gpointer)
     */
  UNINDEX_MC_UNINDEX_CONTEXT = 3,

  /**
     * A gfloat.
     */
  UNINDEX_MC_UNINDEX_PROGRESS = 4,

  /**
     * A boolean.
     */
  UNINDEX_MC_PROGRESS_VISIBLE = 5,

  /**
     * A gchararray.
     */
  UNINDEX_MC_ERROR = 6,

  /**
     * A GdkPixbuf.
     */
  UNINDEX_MC_STATUS_ICON = 7
};


/**
 * Overall context for the dialog.
 */
struct UnindexDialogContext
{
  GtkBuilder *builder;

  /**
   * The dialog object.
   */
  GtkWidget *dialog;

  /**
   * The unindex button.
   */
  GtkWidget *unindex_button;

  /**
   * Main treeview object.
   */
  GtkTreeView *treeview;

  /**
   * Selection of our treeview.
   */
  GtkTreeSelection *selection;

  /**
   * Unindex data model.
   */
  GtkTreeModel *model;

  /**
   * Context for initial listing of indexed files.
   */
  struct GNUNET_FS_GetIndexedContext *gic;
};


/**
 * Information for a specific unindex operation.
 */
struct UnindexEntry
{

  /**
   * Kept in a DLL.
   */
  struct UnindexEntry *next;

  /**
   * Next entry.
   */
  struct UnindexEntry *prev;

  /**
   * Name of the file being unindexed.
   */
  char *filename;

  /**
   * Error description, NULL if no error
   */
  char *emsg;

  /**
   * Handle with FS.
   */
  struct GNUNET_FS_UnindexContext *uc;

  /**
   * Row reference, NULL if the dialog is not open.
   */
  GtkTreeRowReference *rr;

  /**
   * Size of the file.
   */
  uint64_t filesize;

  /**
   * Percentage progress made so far.
   */
  gint progress;
};


/**
 * There can only be one.
 */
static struct UnindexDialogContext *master_udc;

/**
 * Head of linked list.
 */
static struct UnindexEntry *ue_head;

/**
 * Tail of linked list.
 */
static struct UnindexEntry *ue_tail;


/**
 * User has clicked on the 'delete/unindex' button for the dialog.
 * Unindex the selected file.
 *
 * @param dummy the button
 * @param user_data the unindex context
 */
void
GNUNET_FS_GTK_unindex_button_clicked_cb (GtkWidget *dummy, gpointer user_data)
{
  struct UnindexDialogContext *udc = user_data;
  GtkTreeIter iter;
  char *filename;
  struct UnindexEntry *ue;
  GtkTreePath *path;
  guint64 filesize;

  /* find out if a file is selected */
  if (! gtk_tree_selection_get_selected (udc->selection, NULL, &iter))
  {
    GNUNET_break (0);
    return;
  }
  gtk_tree_model_get (udc->model,
                      &iter,
                      UNINDEX_MC_FILENAME,
                      &filename,
                      UNINDEX_MC_FILESIZE,
                      &filesize,
                      UNINDEX_MC_UNINDEX_CONTEXT,
                      &ue,
                      -1);
  if (NULL != ue)
  {
    GNUNET_break (0);
    g_free (filename);
    return;
  }
  ue = GNUNET_new (struct UnindexEntry);
  ue->filesize = filesize;
  GNUNET_CONTAINER_DLL_insert (ue_head, ue_tail, ue);
  ue->filename = GNUNET_strdup (filename);
  path = gtk_tree_model_get_path (udc->model, &iter);
  ue->rr = gtk_tree_row_reference_new (udc->model, path);
  gtk_tree_path_free (path);
  ue->uc =
    GNUNET_FS_unindex_start (GNUNET_FS_GTK_get_fs_handle (), filename, ue);
  gtk_list_store_set (GTK_LIST_STORE (udc->model),
                      &iter,
                      UNINDEX_MC_BACKGROUND_COLOR,
                      "yellow",
                      UNINDEX_MC_UNINDEX_CONTEXT,
                      ue,
                      UNINDEX_MC_UNINDEX_PROGRESS,
                      0,
                      UNINDEX_MC_PROGRESS_VISIBLE,
                      TRUE,
                      -1);
  gtk_widget_set_sensitive (udc->unindex_button, FALSE);
}


/**
 * User has clicked on the 'close' button for the dialog. Close it.
 *
 * @param dummy the button
 * @param user_data the unindex context
 */
void
GNUNET_FS_GTK_unindex_close_button_clicked_cb (GtkWidget *dummy,
                                               gpointer user_data)
{
  struct UnindexDialogContext *udc = user_data;
  GtkTreeIter iter;
  struct UnindexEntry *ue;

  if (NULL != udc->gic)
  {
    GNUNET_FS_get_indexed_files_cancel (udc->gic);
    udc->gic = NULL;
  }
  if (gtk_tree_model_get_iter_first (udc->model, &iter))
    do
    {
      gtk_tree_model_get (udc->model,
                          &iter,
                          UNINDEX_MC_UNINDEX_CONTEXT,
                          &ue,
                          -1);
      if (NULL != ue)
      {
        gtk_tree_row_reference_free (ue->rr);
        ue->rr = NULL;
      }
    } while (TRUE == gtk_tree_model_iter_next (udc->model, &iter));
  gtk_widget_destroy (udc->dialog);
  g_object_unref (G_OBJECT (udc->builder));
  GNUNET_free (udc);
  master_udc = NULL;
}


/**
 * The selection in the tree view changed; update the button sensitivity.
 *
 * @param ts the changed selection
 * @param user_data our unindex context
 */
void
GNUNET_FS_GTK_unindex_treeview_selection_changed_cb (GtkTreeSelection *ts,
                                                     gpointer user_data)
{
  struct UnindexDialogContext *udc = user_data;
  GtkTreeIter iter;
  struct UnindexEntry *ue;

  /* find out if a file is selected */
  if (gtk_tree_selection_get_selected (udc->selection, NULL, &iter))
  {
    gtk_tree_model_get (udc->model, &iter, UNINDEX_MC_UNINDEX_CONTEXT, &ue, -1);
    if (NULL == ue)
    {
      /* selected file not already being unindexed, enable button! */
      gtk_widget_set_sensitive (udc->unindex_button, TRUE);
      return;
    }
  }
  gtk_widget_set_sensitive (udc->unindex_button, FALSE);
}


/**
 * Type of a function called by "GNUNET_FS_get_indexed_files".
 *
 * @param cls closure
 * @param filename the name of the file, NULL for end of list
 * @param file_id hash of the contents of the indexed file
 * @return GNUNET_OK to continue iteration, GNUNET_SYSERR to abort
 */
static int
add_indexed_file (void *cls,
                  const char *filename,
                  const struct GNUNET_HashCode *file_id)
{
  struct UnindexDialogContext *udc = cls;
  GtkTreeIter iter;
  uint64_t filesize;
  struct UnindexEntry *ue;
  GtkTreePath *path;

  if (NULL == filename)
  {
    /* end of list */
    udc->gic = NULL;
    return GNUNET_OK;
  }
  if (GNUNET_OK !=
      GNUNET_DISK_file_size (filename, &filesize, GNUNET_YES, GNUNET_YES))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                _ ("Could not access indexed file `%s'\n"),
                filename);
    return GNUNET_OK;
  }
  for (ue = ue_head; ue != NULL; ue = ue->next)
    if (0 == strcmp (ue->filename, filename))
      break;
  if (NULL == ue)
  {
    gtk_list_store_insert_with_values (GTK_LIST_STORE (udc->model),
                                       &iter,
                                       G_MAXINT,
                                       UNINDEX_MC_FILENAME,
                                       filename,
                                       UNINDEX_MC_FILESIZE,
                                       (guint64) filesize,
                                       UNINDEX_MC_BACKGROUND_COLOR,
                                       "white",
                                       UNINDEX_MC_PROGRESS_VISIBLE,
                                       FALSE,
                                       -1);
  }
  else
  {
    if (NULL != ue->rr)
    {
      GNUNET_break (0);
      return GNUNET_OK;
    }
    gtk_list_store_insert_with_values (GTK_LIST_STORE (udc->model),
                                       &iter,
                                       G_MAXINT,
                                       UNINDEX_MC_FILENAME,
                                       filename,
                                       UNINDEX_MC_FILESIZE,
                                       (guint64) filesize,
                                       UNINDEX_MC_BACKGROUND_COLOR,
                                       (NULL == ue->emsg) ? "yellow" : "red",
                                       UNINDEX_MC_UNINDEX_CONTEXT,
                                       ue,
                                       UNINDEX_MC_UNINDEX_PROGRESS,
                                       ue->progress,
                                       UNINDEX_MC_PROGRESS_VISIBLE,
                                       TRUE,
                                       UNINDEX_MC_ERROR,
                                       ue->emsg,
                                       -1);
    path = gtk_tree_model_get_path (udc->model, &iter);
    ue->rr = gtk_tree_row_reference_new (udc->model, path);
  }
  return GNUNET_OK;
}


/**
 * User selected "List indexed files..." in menu.  Display dialog.
 *
 * @param dummy the menu entry
 * @param data the main dialog builder, unused
 */
void
GNUNET_GTK_main_menu_unindex_activate_cb (GtkWidget *dummy, gpointer data)
{
  GtkWidget *toplevel;
  struct UnindexDialogContext *udc;

  if (NULL != master_udc)
  {
    /* window already exists, raise focus */
    gtk_window_present (GTK_WINDOW (master_udc->dialog));
    return;
  }
  udc = GNUNET_new (struct UnindexDialogContext);
  udc->builder =
    GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_unindex.glade", udc);
  if (NULL == udc->builder)
  {
    GNUNET_break (0);
    GNUNET_free (udc);
    return;
  }
  udc->dialog = GTK_WIDGET (
    gtk_builder_get_object (udc->builder, "GNUNET_FS_GTK_unindex_dialog"));
  udc->treeview = GTK_TREE_VIEW (
    gtk_builder_get_object (udc->builder, "GNUNET_FS_GTK_unindex_treeview"));
  udc->selection = gtk_tree_view_get_selection (udc->treeview);
  udc->unindex_button = GTK_WIDGET (
    gtk_builder_get_object (udc->builder, "GNUNET_FS_GTK_unindex_button"));
  udc->model = GTK_TREE_MODEL (
    gtk_builder_get_object (udc->builder, "GNUNET_FS_GTK_unindex_liststore"));
  udc->gic = GNUNET_FS_get_indexed_files (GNUNET_FS_GTK_get_fs_handle (),
                                          &add_indexed_file,
                                          udc);
  toplevel = gtk_widget_get_toplevel (dummy);
  if (GTK_IS_WINDOW (toplevel))
    gtk_window_set_transient_for (GTK_WINDOW (udc->dialog),
                                  GTK_WINDOW (toplevel));
  gtk_window_present (GTK_WINDOW (udc->dialog));
  master_udc = udc;
}


/**
 * An unindex operation resumed.  Setup the
 * internal context for Gtk.
 *
 * @param uc unindex context with the FS library
 * @param filename name of file being unindexed
 * @param filesize size of the file
 * @param completed how many bytes were done so far
 * @param emsg NULL if everything is fine, otherwise error message
 * @return entry for the resumed operation
 */
struct UnindexEntry *
GNUNET_FS_GTK_unindex_handle_resume_ (struct GNUNET_FS_UnindexContext *uc,
                                      const char *filename,
                                      uint64_t filesize,
                                      uint64_t completed,
                                      const char *emsg)
{
  struct UnindexEntry *ue;

  ue = GNUNET_new (struct UnindexEntry);
  ue->filename = GNUNET_strdup (filename);
  if (NULL != emsg)
    ue->emsg = GNUNET_strdup (emsg);
  ue->filesize = filesize;
  ue->uc = uc;
  ue->progress = (gint) ((100LL * completed) / filesize);
  GNUNET_CONTAINER_DLL_insert (ue_head, ue_tail, ue);
  return ue;
}


/**
 * FS notified us that our unindex operation was stopped.
 *
 * @param ue operation that stopped
 */
void
GNUNET_FS_GTK_unindex_handle_stop_ (struct UnindexEntry *ue)
{
  GtkTreePath *path;
  GtkTreeIter iter;
  GtkTreeModel *model;

  if (NULL != ue->rr)
  {
    path = gtk_tree_row_reference_get_path (ue->rr);
    model = gtk_tree_row_reference_get_model (ue->rr);
    gtk_tree_row_reference_free (ue->rr);
    ue->rr = NULL;
    GNUNET_assert (TRUE == gtk_tree_model_get_iter (model, &iter, path));
    gtk_tree_path_free (path);
    gtk_list_store_set (GTK_LIST_STORE (model),
                        &iter,
                        UNINDEX_MC_UNINDEX_CONTEXT,
                        NULL,
                        -1);
  }
  GNUNET_CONTAINER_DLL_remove (ue_head, ue_tail, ue);
  GNUNET_free (ue->emsg);
  GNUNET_free (ue->filename);
  GNUNET_free (ue);
}


/**
 * FS notified us that our unindex operation had an error.
 *
 * @param ue operation that had an error
 * @param emsg error message
 */
void
GNUNET_FS_GTK_unindex_handle_error_ (struct UnindexEntry *ue, const char *emsg)
{
  GtkTreePath *path;
  GtkTreeIter iter;
  GtkTreeModel *model;

  ue->emsg = GNUNET_strdup (emsg);
  if (NULL == ue->rr)
    return;
  path = gtk_tree_row_reference_get_path (ue->rr);
  model = gtk_tree_row_reference_get_model (ue->rr);
  GNUNET_assert (TRUE == gtk_tree_model_get_iter (model, &iter, path));
  gtk_tree_path_free (path);
  gtk_list_store_set (GTK_LIST_STORE (model),
                      &iter,
                      UNINDEX_MC_BACKGROUND_COLOR,
                      "red",
                      UNINDEX_MC_ERROR,
                      emsg,
                      -1);
}


/**
 * FS notified us that our unindex operation made progress
 *
 * @param ue operation that made progress
 * @param completed number of bytes completed now
 */
void
GNUNET_FS_GTK_unindex_handle_progress_ (struct UnindexEntry *ue,
                                        uint64_t completed)
{
  GtkTreePath *path;
  GtkTreeIter iter;
  GtkTreeModel *model;

  ue->progress = (gint) ((100LL * completed) / ue->filesize);
  if (NULL == ue->rr)
    return;
  path = gtk_tree_row_reference_get_path (ue->rr);
  model = gtk_tree_row_reference_get_model (ue->rr);
  GNUNET_assert (TRUE == gtk_tree_model_get_iter (model, &iter, path));
  gtk_tree_path_free (path);
  gtk_list_store_set (GTK_LIST_STORE (model),
                      &iter,
                      UNINDEX_MC_UNINDEX_PROGRESS,
                      ue->progress,
                      -1);
}


/**
 * FS notified us that our unindex operation completed
 *
 * @param ue operation that completed
 */
void
GNUNET_FS_GTK_unindex_handle_completed_ (struct UnindexEntry *ue)
{
  GtkTreePath *path;
  GtkTreeIter iter;
  GtkTreeModel *model;

  if (NULL != ue->rr)
  {
    path = gtk_tree_row_reference_get_path (ue->rr);
    model = gtk_tree_row_reference_get_model (ue->rr);
    GNUNET_assert (TRUE == gtk_tree_model_get_iter (model, &iter, path));
    gtk_tree_path_free (path);
    gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
    gtk_tree_row_reference_free (ue->rr);
    ue->rr = NULL;
  }
  GNUNET_FS_unindex_stop (ue->uc);
}


/* end of gnunet-fs-gtk_unindex.c */
