/* NessusClient
 * Copyright (C) 1998, 2005 Renaud Deraison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * In addition, as a special exception, Renaud Deraison
 * gives permission to link the code of this program with any
 * version of the OpenSSL library which is distributed under a
 * license identical to that listed in the included COPYING.OpenSSL
 * file, and distribute linked combinations including the two.
 * You must obey the GNU General Public License in all respects
 * for all of the code used other than OpenSSL.  If you modify
 * this file, you may extend this exception to your version of the
 * file, but you are not obligated to do so.  If you do not wish to
 * do so, delete this exception statement from your version.
 */

#include <time.h>
#include <includes.h>
#include "comm.h"
#include "nessus_i18n.h"

#ifdef USE_GTK
#include <gtk/gtk.h>
#include "../nessus_plugin.h"
#include "../plugin_infos.h"
#include "error_dlg.h"
#include "../xpm/warning_small.xpm"
#include "filter.h"
#include "context.h"
#include "prefs_help.h"
#include "prefs_plugins_tree.h"



/** The columns of the tree store we use. */
enum {
  /** The name of the family/plugin. */
  COL_NAME,

  /** struct arglist * of the plugin. For a family the value is NULL. */
  COL_PLUGIN,

  /** Number of columns. */
  NUM_COLS
};


/**
 * Define a new signal, "statistics-changed", to be emitted when the
 * plugin tree changes in a way that may have changed the statistics
 * (number of plugins, number of enabled plugins, number of filtered
 * plugins) tree view.
 *
 * The signal handlers have the following signature:
 *
 * void statistics_changed_handler (GtkWidget* tree_view, gpointer user_data)
 */

/** ID of the "statistics-changed"- signal. */
static gint statistics_changed_signal = 0;

/**
 * @brief Register the "statistics-changed" signal with glib.
 * 
 * This function must be called before the signal is used for the first time in
 * any way.
 * It may be called multiple times as it only registers the signal on the first
 * call.
 */
static void
create_statistics_changed_signal()
{
  if (statistics_changed_signal == 0)
    {
      statistics_changed_signal = g_signal_new("statistics-changed",
                           GTK_TYPE_TREE_VIEW, G_SIGNAL_RUN_LAST, 0, NULL, NULL,
                           g_cclosure_marshal_VOID__VOID, GTK_TYPE_NONE, 0);
    }
}

/**
 * @brief Emit the "statistics-changed" signal with the tree_view as the instance.
 * 
 * @param tree_view The signal- emitting treeview.
 */
static void
emit_statistics_changed (GtkWidget* tree_view)
{
  g_signal_emit (tree_view, statistics_changed_signal, 0);
}

/**
 * @brief Handler for the tree model's "row-changed" signal.
 * 
 * Emit the "statistics-changed" signal on the tree model.
 */
static void
emit_statistics_when_row_changed (GtkTreeModel *treemodel,
                                  GtkTreePath *arg1,
                                  GtkTreeIter *arg2,
                                  gpointer user_data)
{
  emit_statistics_changed (GTK_WIDGET(user_data));
}

/**
 * @brief Handler for the tree model's "row-deleted" signal.
 * 
 * Emit the "statistics-changed" signal on the tree model.
 */
static void
emit_statistics_when_row_deleted (GtkTreeModel *treemodel, GtkTreePath *arg1,
                                  gpointer user_data)
{
  emit_statistics_changed (GTK_WIDGET(user_data));
}


/**
 * @brief Return whether the family given by the model and iter is enabled.
 *
 * The iter argument must be the iter pointing to the family row.
 * A family is enabled when at least one of it's plugins is enabled as
 * determined by plug_get_launch.
 * 
 * @return TRUE if the family that model and iter define is enabled, FALSE
 *         otherwise.
 *
 * @TODO Make use of the third state of checkboxes ("half"), to indicate that
 *       some (but not all) NVTs of a family are enabled.
 */
static gboolean
is_family_enabled (GtkTreeModel* model, GtkTreeIter * iter)
{
  GtkTreeIter child_iter;

  if (gtk_tree_model_iter_children (model, &child_iter, iter))
    {
      do
        {
          struct nessus_plugin *plugin;
          gtk_tree_model_get (model, &child_iter, COL_PLUGIN, &plugin, -1);
          if (plugin->enabled)
            return TRUE;
        }
      while (gtk_tree_model_iter_next (model, &child_iter));
    }
  return FALSE;
}


