/*
     This file is part of GNUnet.
     Copyright (C) 2010, 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_common.c
 * @brief Common functions used in various places
 * @author Christian Grothoff
 */
#include "gnunet-fs-gtk_common.h"
#include "gnunet-fs-gtk_download-save-as.h"
#include "gnunet-fs-gtk.h"
#include "gnunet-fs-gtk_event-handler.h"


/**
 * Converts metadata specified by @a data of size @a data_len
 * and saved in format @a format to UTF-8 encoded string.
 * Works only for C-string and UTF8 metadata formats
 * (returns NULL for everything else).
 * Verifies UTF-8 strings.
 *
 * @param format format of the @a data
 * @param data data to convert
 * @param data_len length of the @a data buffer (in bytes)
 * @return NULL if can't be converted, allocated string otherwise,
 *         freeable with #GNUNET_free.
 */
char *
GNUNET_FS_GTK_dubious_meta_to_utf8 (enum EXTRACTOR_MetaFormat format,
                                    const char *data,
                                    size_t data_len)
{
  switch (format)
  {
  case EXTRACTOR_METAFORMAT_UTF8:
    /* data must not contain NULLs (hence the -1) */
    if (g_utf8_validate (data, data_len - 1, NULL))
      return GNUNET_strdup (data);
    GNUNET_log (
      GNUNET_ERROR_TYPE_INFO,
      _ (
        "Failed to validate supposedly utf-8 string `%s' of length %u, assuming it to be a C string\n"),
      data,
      (unsigned int) data_len);
    format = EXTRACTOR_METAFORMAT_C_STRING;
  /* fall-through */
  case EXTRACTOR_METAFORMAT_C_STRING:
    if (data_len > 0)
    {
      /* There are no guarantees that data is NULL-terminated, AFAIU,
       * so let's play it safe, shall we?
       */
      char data_copy[data_len + 1];

      memcpy (data_copy, data, data_len);
      data_copy[data_len] = '\0';
      return GNUNET_GTK_from_loc_to_utf8 (data_copy);
    }
    break;
  default:
    break;
  }
  return NULL;
}


/**
 * Add meta data to list store.
 *
 * @param cls closure (the GtkListStore)
 * @param plugin_name name of the plugin that produced this value;
 *        special values can be used (i.e. '\<zlib\>' for zlib being
 *        used in the main libextractor library and yielding
 *        meta data).
 * @param type libextractor-type describing the meta data
 * @param format basic format information about data
 * @param data_mime_type mime-type of data (not of the original file);
 *        can be NULL (if mime-type is not known)
 * @param data actual meta-data found
 * @param data_len number of bytes in @a data
 * @return 0 to continue (always)
 */
int
GNUNET_FS_GTK_add_meta_data_to_list_store (void *cls,
                                           const char *plugin_name,
                                           enum EXTRACTOR_MetaType type,
                                           enum EXTRACTOR_MetaFormat format,
                                           const char *data_mime_type,
                                           const char *data,
                                           size_t data_len)
{
  GtkListStore *ls = GTK_LIST_STORE (cls);
  char *data_to_insert;

  data_to_insert = GNUNET_FS_GTK_dubious_meta_to_utf8 (format, data, data_len);
  if (NULL == data_to_insert)
    return 0;
  gtk_list_store_insert_with_values (
    ls,
    NULL,
    G_MAXINT,
    GNUNET_GTK_FS_MAIN_WINDOW_META_DATA_MC_META_TYPE,
    type,
    GNUNET_GTK_FS_MAIN_WINDOW_META_DATA_MC_META_FORMAT,
    format,
#if HAVE_EXTRACTOR
    GNUNET_GTK_FS_MAIN_WINDOW_META_DATA_MC_META_TYPE_STRING,
    EXTRACTOR_metatype_to_string (type),
#endif
    GNUNET_GTK_FS_MAIN_WINDOW_META_DATA_MC_META_VALUE,
    data_to_insert,
    -1);
  GNUNET_free (data_to_insert);
  return 0;
}


/**
 * Obtain pixbuf from thumbnail data in meta data.
 *
 * @param meta input meta data
 * @return NULL on error, otherwise the embedded thumbnail
 */
GdkPixbuf *
GNUNET_FS_GTK_get_thumbnail_from_meta_data (
  const struct GNUNET_FS_MetaData *meta)
{
  GdkPixbuf *pixbuf;
  GdkPixbufLoader *loader;
  size_t ts;
  unsigned char *thumb;

  thumb = NULL;
  ts = GNUNET_FS_meta_data_get_thumbnail (meta, &thumb);
  if (0 == ts)
    return NULL;
  loader = gdk_pixbuf_loader_new ();
  gdk_pixbuf_loader_write (loader, (const guchar *) thumb, ts, NULL);
  pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
  gdk_pixbuf_loader_close (loader, NULL);
  if (NULL != pixbuf)
    g_object_ref (pixbuf);
  g_object_unref (loader);
  GNUNET_free (thumb);
  return pixbuf;
}