/**
 * @brief Determine whether a warning should be shown for the given row.
 *
 * Some plugins are dangerous and a warning sign is shown for the them
 * in the tree view.  This function determines whether a warning is
 * necessary for a particular row of the tree model.  The row can be
 * specified either with a path or an iter.  If an iter is given,
 * i.e. the iter parameter is not NULL, the iter is used to retrieve the
 * plugin information and the path argument is ignored.  If iter is
 * NULL, the path argument must be the GtkTreePath for the row and it is
 * used to determine the iter needed to access the row data.
 *
 * The return value is true if the category of the plugin is one of
 * "denial", "kill_host", "flood" or "destructive_attack" and false
 * otherwise.
 */
static int
should_show_warning (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter)
{
  GtkTreeIter real_iter;
  struct nessus_plugin *plugin;
  char *cat;
  int warning = FALSE;

  if (!iter)
  {
    gtk_tree_model_get_iter(model, &real_iter, path);
    iter = &real_iter;
  }

  gtk_tree_model_get(model, iter, COL_PLUGIN, &plugin, -1);
  if (plugin)
  {
    cat = plugin->category;
    warning = cat ? (!strcmp(cat, "denial") ||
                     !strcmp(cat, "kill_host") ||
                     !strcmp(cat, "flood") ||
                     !strcmp(cat, "destructive_attack")) : 0;
  }

  return warning;
}


/**
 * @brief Trigger a row update in any view of the given model.
 *
 * The iter should be an iter for the row in question.  This function is
 * needed because some information shown in the plugin tree is not
 * directly stored in the tree store.  When that information is changed
 * the update is not automatically noted by the tree views because the
 * model doesn't send any signals.
 */
static void
trigger_row_update (GtkTreeModel *model, GtkTreeIter *iter)
{
  GtkTreePath *path = gtk_tree_model_get_path(model, iter);

  gtk_tree_model_row_changed(model, path, iter);

  gtk_tree_path_free(path);
}



/**
 * @brief Toggle the active/launch flag of a plugin.
 *
 * This function is called when the checkbox in the tree view widget has
 * been toggled by the user.  There are two cases: The row on which it
 * happens is a family, in which case all plugins of the family are
 * activated/deactivated depending on the state of the family checkbox,
 * or the row is a single plugin in which case on that plugins flag is
 * changed.
 *
 * This function expects the tree view in the data parameter.  The
 * path_str must describe the path in the model of that tree view.
 *
 * The changes are always made on the actual plugins, that is the
 * arglists, with the function plug_set_launch.  To update the view, the
 * appropriate signal is emitted for the rows on the model that are
 * modified.  If a plugin is modified, a signal is also emitted for it's
 * parent family.
 */
static void
active_toggled (GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
{
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(data));
  GtkTreeIter iter;
  struct nessus_plugin *plugin;

  gtk_tree_model_get_iter_from_string(model, &iter, path_str);

  /* If the item is a family instead of a plugin, toggle the state of
   * all the children as well.  Since only families have children, we
   * can determine whether the iter points to a family by checking
   * whether we can get an iterator for its children.
   */
  if (gtk_tree_model_iter_has_child(model, &iter))
  {
    GtkTreeIter child_iter;

    gboolean launch = !is_family_enabled(model, &iter);

    gtk_tree_model_iter_children(model, &child_iter, &iter);
    do
    {
      gtk_tree_model_get(model, &child_iter, COL_PLUGIN, &plugin, -1);
      plugin->enabled = launch;
      trigger_row_update(model, &child_iter);
    }
    while (gtk_tree_model_iter_next(model, &child_iter));
  }
  else
  {
    GtkTreeIter parent_iter;

    gtk_tree_model_get(model, &iter, COL_PLUGIN, &plugin, -1);
    plugin->enabled = ! plugin->enabled;
    trigger_row_update(model, &iter);

    /* update the parent too because the active flag of the family
     * depends on the active flags of all of its children */
    gtk_tree_model_iter_parent(model, &parent_iter, &iter);
    trigger_row_update(model, &parent_iter);
  }
}

/**
 * @brief Called when the user double clicks on a row of the tree view.
 * 
 * Pop up the plugin info dialog if the row is a plugin, not a family.
 * 
 * @param user_data ignored (callback).
 */
static void
row_activated (GtkTreeView *treeview, GtkTreePath *path,
               GtkTreeViewColumn *column, gpointer user_data)
{
  GtkTreeModel * model = gtk_tree_view_get_model(treeview);
  int depth = gtk_tree_path_get_depth(path);

  if (depth > 1)
  {
    GtkTreeIter iter;
    struct nessus_plugin *plugin;
    gtk_tree_model_get_iter(model, &iter, path);
    gtk_tree_model_get(model, &iter, COL_PLUGIN, &plugin, -1);

    plugin_info_window_setup(plugin, plugin->name);
  }
}


/**
 * @brief Make the check boxes and warning "buttons" work on the first mouse click.
 *
 * Normally, the tree view only selects a line on the first click with
 * the mouse button.  Only clicks on a selected row will toggle a check
 * box.  To work around that, we handle button clicks ourselves in some
 * cases.  Any click on in the column with the check box will call
 * gtk_tree_view_set_cursor for the row/column which will lead to the
 * corresponding check box being toggled.  Also, a click on a warning
 * sign will pop up the warning dialog.
 *
 * When the event was handled, i.e. one of the two cases above occurred,
 * the function returns TRUE to indicate that the event was handled.
 * Otherwise FALSE is returned to indicate that the event wasn't handled
 * yet so that we get the default behavior of the tree view widget in
 * all other cases.
 */
static gboolean
button_press_event (GtkTreeView *tree, GdkEventButton *event,
                    gpointer user_data)
{
  GtkTreePath * path;
  GtkTreeViewColumn *column;
  int colnum;
  gboolean event_was_handled = FALSE;

  if (!gtk_tree_view_get_path_at_pos (tree, event->x, event->y, &path, &column,
                                      NULL, NULL))
    return FALSE;

  colnum = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "colnum"));

  if (colnum == 1)
    {
      if (should_show_warning(gtk_tree_view_get_model(tree), path, NULL))
        {
          show_info ("%s", HLP_WARNING);
        }
      event_was_handled = TRUE;
    }
  else if (colnum == 2)
    {
      gtk_tree_view_set_cursor(tree, path, column, TRUE);
      event_was_handled = TRUE;
    }

  gtk_tree_path_free (path);

  return event_was_handled;
}


/**
 * @brief Set the "active" property of the toggle cell renderer.
 */
static void
active_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
                  GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
{
  struct nessus_plugin *plugin;
  gboolean is_active = FALSE;
  GObject *tree_view = G_OBJECT(data);

  if (gtk_tree_model_iter_has_child(model, iter))
  {
    is_active = is_family_enabled(model, iter) == 0 ? 0:1;
  }
  else
  {
    gtk_tree_model_get(model, iter, COL_PLUGIN, &plugin, -1);
    is_active = plugin->enabled == 0 ? 0:1;
  }

  g_object_set(G_OBJECT(cell), "active", is_active, NULL);

  /* the cell is activatable if the tree is not in read-only mode */
  g_object_set(G_OBJECT(cell), "activatable",
      (gboolean)!GPOINTER_TO_INT(g_object_get_data(tree_view, "read-only")),
      NULL);
}


/**
 * @brief Set the "pixbuf" property of the warning cell renderer.
 */
static void
warning_data_func(tree_column, cell, model, iter, data)
  GtkTreeViewColumn *tree_column;
  GtkCellRenderer *cell;
  GtkTreeModel *model;
  GtkTreeIter *iter;
  gpointer data;
{
  static GdkPixbuf* warning_pixbuf = NULL;
  GdkPixbuf *value = NULL;

  if (should_show_warning(model, NULL, iter))
  {
    if (!warning_pixbuf)
      warning_pixbuf = gdk_pixbuf_new_from_xpm_data((const char**) warning_small_xpm);
    value = warning_pixbuf;
  }

  g_object_set(G_OBJECT(cell), "pixbuf", value, NULL);
}



/**
 * @brief Create a new plugin tree and model and hooks up widgets in ctrls.
 * 
 * @param ctrls[in,out] Arglist in which to hook up widgets.
 */