/**
 * mmap the given file and run the #GNUNET_FS_directory_list_contents
 * function on it.
 *
 * @param filename name with the directory
 * @param dep function to call on each entry
 * @param dep_cls closure for @a dep
 * @return #GNUNET_OK on success
 */
int
GNUNET_FS_GTK_mmap_and_scan (const char *filename,
                             GNUNET_FS_DirectoryEntryProcessor dep,
                             void *dep_cls)
{
  struct GNUNET_DISK_FileHandle *fh;
  struct GNUNET_DISK_MapHandle *mh;
  uint64_t fsize;
  void *ddata;
  int ret;

  if (GNUNET_OK !=
      GNUNET_DISK_file_size (filename, &fsize, GNUNET_YES, GNUNET_YES))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  if (0 == fsize)
  {
    /* empty file, cannot be a directory */
    return GNUNET_SYSERR;
  }
  if (NULL == (fh = GNUNET_DISK_file_open (filename,
                                           GNUNET_DISK_OPEN_READ,
                                           GNUNET_DISK_PERM_NONE)))
  {
    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", filename);
    return GNUNET_SYSERR;
  }
  if (NULL == (ddata = GNUNET_DISK_file_map (fh,
                                             &mh,
                                             GNUNET_DISK_MAP_TYPE_READ,
                                             (size_t) fsize)))
  {
    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "mmap", filename);
    GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
    return GNUNET_SYSERR;
  }
  if (GNUNET_SYSERR == GNUNET_FS_directory_list_contents ((size_t) fsize,
                                                          ddata,
                                                          0,
                                                          dep,
                                                          dep_cls))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                _ ("Selected file `%s' is not a GNUnet directory!\n"),
                filename);
    ret = GNUNET_SYSERR;
  }
  else
  {
    ret = GNUNET_OK;
  }
  GNUNET_break (GNUNET_OK == GNUNET_DISK_file_unmap (mh));
  GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
  return ret;
}


/**
 * Obtain the string we will use to describe a search result from
 * the respective meta data.
 *
 * @param meta meta data to inspect
 * @param is_a_dup is set to #GNUNET_YES if the result is a dup, and there was
 *        no description to be found. #GNUNET_NO otherwise.
 * @return description of the result in utf-8, never NULL
 */
char *
GNUNET_FS_GTK_get_description_from_metadata (
  const struct GNUNET_FS_MetaData *meta,
  int *is_a_dup)
{
  char *desc;
  char *utf8_desc;

  desc = GNUNET_FS_meta_data_get_first_by_types (
    meta,
    EXTRACTOR_METATYPE_PACKAGE_NAME,
    EXTRACTOR_METATYPE_TITLE,
    EXTRACTOR_METATYPE_BOOK_TITLE,
    EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME,
    EXTRACTOR_METATYPE_FILENAME,
    EXTRACTOR_METATYPE_DESCRIPTION,
    EXTRACTOR_METATYPE_ALBUM,
    EXTRACTOR_METATYPE_COMMENT,
    EXTRACTOR_METATYPE_SUBJECT,
    EXTRACTOR_METATYPE_KEYWORDS,
    -1);
  if (desc == NULL)
  {
    *is_a_dup = GNUNET_YES;
    return GNUNET_strdup (_ ("no description supplied"));
  }
  utf8_desc = GNUNET_FS_GTK_dubious_meta_to_utf8 (EXTRACTOR_METAFORMAT_UTF8,
                                                  desc,
                                                  strlen (desc) + 1);
  GNUNET_free (desc);
  if (utf8_desc == NULL)
  {
    *is_a_dup = GNUNET_YES;
    return GNUNET_strdup (_ ("no description supplied"));
  }
  *is_a_dup = GNUNET_NO;
  return utf8_desc;
}


/**
 * A URI was selected (or pasted into the application).  Run
 * the appropriate action.
 *
 * @param uri the URI
 * @param anonymity_level anonymity level to use
 */