GtkWidget *
prefs_create_plugins_tree (struct context *context, struct arglist* ctrls)
{
  GtkWidget *tree;
  GtkTreeSelection *selection;
  GtkTreeViewColumn *column;
  GtkCellRenderer *renderer;

  /* Make sure the "statistics-changed" signal exists */
  create_statistics_changed_signal();

  /* Create the tree and its columns*/
  tree = gtk_tree_view_new();
  arg_add_value(ctrls, "PLUGINS_TREE_VIEW", ARG_PTR, -1, tree);

  renderer = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new_with_attributes(_("Name"), renderer,
      "text", COL_NAME, NULL);
  gtk_tree_view_column_set_resizable(column, TRUE);
  gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
  g_object_set_data(G_OBJECT(column), "colnum", GINT_TO_POINTER(0));

  renderer = gtk_cell_renderer_pixbuf_new();
  column = gtk_tree_view_column_new_with_attributes(_("Warning"), renderer,
      NULL);
  gtk_tree_view_column_set_cell_data_func(column, renderer, warning_data_func,
      NULL, NULL);
  gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
  g_object_set_data(G_OBJECT(column), "colnum", GINT_TO_POINTER(1));

  renderer = gtk_cell_renderer_toggle_new();
  g_signal_connect(renderer, "toggled", G_CALLBACK(active_toggled), tree);
  column = gtk_tree_view_column_new_with_attributes(_("Active"), renderer,
      NULL);
  gtk_tree_view_column_set_cell_data_func(column, renderer, active_data_func,
      tree, NULL);
  gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
  g_object_set_data(G_OBJECT(column), "colnum", GINT_TO_POINTER(2));

  /* selection settings */
  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
  gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);

  g_signal_connect(G_OBJECT(tree), "row-activated", G_CALLBACK(row_activated),
      NULL);
  g_signal_connect(G_OBJECT(tree), "button-press-event",
      G_CALLBACK(button_press_event), NULL);

  return tree;
}


/**
 * @brief Fill the context's tree model with the plugins, applying the filter
 *        from ctrls if one is set.
 */
static void
fill_plugin_tree_store (struct context *context,   struct arglist *ctrls)
{
  struct nessus_plugin *plugs = context->plugins;
  GtkTreeModel *store = GTK_TREE_MODEL(context->plugin_tree_store);
  struct plugin_filter *filter = arg_get_value(ctrls, "FILTER");
  GHashTable* family_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
      NULL, (GDestroyNotify)gtk_tree_path_free);

  /* Add the plugins and families.  The family nodes are created when
   * the first plugin of that family is encountered */
  while(plugs != NULL )
  {
    GtkTreeIter family_iter, insert_iter;
    char *plug_family;
    GtkTreePath * family_path;
    /* char *summary = arg_get_value(plugs->value, "SUMMARY"); */

    if(filter && filter_plugin(filter, plugs))
    {
      plugs = plugs->next;
      continue;
    }

    /* Determine the iter of the family node for the current plugin.  If
     * the node doesn't exist yet, create it. */
    plug_family = plugs->family;
    family_path = g_hash_table_lookup(family_hash, plug_family);

    if (family_path == NULL )
    {
      gtk_tree_store_append(GTK_TREE_STORE(store), &family_iter, NULL);
      family_path = gtk_tree_model_get_path(store, &family_iter);
      g_hash_table_insert(family_hash, plug_family, family_path);
      gtk_tree_store_set (GTK_TREE_STORE(store), &family_iter,
                          COL_NAME, plug_family, COL_PLUGIN, NULL, -1);
    }
    else
    {
      gtk_tree_model_get_iter(store, &family_iter, family_path);
    }

    /* Now family_iter should be set to the iter of the family the
     * current plugin belongs to.  Append the plugin to that family. */
    gtk_tree_store_append(GTK_TREE_STORE(store), &insert_iter, &family_iter);
    gtk_tree_store_set (GTK_TREE_STORE(store), &insert_iter,
                        COL_NAME, plugs->name, COL_PLUGIN, plugs, -1);

    plugs = plugs->next;
  }

  g_hash_table_destroy(family_hash);
}


/**
 * @brief Update the plugin tree with a different context.
 * 
 * The plugin-tree
 * maintains a tree store and a sorted model in the context.  Make the
 * tree store of the new context the model shown in the plugin tree view
 * (taken from ctrls).  If the store doesn't exist yet or if the
 * force_rebuild parameter is TRUE, this function creates a new store.
 *
 * Also, connect to some of the model's signals so that we can emit the
 * statistics_changed signal.
 */
void
prefs_plugin_tree_context_changed (struct context *context,
                                   struct arglist *ctrls,
                                   gboolean force_rebuild)
{
  GtkTreeView *tree_view = arg_get_value(ctrls, "PLUGINS_TREE_VIEW");
  GtkTreeModel *model = context->plugin_tree_model;
  GtkTreeModel *old_model = gtk_tree_view_get_model(tree_view);

  /* If the caller wants, force a rebuild of the store by deleting the
   * old one */
  if (force_rebuild)
    context_reset_plugin_tree(context);

  /* If the context doesn't have a tree store yet, create it together
   * with the sort model and fill it */
  if (context->plugin_tree_store == NULL)
  {
    context->plugin_tree_store = gtk_tree_store_new(NUM_COLS, G_TYPE_STRING,
	G_TYPE_POINTER);

    model = gtk_tree_model_sort_new_with_model(
      GTK_TREE_MODEL(context->plugin_tree_store));
    gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model),
	0, GTK_SORT_ASCENDING);
    context->plugin_tree_model = model;

    fill_plugin_tree_store(context, ctrls);
    g_object_set_data(G_OBJECT(context->plugin_tree_store), "plugins",
	context->plugins);
  }

  /* Set the new model, reconnecting the signals that we need to emit
   * the statistics_changed signal */
  if (old_model != NULL)
  {
    g_signal_handlers_disconnect_by_func(old_model,
	emit_statistics_when_row_deleted, tree_view);
    g_signal_handlers_disconnect_by_func(old_model,
	emit_statistics_when_row_changed, tree_view);
  }

  gtk_tree_view_set_model(tree_view, model);

  if (model != NULL)
  {
    g_signal_connect (model, "row-changed",
                      (GCallback) emit_statistics_when_row_changed, tree_view);
    g_signal_connect (model, "row-deleted",
                      (GCallback) emit_statistics_when_row_deleted, tree_view);
  }

  /* When a new model is set, the statistics will have changed: */
  emit_statistics_changed (GTK_WIDGET(tree_view));
}


/**
 * @brief Enable/Disable all plugins in the tree.
 */
void
prefs_plugin_tree_enable_all (GtkTreeView *tree, int enable)
{
  GtkTreeModel *model = gtk_tree_view_get_model(tree);
  GtkTreeIter iter, child_iter;
  struct nessus_plugin *plugin;

  if (gtk_tree_model_get_iter_first(model, &iter))
  {
    do
    {
      if (gtk_tree_model_iter_children(model, &child_iter, &iter))
      {
	do
	{
	  gtk_tree_model_get(model, &child_iter, COL_PLUGIN, &plugin, -1);
	  plugin->enabled = enable;
	  trigger_row_update(model, &child_iter);
	}
	while (gtk_tree_model_iter_next(model, &child_iter));

	trigger_row_update(model, &iter);
      }
    }
    while (gtk_tree_model_iter_next(model, &iter));
  }
}

/**
 * @brief Compute some statistics for the plugin tree.
 *
 * The tree_view parameter must be a tree view created by
 * prefs_create_plugins_tree and filled with
 * prefs_fill_plugin_tree_model.
 *
 * The function determines the total number of plugins and the number of
 * active plugins and stores these values is *total and *enabled.
 * 
 * @param tree_view    The treeview for which to count nvts.
 * @param[out] total   Will contain the total number of NVTs in the tree
 *                     model of the tree view tree_view.
 * @param[out] enabled Will contain the number of enabled NVTs in the tree model
 *                     of the tree view tree_view.
 */
void
prefs_plugin_tree_statistics (GtkTreeView *tree_view, int *total, int *enabled)
{
  GtkTreeModel *tree_model = gtk_tree_view_get_model(tree_view);
  GtkTreeModel *real_model = gtk_tree_model_sort_get_model(
    GTK_TREE_MODEL_SORT(tree_model));
  struct nessus_plugin *plugins = g_object_get_data(G_OBJECT(real_model),
      "plugins");

  *total = 0;
  *enabled = 0;

  while (plugins != NULL)
  {
    *total += 1;
    if (plugins->enabled)
      *enabled += 1;

    plugins = plugins->next;
  }
}

/**
 * @brief Make the tree "read only", meaning that the user cannot change the
 *        "active" tflag.
 */
void
prefs_plugin_tree_read_only (GtkTreeView* tree_view, gboolean readonly)
{
  g_object_set_data (G_OBJECT(tree_view), "read-only",
                     GINT_TO_POINTER(readonly));
}

#endif /* USE_GTK */