void
GNUNET_FS_GTK_handle_uri (const struct GNUNET_FS_Uri *uri,
                          guint anonymity_level)
{
  GtkEntry *query_entry;
  GtkComboBox *ns_cb;
  GtkEntry *ns_entry;
  struct GNUNET_CRYPTO_EcdsaPublicKey want;
  struct GNUNET_IDENTITY_PublicKey pkey;

  if (GNUNET_FS_uri_test_chk (uri) || GNUNET_FS_uri_test_loc (uri))
  {
    struct DownloadEntry *de;

    de = GNUNET_new (struct DownloadEntry);
    de->anonymity = anonymity_level;
    de->uri = GNUNET_FS_uri_dup (uri);
    GNUNET_FS_GTK_open_download_as_dialog (de);
    return;
  }
  query_entry = GTK_ENTRY (
    GNUNET_FS_GTK_get_main_window_object ("main_window_search_entry"));
  ns_cb = GTK_COMBO_BOX (GNUNET_FS_GTK_get_main_window_object (
                           "main_window_search_namespace_combobox"));
  ns_entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (ns_cb)));
  if (GNUNET_FS_uri_test_sks (uri))
  {
    /* select the namespace */
    if (GNUNET_OK != GNUNET_FS_uri_sks_get_namespace (uri, &want))
    {
      GNUNET_break (0);
      return;
    }
    pkey.type = GNUNET_IDENTITY_TYPE_ECDSA;
    pkey.ecdsa_key = want;
    {
      const char *ns_zkey;

      ns_zkey = GNUNET_GNSRECORD_pkey_to_zkey (&pkey);
      gtk_entry_set_text (ns_entry, ns_zkey);
    }
    /* set search entry to the namespace identifier */
    {
      char *query_string;

      query_string = GNUNET_FS_uri_sks_get_content_id (uri);
      gtk_entry_set_text (query_entry, query_string);
      GNUNET_free (query_string);
    }
    return;
  }

  if (GNUNET_FS_uri_test_ksk (uri))
  {
    gtk_entry_set_text (ns_entry, "");
    /* set search entry to the query string */
    {
      char *query_string;

      query_string = GNUNET_FS_uri_ksk_to_string_fancy (uri);
      gtk_entry_set_text (query_entry, query_string);
      GNUNET_free (query_string);
    }
    return;
  }
  GNUNET_break (0);
}


/**
 * Converts a GtkTreeRowReference to a GtkTreeIter.
 *
 * @param rr row reference
 * @param iter pointer to an iter structure to fill
 * @return #GNUNET_OK if iter was filled, #GNUNET_SYSERR otherwise
 */
int
GNUNET_GTK_get_iter_from_reference (GtkTreeRowReference *rr, GtkTreeIter *iter)
{
  int result = GNUNET_SYSERR;
  if (rr != NULL)
  {
    if (gtk_tree_row_reference_valid (rr))
    {
      GtkTreePath *path;
      GtkTreeModel *model;
      path = gtk_tree_row_reference_get_path (rr);
      model = gtk_tree_row_reference_get_model (rr);
      if ((path != NULL) && (model != NULL))
      {
        if (gtk_tree_model_get_iter (model, iter, path))
          result = GNUNET_OK;
        gtk_tree_path_free (path);
      }
    }
  }
  return result;
}


/**
 * Creates a GtkTreeRowReference from a GtkTreeIter.
 *
 * @param model a model to reference
 * @param iter an iter that points to a row in the model
 * @return newly created reference or NULL in case of error
 */
GtkTreeRowReference *
GNUNET_GTK_get_reference_from_iter (GtkTreeModel *model, GtkTreeIter *iter)
{
  GtkTreeRowReference *result = NULL;
  if ((iter != NULL) && (model != NULL))
  {
    GtkTreePath *path = gtk_tree_model_get_path (model, iter);
    if (path != NULL)
    {
      result = gtk_tree_row_reference_new (model, path);
      gtk_tree_path_free (path);
    }
  }
  return result;
}


/**
 * Fills "next_iter" with iterator for an item that comes next in the tree
 * after "iter".
 * Next item is, in order of precedence:
 * 1) First child of "iter", if "iter" has children and "allow_children"
 *   is enabled.
 * 2) Next sibling of "iter", unless "iter" is the last sibling.
 * If none of those are present, function recursively checks parents of
 *   "iter" until it finds next item or runs out of parents.
 *
 * @param model a model to reference
 * @param iter an iter that points to current row in the model
 * @param allow_children whether child of "iter" is considered to be next.
 * @param next_iter will be filled with the next row in the model on success
 * @return TRUE if next_iter is set to a valid iter,
 *         FALSE if ran out of parents
 */
gboolean
GNUNET_GTK_tree_model_get_next_flat_iter (GtkTreeModel *model,
                                          GtkTreeIter *iter,
                                          gboolean allow_children,
                                          GtkTreeIter *next_iter)
{
  GtkTreeIter current_iter = *iter;
  while (TRUE)
  {
    GtkTreeIter tmp_iter;

    tmp_iter = current_iter;
    if (gtk_tree_model_iter_next (model, &tmp_iter))
    {
      *next_iter = tmp_iter;
      return TRUE;
    }
    if (allow_children)
    {
      if (gtk_tree_model_iter_children (model, &tmp_iter, &current_iter))
      {
        *next_iter = tmp_iter;
        return TRUE;
      }
    }
    allow_children = FALSE;
    if (! gtk_tree_model_iter_parent (model, &tmp_iter, &current_iter))
      return FALSE;
    current_iter = tmp_iter;
  }
  return FALSE;
}


/* end of gnunet-fs-gtk-common.c */
